[Infra] Pin All Docker Build Dependencies#24905
Conversation
Pin every dependency across all Docker builds so upgrades are intentional. Verified by building all 3 production images and diffing pip freeze against known-good v1.83.0-nightly baselines — zero version drift. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR hardens the LiteLLM supply chain by pinning every Docker build dependency — base images (via Key changes:
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| docker/build_from_pip/Dockerfile.build_from_pip | Breaking OS switch from Alpine (musl) to Debian slim — not just a digest pin as stated in PR description; removes Rust/Cargo/OpenSSL build deps; backwards-incompatible for Alpine users |
| docker/Dockerfile.non_root | Base images pinned; .npmrc rename dance added to bypass min-release-age for npm 11 install (comment mismatch addressed in prior review thread) |
| deploy/Dockerfile.ghcr_base | main-latest base pinned to SHA digest, but that digest will go stale on every CI push to main since it's a moving tag |
| requirements.txt | All 84+ deps exact-pinned; aioboto3 removed; transitive deps (aiofiles, colorlog, grpc-google-iam-v1, hf-xet, requests-toolbelt) explicitly pinned; hf-xet musl/Alpine compat concern flagged in prior thread |
| pyproject.toml | All prod/dev deps exact-pinned; cryptography pinned at 43.0.3 vs requirements.txt 46.0.5 (security concern flagged in prior thread); grpcio collapsed to single entry |
| .circleci/config.yml | aioboto3 removed from all CI job installs; dependency versions bumped to match production pins; aiobotocore --no-deps concern flagged in prior thread |
| scripts/health_check/health_check_requirements.txt | httpx and pyyaml pinned; pyyaml pinned to 6.0.2 while main requirements.txt uses 6.0.3 — minor version drift |
| docker/build_from_pip/requirements.txt | Updated from stale dev-version pins; openai pinned to 2.24.0 while requirements.txt uses 2.30.0 — minor drift introduced |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Docker Build Triggered] --> B{Which Dockerfile?}
B -->|Main / Database / Non-Root| C[wolfi-base SHA pinned]
B -->|Dev / Health Check| D[python 3.13-slim SHA pinned]
B -->|Alpine| E[python 3.11-alpine SHA pinned]
B -->|build-from-pip BREAKING| F[python 3.13-slim was Alpine]
B -->|ghcr-base deploy| G[main-latest SHA pinned but stale]
C --> H[pip install requirements.txt exact pins]
D --> H
E --> H
F --> I[pip install litellm proxy from PyPI build-from-pip reqs]
H --> J[pip install wheel --no-deps skips re-resolution]
J --> K[npm install -g npm 11.12.1 pinned]
K --> L[Production Image]
I --> L
Reviews (11): Last reviewed commit: "add poetry lock" | Re-trigger Greptile
| # NOTE: --userconfig /dev/null bypasses .npmrc (which has ignore-scripts=true and | ||
| # min-release-age=3d) for this step only. This is safe because npm ci installs from | ||
| # package-lock.json with pinned versions + integrity hashes. |
There was a problem hiding this comment.
Misleading comment vs actual implementation
The block comment says --userconfig /dev/null bypasses .npmrc but the actual code does not use --userconfig /dev/null — it uses mv .npmrc .npmrc.bak / mv .npmrc.bak .npmrc to temporarily rename the file. The comment describes a hypothetical approach that was not implemented, which will confuse future maintainers.
Consider either:
- Update the comment to accurately describe the file-renaming approach used, or
- Replace the
mvdance with the simpler--userconfig /dev/nullflag directly onnpm install -gandnpm ci:
npm install -g [email protected] --userconfig /dev/null && npm cache clean --force && \
...
npm ci --userconfig /dev/null && \This is cleaner, avoids renaming state that could be lost if the build fails partway through, and matches what the comment claims.
| # Use the provided base image | ||
| # NOTE: This is a dev/branch-specific tag. Update digest when the base image is rebuilt. | ||
| FROM ghcr.io/berriai/litellm:litellm_fwd_server_root_path-dev |
There was a problem hiding this comment.
Base image remains unpinned by digest
Every other Dockerfile in this PR now pins its base image to a @sha256:... digest for supply-chain safety, but Dockerfile.custom_ui still uses an unqualified mutable tag (litellm_fwd_server_root_path-dev). The added comment acknowledges this but offers no plan. A floating dev tag is exactly the kind of unpinned surface the PR is trying to eliminate.
If the tag intentionally tracks a moving branch build, consider at minimum documenting how to refresh the digest (e.g. docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/berriai/litellm:litellm_fwd_server_root_path-dev) and adding it to a CI step that pins it after each new push to that branch.
| HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ | ||
| CMD python /app/health_check_client.py --help || exit 1 |
There was a problem hiding this comment.
HEALTHCHECK tests argument parsing, not actual connectivity
CMD python /app/health_check_client.py --help || exit 1 only verifies that Python can load the script and parse --help. It will pass even if the health-check client cannot reach the LiteLLM server, cannot import a required network library, or is fundamentally misconfigured.
A more meaningful check would execute the script against a known endpoint (e.g., with a --url flag and a short --timeout), or at minimum verify all imports succeed without hitting the network:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python -c "import health_check_client" || exit 1Even that only tests imports, but it's more meaningful than --help. Ideally the check should do a lightweight connectivity probe against the configured target.
| # grpcio: pinned to 1.80.0 (past reconnect bug #38290 in 1.68.x, has Python 3.14 wheels) | ||
| grpcio==1.80.0; python_version < "3.14" | ||
| grpcio==1.80.0; python_version >= "3.14" |
There was a problem hiding this comment.
Redundant Python-version markers on identical
grpcio pins
Both conditions now pin grpcio to exactly 1.80.0, so the python_version markers serve no purpose. This is functionally equivalent to a single unconditional line and creates a maintenance trap where someone could change only one of the two entries in the future, reintroducing version divergence.
| # grpcio: pinned to 1.80.0 (past reconnect bug #38290 in 1.68.x, has Python 3.14 wheels) | |
| grpcio==1.80.0; python_version < "3.14" | |
| grpcio==1.80.0; python_version >= "3.14" | |
| grpcio==1.80.0 # pinned to 1.80.0 (past reconnect bug #38290 in 1.68.x, has Python 3.14 wheels) |
The same redundancy exists in pyproject.toml:
grpcio = [
{version = "1.80.0", python = "<3.14", optional = true},
{version = "1.80.0", python = ">=3.14", optional = true},
]which can be collapsed to a single entry.
| aiofiles==24.1.0 # transitive dep (langfuse) | ||
| colorlog==6.10.1 # transitive dep (ddtrace) | ||
| grpc-google-iam-v1==0.14.3 # transitive dep (google-cloud-iam) | ||
| hf-xet==1.4.2 # transitive dep (huggingface_hub) |
There was a problem hiding this comment.
hf-xet may not be installable on all platforms
hf-xet is a Rust-compiled extension that ships manylinux (glibc) and macOS wheels but typically does not provide musl/Alpine wheels. Pinning it in requirements.txt is fine for the glibc-based Wolfi images, but if this file is ever used on Alpine (musl) or Windows hosts — including developer machines — pip install -r requirements.txt will fail with "No matching distribution found."
Consider adding a platform marker to restrict the pin to supported platforms:
| hf-xet==1.4.2 # transitive dep (huggingface_hub) | |
| hf-xet==1.4.2; sys_platform != "win32" and platform_machine in ("x86_64", "aarch64") # transitive dep (huggingface_hub) |
Or, since hf-xet is an optional performance plugin for huggingface_hub, verify that dropping it from requirements.txt and letting it be installed implicitly only where available is acceptable.
Pin pyproject.toml deps from PyPI resolution of `pip install litellm[proxy]==1.83.0` instead of Docker freeze versions. Docker builds (requirements.txt) and PyPI installs (pyproject.toml) are independent dependency paths. Some packages pinned to 3.9-compatible versions where latest requires >=3.10. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
pytest-asyncio 1.x no longer provides an implicit event loop in sync fixtures/tests. Make async-dependent fixtures and tests async, and replace deprecated asyncio.get_event_loop() in tests. Switch Dockerfile.build_from_pip from Alpine to Debian slim since pyroscope-io 0.8.x has no musl wheels. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
hf-xet is Apache 2.0 licensed but PyPI metadata doesn't expose the license string, so the automated checker can't determine it. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
boto3==1.42.80 and aioboto3==15.5.0 have incompatible botocore ranges. Both uv and pip 26.0.1 reject the resolution. Fix: filter aioboto3 out of requirements.txt at install time, then install aioboto3+aiobotocore separately with --no-deps to bypass resolution. Removes uv-overrides.txt which only partially addressed the conflict. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ilds boto3==1.42.80 and aioboto3==15.5.0 have incompatible botocore version ranges. No aioboto3 release supports botocore 1.42.x yet. Both uv and pip 26.0.1 reject the resolution. Fix: filter aioboto3 out of requirements.txt at install time, then install aioboto3+aiobotocore with --no-deps to bypass resolution. Added wrapt and aioitertools to requirements.txt as pinned transitive deps of aiobotocore (skipped by --no-deps). Fixed pip stdin handling (/dev/stdin). Applied to all 5 Dockerfiles and all CircleCI install paths. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
| # Use uv for the heavy requirements.txt (10-100x faster than pip) | ||
| uv pip install --system -r requirements.txt | ||
| grep -v '^aioboto3' requirements.txt | uv pip install --system -r - |
There was a problem hiding this comment.
--no-deps for aiobotocore in CI installs the package without its pinned botocore range
The CI install command mirrors the Dockerfile workaround, but it uses a plain pip install --no-deps rather than the wheel-based approach. In CI, when aiobotocore==2.25.1 is installed with --no-deps, it is placed alongside botocore==1.42.80 (transitively required by boto3==1.42.80). aiobotocore==2.25.1 has a declared botocore requirement of approximately botocore==1.36.x; using 1.42.x skips a significant number of botocore internal API changes.
Any test that exercises aiobotocore (async S3, async SageMaker) against this environment runs against a botocore version that aiobotocore was never tested with. Failures would appear as AttributeError or incorrect service calls rather than dependency errors, making them hard to diagnose.
This appears at two separate CI job definitions:
- Command step (line ~36)
mypyjob (line ~97)
Consider also pinning a botocore version in the pip install line that is known to be compatible with aiobotocore==2.25.1, or explicitly installing a boto3 version whose bundled botocore is within aiobotocore's tested range.
Both are transitive deps of aiobotocore, added to requirements.txt in the previous commit. aioitertools is MIT, wrapt is BSD. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…unds aioboto3 was listed as a dependency for async sagemaker calls but is not imported anywhere in the codebase — async calls use httpx + botocore SigV4 instead. Removing it eliminates the unresolvable botocore version conflict between boto3 and aiobotocore, along with all grep -v / --no-deps workarounds across Dockerfiles and CI. Also addresses Greptile review feedback: collapse redundant grpcio python-version markers, bump pyproject.toml cryptography to 46.0.5 to match Docker (GHSA-r6ph-v2qm-q3c2), and fix misleading .npmrc comment. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…0/3.9.1 compat cryptography 46.0.5 excludes Python 3.9.0 and 3.9.1, which conflicts with pyproject.toml's python = ">=3.9,<4.0" range. Docker still uses 46.0.5 via requirements.txt. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
| fastapi-sso = { version = "0.16.0", optional = true } | ||
| PyJWT = { version = "2.12.1", optional = true, python = ">=3.9" } | ||
| python-multipart = { version = "0.0.20", optional = true} | ||
| cryptography = {version = "43.0.3", optional = true} # Docker uses 46.0.5; pyproject uses 43.0.3 for Poetry Python 3.9.0/3.9.1 compat |
There was a problem hiding this comment.
@yuneng-berri why not have this be the same version 46.0.5?
Poetry validates the full >=3.9 range and cryptography 46.0.5 explicitly excludes 3.9.0/3.9.1 |
| a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"} | ||
| litellm-proxy-extras = {version = "^0.4.62", optional = true} | ||
| rich = {version = "^13.7.1", optional = true} | ||
| fastuuid = "0.14.0" |
There was a problem hiding this comment.
@yuneng-berri pyproject.toml is not an appropriate place for a library to pin exact dependency versions, because these are enforced on all downstream consumers of the PyPI package. Downstream consumers often use these same transitive dependencies in other ways with different version requirements, and need the ability to upgrade them independently. Such conflicts either prevent those consumers from upgrading litellm, or prevent those consumers from upgrading any of these dependencies, perhaps causing them to miss future security patches.
- [Bug]: python-dotenv pinned to ==1.0.1 since v1.83.1 causes dependency conflicts for library consumers #25210
- [Bug]: Dependency pinning in commit #5f63873 — intentional change? #25280
The appropriate place for these pins is poetry.lock, or now uv.lock.
Summary
Problem
After the LiteLLM supply chain attacks, Docker builds pulled unpinned dependencies — base images, pip packages, npm packages, and transitive deps could silently change between builds. Additionally,
aioboto3was listed inrequirements.txtfor async SageMaker calls but was never actually imported — the codebase uses httpx + botocore SigV4 instead. Its presence caused an unresolvable botocore version conflict withboto3, requiringgrep -v/--no-depsworkarounds across all Dockerfiles and ~20 CI job definitions.Fix
Pin every dependency across all Docker builds to exact versions verified against known-good v1.83.0-nightly images. Remove the unused
aioboto3dependency and all associated botocore conflict workarounds. Bumpcryptographyinpyproject.tomlto 46.0.5 to match Docker and address GHSA-r6ph-v2qm-q3c2.Changes
aioboto3 removal
aioboto3==15.5.0and its transitive deps (aioitertools,wrapt) fromrequirements.txtgrep -v '^aioboto3'+pip install --no-depsworkarounds from all Dockerfiles (main, database, alpine, dev, non_root) and all CI jobs in.circleci/config.ymlaioboto3,aioitertools,wraptfromliccheck.iniauthorized licensesPython dependencies
requirements.txt: All 84+ entries now use==exact pins, including transitive deps (aiofiles,colorlog,grpc-google-iam-v1,hf-xet,requests-toolbelt)pyproject.toml: All production and dev dependencies pinned to exact versions;cryptographybumped from 43.0.3 to 46.0.5 (GHSA-r6ph-v2qm-q3c2)grpciopython-version markers into single entry in bothrequirements.txtandpyproject.tomlscripts/health_check/health_check_requirements.txt: Pinned to match maindocker/build_from_pip/requirements.txt: Pinned, updated from stale dev versionDocker base images — pinned to
@sha256digests:cgr.dev/chainguard/wolfi-base(main, database, non_root)python:3.13-slim(dev, health_check — upgraded from 3.11-slim to resolve critical vuln)python:3.11-alpine(alpine)python:3.13-alpine(build_from_pip)ghcr.io/berriai/litellm:main-latest(deploy)Build tools
npm@latest→[email protected]in all Dockerfilespip install build→build==1.4.2in all Dockerfilespip install --upgrade pip→pip==26.0.1in all Dockerfilesaurelio-sdkinstall uses--no-depsto prevent transitive dep floatingDockerfile fixes
Dockerfile.non_root: Bypass.npmrcduring UI build (min-release-ageincompatible with npm 11); fixed misleading comment about--userconfig /dev/nullDockerfile.custom_ui: Added--no-install-recommendstoapt-get installDockerfile.health_check: Added non-root user, HEALTHCHECK instruction, upgraded to python:3.13-slimTesting
grep -vworkaroundaioboto3oraiobotocorein the Python sourcepip freezefrom each image and diffed against v1.83.0-nightly baselineType
🚄 Infrastructure