Skip to content

fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline - #33853

Merged
yuneng-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_/litellm-issue-33650-02faad
Jul 18, 2026
Merged

yuneng-berri merged 3 commits into
litellm_internal_stagingfrom
litellm_/litellm-issue-33650-02faad

Conversation

@yuneng-berri

@yuneng-berri yuneng-berri commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Relevant issues

Fixes #33650, fixes #24554

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Screenshots / Proof of Fix

Before, on the published image ghcr.io/berriai/litellm:v1.93.0-rc.1, reproducing the deployment shape from #33650 (runtime HOME is not /root, brand-new Postgres):

$ docker run -d --name ll-before --network net -e HOME=/home/litellm \
    -e DATABASE_URL=postgresql://postgres:pw@pg:5432/litellm -e LITELLM_MASTER_KEY=sk-test-1234 \
    ghcr.io/berriai/litellm:v1.93.0-rc.1

$ docker logs ll-before 2>&1 | grep -E "npm install|libatomic"
subprocess.CalledProcessError: Command '['/home/litellm/.cache/prisma-python/nodeenv/bin/npm', 'install', 'prisma@5.4.2']' returned non-zero exit status 127.
An error ocurred while installing the Prisma CLI; npm install log: node: error while loading shared libraries: libatomic.so.1: cannot open shared object file: No such file or directory

$ docker exec ll-before python -c "...POST /team/new with the master key..."
ERR: 500 {"error":{"message":"The table `public.LiteLLM_TeamTable` does not exist in the current database.","type":"internal_server_error","param":"None","code":"500"}}

$ docker exec pg psql -U postgres -d litellm -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';"
0

The same happens as an arbitrary non-root uid (--user 12345:0), failing earlier with PermissionError: [Errno 13] Permission denied: '/.cache' because HOME resolves to /

After, built at 3b52b6c, hardest case first: an internal-only docker network (zero egress, the registry and npm are unreachable), an arbitrary non-root uid, and a brand-new Postgres:

$ docker build -t litellm-fix:test .
$ docker network create --internal airgap
$ docker run -d --name pg --network airgap -e POSTGRES_PASSWORD=pw -e POSTGRES_DB=litellm postgres:16-alpine
$ docker run -d --name ll --network airgap --user 12345:0 \
    -e DATABASE_URL=postgresql://postgres:pw@pg:5432/litellm -e LITELLM_MASTER_KEY=sk-test-1234 litellm-fix:test

$ docker exec ll python -c "...POST /team/new with the master key..."
HTTP 200 {"team_alias":"matrix-e2","team_id":"..."}

$ docker exec pg psql -U postgres -d litellm -tAc "SELECT count(*) FROM information_schema.tables WHERE table_schema='public';"
74

$ docker logs ll 2>&1 | grep -cE "nodeenv|Installing Prisma CLI|libatomic|npm install"
0

Full matrix, all captured at 3b52b6c, each row a live container against its own fresh Postgres unless noted, each passing row asserting 74 tables created, POST /team/new returning 200, and zero nodeenv or npm activity in the logs:

Scenario Published v1.93.0-rc.1 This PR
root, fresh DB, online pass pass
root, existing DB with full migration history not run pass (No pending migrations)
uid 12345:0, fresh DB fail (PermissionError /.cache) pass
HOME=/home/litellm, fresh DB (the #33650 shape) fail (nodeenv, libatomic.so.1) pass
internal-only network, root, fresh DB pass pass (~22s to live)
internal-only network, uid 12345:0, fresh DB not run pass
read-only rootfs + tmpfs /tmp /.cache /.npm + uid 12345:0 (the helm chart readOnlyRootFilesystem shape) not run pass
Dockerfile.database image, root and uid 12345:0, fresh DB not run pass

grype reports no new findings versus the published image (npm stays out of the image) and the image size is unchanged (1.55GB vs 1.53GB)

Type

🐛 Bug Fix

Changes

The runtime image ships the prisma CLI and both engines under /root/.cache, the HOME-derived prisma-python default. Any deployment whose runtime HOME is not /root (kubernetes runAsUser, docker --user, HOME overrides) misses that cache on a fresh database; prisma-python then bootstraps a CLI through nodeenv, whose downloaded Node build crashes on Wolfi (libatomic.so.1 is not shipped), the migration fails, the proxy starts anyway, and every DB-backed endpoint returns 500 with The table public.LiteLLM_TeamTable does not exist. Runs as exact root with default HOME hit the baked cache and work, which is why CI and default docker run never caught it

The fix relocates the bake to /opt/prisma, a path that no HOME resolution depends on and that no cache volume mount shadows. The builder runs prisma generate with HOME, XDG_CACHE_HOME and PRISMA_BINARY_CACHE_DIR redirected there (inline on the RUN, so nothing leaks into runtime HOME semantics), which also makes the engine paths recorded inside the generated client point at /opt/prisma. The runtime stage copies /opt/prisma once, marks it world-readable, and pins four env vars: PRISMA_BINARY_CACHE_DIR so prisma-python resolves the baked CLI and query engine, PRISMA_CLI_PATH so litellm-proxy-extras execs the baked CLI directly, PRISMA_CLI_QUERY_ENGINE_TYPE=binary so the CLI uses the baked binary engines instead of fetching the library variant at startup (the python wrapper always set this; the direct exec path must too), and PRISMA_OFFLINE_MODE so proxy-extras prefers the direct path. prisma migrate deploy on a fresh database now needs no npm and no network access for any runtime uid. A build-time assertion in the runtime stage verifies the baked CLI shim is executable and its entrypoint exists after the COPY and chmod, so a layout change in a future prisma-python release fails the image build loudly instead of silently degrading the migration path at startup (raised by Greptile; if the pinned PRISMA_CLI_PATH ever goes stale at runtime anyway, proxy-extras falls back to the python wrapper, which re-resolves the baked CLI through PRISMA_BINARY_CACHE_DIR)

/opt/prisma was chosen over /app/.cache deliberately: the previous comment in Dockerfile.database warned that readOnlyRootFilesystem deployments volume-mount /app/.cache with an emptyDir, which would shadow a bake placed there. docker/Dockerfile.non_root is already immune through the same env-pinning pattern and is untouched, as is migrations/Dockerfile. npm intentionally stays out of the runtime image, preserving the reduced scanner surface from the earlier image slimming work; this supersedes the approach in #33657, which restores the older behavior of downloading the CLI from the npm registry at container startup and so cannot work where that registry is unreachable

On tests: a pytest unit test cannot exercise a Docker build, so verification for this PR is the live-container matrix above. The durable regression net is a CI job that boots the built image on an internal-only network as a non-root uid against a fresh Postgres and asserts the migration outcome; that lands as a follow-up PR, alongside a separate change to fail startup loudly when migrations fail on a schema-less database instead of continuing silently

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

…migrations work for any uid offline

The runtime image shipped the prisma CLI and engines under /root/.cache, the
default HOME-derived prisma-python cache location. Any deployment whose
runtime HOME is not /root (kubernetes runAsUser, docker --user, HOME
overrides) missed that cache on a fresh database, fell back to a nodeenv
Node download that crashes on Wolfi (libatomic.so.1), and started the proxy
with zero tables while every DB-backed endpoint returned 500

The bake now lives at /opt/prisma, a path no HOME resolution or cache
volume mount can shadow. The builder records the engine paths there at
generate time, and the runtime stage pins PRISMA_BINARY_CACHE_DIR,
PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary and
PRISMA_OFFLINE_MODE so both litellm-proxy-extras and prisma-python resolve
the baked CLI and engines directly. prisma migrate deploy on a fresh
database now needs no npm and no network access for any runtime uid,
including readOnlyRootFilesystem deployments

Verified against live containers: fresh and existing databases as root,
uid 12345, HOME overridden, on an internal-only docker network, and with
a read-only root filesystem all migrate and serve /team/new successfully

Fixes #33650, #24554
@greptile-apps

greptile-apps Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes prisma migrate deploy failures that silently left fresh databases empty when the container ran as a non-root UID or with a non-default HOME. The root cause was that the prisma CLI and engines were baked into /root/.cache, which is inaccessible when HOME resolves elsewhere, causing prisma-python to attempt a runtime npm install that fails on Wolfi due to a missing libatomic.so.1.

  • Builder stage now redirects HOME, XDG_CACHE_HOME, and PRISMA_BINARY_CACHE_DIR to /opt/prisma for the prisma generate step, baking the CLI and engines at a fixed, UID-independent location; npm_config_cache is kept at /root/.npm so the npm cache does not pollute /opt/prisma.
  • Runtime stage sets four env vars (PRISMA_BINARY_CACHE_DIR, PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary, PRISMA_OFFLINE_MODE=true) that point both the Python wrapper and the proxy-extras direct-exec path at the baked artifacts, and then runs chmod -R a+rX /opt/prisma followed by test -x / test -f assertions so a future prisma-python layout change fails the image build loudly rather than silently in production.

Confidence Score: 5/5

Safe to merge — the changes are confined to Docker build logic, address a well-documented runtime failure, and include build-time assertions that fail the image build if the layout ever drifts.

Both Dockerfiles are changed identically and symmetrically. The fix correctly moves the prisma bake to a UID-independent path, pins all four env vars needed for both the Python wrapper and the direct-exec path, and adds test -x / test -f guards that catch future prisma-python layout changes at build time rather than at container startup. The PR description includes a comprehensive live-container test matrix covering all relevant deployment shapes. No application logic is touched.

No files require special attention.

Important Files Changed

Filename Overview
Dockerfile Relocates prisma CLI bake from HOME-derived /root/.cache to fixed /opt/prisma path; adds four runtime env pins and build-time assertions for executable validity
docker/Dockerfile.database Mirrors the same prisma relocation and env-pin pattern from Dockerfile; changes are symmetric and correct

Reviews (2): Last reviewed commit: "chore(docker): fail the image build if t..." | Re-trigger Greptile

Comment thread Dockerfile
Comment thread docker/Dockerfile.database
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…ifts

Asserts the baked CLI shim is executable and its entrypoint exists in the
runtime stage after the COPY and chmod, so a layout change in a future
prisma-python release breaks the image build loudly instead of silently
degrading the migration path at container startup
@yuneng-berri

Copy link
Copy Markdown
Contributor Author

@greptile review again please

@yuneng-berri
yuneng-berri enabled auto-merge (squash) July 18, 2026 21:46
@yuneng-berri
yuneng-berri disabled auto-merge July 18, 2026 21:47
@yuneng-berri
yuneng-berri merged commit 567ebcb into litellm_internal_staging Jul 18, 2026
78 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_/litellm-issue-33650-02faad branch July 18, 2026 21:52
@devin-ai-integration

Copy link
Copy Markdown
Contributor

✅ QA: verified the fix works end-to-end

Tested with live Docker containers against a real Postgres 16, comparing the published image vs. this PR's image under the deployment shape that triggers the bug (HOME != /root). Full walkthrough video below.

QA walkthrough — before/after

BEFORE (ghcr.io/berriai/litellm:v1.93.0-rc.1, HOME=/home/litellm, fresh Postgres): migration fails — node: error while loading shared libraries: libatomic.so.1 while bootstrapping the Prisma CLI, 0 tables created, and POST /team/new → HTTP 500 The table public.LiteLLM_TeamTable does not exist.

AFTER (image built from this PR, run as non-root uid 12345:0 on an internal-only network with zero egress, fresh Postgres): uses the baked CLI at /opt/prisma, migrates fully offline with 0 npm/nodeenv/libatomic activity, 75 tables created, and POST /team/new → HTTP 200. (PR text says 74; the schema has since grown to 75.)

AFTER — fix works offline as non-root (HTTP 200, 75 tables)

AFTER pass

BEFORE — reproduces the bug (HTTP 500, 0 tables, libatomic error)

BEFORE fail

The fix (Dockerfile diff: /opt/prisma bake + pinned PRISMA_* ENV)

Dockerfile diff

QA performed by Devin — https://app.devin.ai/sessions/923e091eb7024080b2f7017b7092d4a5

@codspeed

codspeed Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_/litellm-issue-33650-02faad (0248ec1) with litellm_internal_staging (e238e89)

Open in CodSpeed

yuneng-berri added a commit that referenced this pull request Jul 18, 2026
…mplete the 1.93.0 stable cut (#33869)

* fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline (#33853)

* fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline

The runtime image shipped the prisma CLI and engines under /root/.cache, the
default HOME-derived prisma-python cache location. Any deployment whose
runtime HOME is not /root (kubernetes runAsUser, docker --user, HOME
overrides) missed that cache on a fresh database, fell back to a nodeenv
Node download that crashes on Wolfi (libatomic.so.1), and started the proxy
with zero tables while every DB-backed endpoint returned 500

The bake now lives at /opt/prisma, a path no HOME resolution or cache
volume mount can shadow. The builder records the engine paths there at
generate time, and the runtime stage pins PRISMA_BINARY_CACHE_DIR,
PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary and
PRISMA_OFFLINE_MODE so both litellm-proxy-extras and prisma-python resolve
the baked CLI and engines directly. prisma migrate deploy on a fresh
database now needs no npm and no network access for any runtime uid,
including readOnlyRootFilesystem deployments

Verified against live containers: fresh and existing databases as root,
uid 12345, HOME overridden, on an internal-only docker network, and with
a read-only root filesystem all migrate and serve /team/new successfully

Fixes #33650, #24554

* chore(docker): fail the image build if the baked prisma CLI layout drifts

Asserts the baked CLI shim is executable and its entrypoint exists in the
runtime stage after the COPY and chmod, so a layout change in a future
prisma-python release breaks the image build loudly instead of silently
degrading the migration path at container startup

(cherry picked from commit 567ebcb)

* fix(router): treat malformed configured token limits as absent on /v1/models (#33864)

A deployment whose model_info carried a non-numeric max_input_tokens or
max_output_tokens (for example "128,000" or an empty string) made the
bare int() in get_configured_token_limits raise inside the per-model
/v1/models loop, so one misconfigured deployment turned the entire
listing into a 500. Coerce each configured limit safely and treat
malformed values as absent, matching the graceful degradation the
listing had before the cost-map switch

(cherry picked from commit ef7007c)
yuneng-berri added a commit that referenced this pull request Jul 19, 2026
…migrations work for any uid offline (#33853)

* fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline

The runtime image shipped the prisma CLI and engines under /root/.cache, the
default HOME-derived prisma-python cache location. Any deployment whose
runtime HOME is not /root (kubernetes runAsUser, docker --user, HOME
overrides) missed that cache on a fresh database, fell back to a nodeenv
Node download that crashes on Wolfi (libatomic.so.1), and started the proxy
with zero tables while every DB-backed endpoint returned 500

The bake now lives at /opt/prisma, a path no HOME resolution or cache
volume mount can shadow. The builder records the engine paths there at
generate time, and the runtime stage pins PRISMA_BINARY_CACHE_DIR,
PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary and
PRISMA_OFFLINE_MODE so both litellm-proxy-extras and prisma-python resolve
the baked CLI and engines directly. prisma migrate deploy on a fresh
database now needs no npm and no network access for any runtime uid,
including readOnlyRootFilesystem deployments

Verified against live containers: fresh and existing databases as root,
uid 12345, HOME overridden, on an internal-only docker network, and with
a read-only root filesystem all migrate and serve /team/new successfully

Fixes #33650, #24554

* chore(docker): fail the image build if the baked prisma CLI layout drifts

Asserts the baked CLI shim is executable and its entrypoint exists in the
runtime stage after the COPY and chmod, so a layout change in a future
prisma-python release breaks the image build loudly instead of silently
degrading the migration path at container startup

(cherry picked from commit 567ebcb)
yuneng-berri added a commit that referenced this pull request Jul 19, 2026
…migrations work for any uid offline (#33853)

* fix(docker): bake prisma CLI and engines at a fixed path so fresh-DB migrations work for any uid offline

The runtime image shipped the prisma CLI and engines under /root/.cache, the
default HOME-derived prisma-python cache location. Any deployment whose
runtime HOME is not /root (kubernetes runAsUser, docker --user, HOME
overrides) missed that cache on a fresh database, fell back to a nodeenv
Node download that crashes on Wolfi (libatomic.so.1), and started the proxy
with zero tables while every DB-backed endpoint returned 500

The bake now lives at /opt/prisma, a path no HOME resolution or cache
volume mount can shadow. The builder records the engine paths there at
generate time, and the runtime stage pins PRISMA_BINARY_CACHE_DIR,
PRISMA_CLI_PATH, PRISMA_CLI_QUERY_ENGINE_TYPE=binary and
PRISMA_OFFLINE_MODE so both litellm-proxy-extras and prisma-python resolve
the baked CLI and engines directly. prisma migrate deploy on a fresh
database now needs no npm and no network access for any runtime uid,
including readOnlyRootFilesystem deployments

Verified against live containers: fresh and existing databases as root,
uid 12345, HOME overridden, on an internal-only docker network, and with
a read-only root filesystem all migrate and serve /team/new successfully

Fixes #33650, #24554

* chore(docker): fail the image build if the baked prisma CLI layout drifts

Asserts the baked CLI shim is executable and its entrypoint exists in the
runtime stage after the COPY and chmod, so a layout change in a future
prisma-python release breaks the image build loudly instead of silently
degrading the migration path at container startup

(cherry picked from commit 567ebcb)
@devin-ai-integration

Copy link
Copy Markdown
Contributor

QA: re-verified with fresh live containers (screenshots)

Rebuilt the PR image from the merge commit and re-ran the fix vs. the published image against a real Postgres 16, reproducing the exact deployment shape from #33650.

BEFORE — published ghcr.io/berriai/litellm:v1.93.0-rc.1, HOME=/home/litellm, fresh Postgres: Prisma CLI bootstrap crashes on libatomic.so.1, migration never runs → 0 tables, and POST /team/new → HTTP 500 The table public.LiteLLM_TeamTable does not exist.

BEFORE — reproduces the bug

AFTER — this PR's image, run as non-root uid 12345:0 on an internal-only (zero-egress) network so the registry/npm are unreachable, fresh Postgres: uses the baked /opt/prisma CLI, migrates fully offline (0 npm/nodeenv/libatomic activity), 75 tables created, POST /team/new → HTTP 200.

AFTER — fix works offline as non-root

(schema now has 75 tables vs. the 74 in the PR description — it has since grown by one; not a defect.)

QA performed by Devin — https://app.devin.ai/sessions/67ca006a9a32420e988c3e6f03b64770

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants