Skip to content

fix(docker): resolve LiteLLM startup failure on self-hosted dedicated deployments - #129

Merged
mateo-di merged 2 commits into
carto/mainfrom
sc-566140/restore-prisma-binary-targets
Aug 12, 2026
Merged

fix(docker): resolve LiteLLM startup failure on self-hosted dedicated deployments#129
mateo-di merged 2 commits into
carto/mainfrom
sc-566140/restore-prisma-binary-targets

Conversation

@mateo-di

@mateo-di mateo-di commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Since #121 (Upstream Sync: LiteLLM v1.92.0), the non_root runtime image can't apply Prisma migrations in any environment without internet egress: ded-k8s and any egress-restricted Self-Hosted deployment. prisma migrate deploy fails trying to download libquery_engine.so.node from binaries.prisma.sh, logs "Database migration failed but continuing startup" (non-fatal), and the pod comes up healthy with zero schema — every keyed AI-api request then 401s with The table public.LiteLLM_VerificationToken does not exist.

Root cause

The sync's restructured docker/Dockerfile.non_root dropped PRISMA_CLI_BINARY_TARGETS from the runtime-stage ENV block, silently reverting a prior CARTO fix for the same failure class (arm64 schema-engine fetching). But that env var was never the durable fix — Prisma's own platform detection is "arch-blind" on the Wolfi base, so relying on it to resolve a cached engine by name is inherently fragile.

Upstream had already fixed this properly, just not in the release CARTO happened to sync to: BerriAI#33853 and BerriAI#34325 (merged 2026-07-18 and 2026-07-23) bake the Prisma CLI and engines at a fixed path, /opt/prisma, and resolve them by exact path (PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary) instead of by Prisma's name-based platform guess. Their commit message describes the exact same symptom independently: "fell back to a nodeenv Node download that crashes on Wolfi (libatomic.so.1)." Both PRs merged to upstream main before v1.92.0 published, but v1.92.0's tag doesn't include them — BerriAI backports fixes to separate stable/X.Y.x branches (see BerriAI#34670), and this fix landed on stable/1.93.x, not on the v1.92.0 lineage CARTO's sync picked.

What changed

Cherry-picked upstream's actual fix (BerriAI#34325) rather than carrying a bespoke patch, so the fork stays aligned with upstream's approach:

  • docker/Dockerfile.non_root — as upstream merged it, minus CARTO's now-superseded PRISMA_CLI_BINARY_TARGETS/PRISMA_BINARY_CACHE_DIR lines (upstream's path-based resolution replaces what those were working around).
  • tests/proxy_migration_tests/test_offline_image_migration.py — upstream's own regression test, which reproduces this exact failure (fresh DB, internal-only no-egress network, arbitrary non-root uid) and asserts the schema was actually created.
  • .github/workflows/image-scan.yml — wires that test into CI, plus one CARTO-specific addition: added carto/main to the trigger's branches: list. Without it, this workflow (including this new regression test, and the existing vulnerability scan) never runs automatically on a CARTO PR, because it only ever triggered on main/litellm_internal_staging/litellm_oss_branch — never on carto/main, our actual default branch. This gap predates this fix entirely.

This is the only CARTO-specific customization needed. Adopting upstream's /opt/prisma mechanism as-is fully resolves this for both amd64 and arm64 — CARTO's old arm64-specific workaround is now obsolete, since path-based resolution doesn't depend on Prisma's arch-blind detection at all.

Note: v1.95.0 (the target of the currently-open sync PR #128) already includes this fix upstream — so once #128 merges, a future sync would carry it forward correctly. Landing this now unblocks the current Self-Hosted release cycle without waiting on that sync.

Verification

Passed at every layer:

  • Local build, --no-cache, offline against a fresh Postgres on a network with no route to the internet: clean startup, no binaries.prisma.sh calls.
  • Upstream's own test_offline_image_migration.py suite, run locally against the built image: both tests pass.
  • Image Scan CI on this PR: both offline-migration tests pass (the CI run is red only on an unrelated CVE-scan finding, not this change).
  • Real dedicated-k8s environment (the actual failure surface): deployed via CartoDB/cloud-native#27390, confirmed a live authenticated /v1/models request returns 200 with the full model list — no VerificationToken 401.

AI-generated code disclosure

This fix (investigation, the cherry-pick, conflict resolution, and all verification above) was done by Claude Code. Flagging for extra reviewer scrutiny given the Dockerfile/build-tooling nature of the change.

@mateo-di mateo-di self-assigned this Aug 12, 2026
…s run offline for any uid (BerriAI#34325)

* fix(docker): bake non_root prisma engines at /opt/prisma so migrations run offline for any uid

The non_root image baked the prisma CLI and engines under /app/.cache and used
the CLI's default (library) engine mode. Prisma stopped baking the library
engine, so `prisma migrate deploy` fell back to downloading it at startup,
which needs network egress and a writable cache. Under an arbitrary non-root
uid (OpenShift restricted-v2), an air-gapped network, or a readOnlyRootFilesystem,
that download fails and the proxy starts on an empty schema while every DB
endpoint returns 500. The migration entrypoint exits 0 on that failure, so a
default-uid `docker run` with network never surfaced it

Bake to /opt/prisma, a fixed world-readable path no cache mount shadows, and
pin PRISMA_CLI_PATH plus PRISMA_CLI_QUERY_ENGINE_TYPE=binary so the baked binary
engine is used directly, matching Dockerfile and Dockerfile.database. A
build-time guard asserts the binary query engine is present, so a future prisma
change that stops baking it fails the image build instead of silently degrading
migrations

Adds docker/test_offline_migration.sh, run from image-scan, which migrates a
fresh Postgres with no egress as a non-root uid and asserts the schema was
created, the case a default-uid `docker run` with network cannot catch

* test(docker): move the offline migration check into a gated pytest and stop pinning XDG_CACHE_HOME at the read-only bake

The offline migration check lived in docker/ as a shell script. It now lives in
tests/proxy_migration_tests/ as a pytest gated on LITELLM_IMAGE, matching the
sibling schema-migration test gated on DATABASE_URL, and image-scan invokes it
with pytest instead of bash. It also asserts the migration entrypoint's exit
code alongside the table count, so a crash or a container-startup failure fails
loudly rather than only surfacing as a low table count

Runtime XDG_CACHE_HOME pointed at /opt/prisma/.cache, which is baked a+rX with
no write, so any XDG-aware library writing a cache at runtime would be denied
for every uid. Leave it unset so it falls back to $HOME/.cache (/app/.cache,
created here and owned by the runtime uid), matching Dockerfile and
Dockerfile.database which never pin XDG at runtime. A second test guards against
a future edit pointing a cache or home var back at the read-only bake
@mateo-di
mateo-di force-pushed the sc-566140/restore-prisma-binary-targets branch from aff3eb7 to 26c3a72 Compare August 12, 2026 12:43
image-scan.yml only ever triggered on main/litellm_internal_staging/
litellm_oss_branch/litellm_** — carto/main was never in the list, so
this workflow (including the offline-migration regression test just
cherry-picked from upstream) has never run automatically on a CARTO PR
since the fork's default branch became carto/main.
@mateo-di
mateo-di marked this pull request as ready for review August 12, 2026 13:14
@mateo-di
mateo-di merged commit 536ae9c into carto/main Aug 12, 2026
12 of 13 checks passed
@mateo-di mateo-di changed the title fix(docker): non_root image can't run Prisma migrations without internet egress fix(docker): resolve LiteLLM startup failure on self-hosted dedicated deployments Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants