Skip to content

[Infra] Pin All Docker Build Dependencies#24905

Merged
yuneng-berri merged 16 commits into
mainfrom
litellm_pin_pip_2
Apr 1, 2026
Merged

[Infra] Pin All Docker Build Dependencies#24905
yuneng-berri merged 16 commits into
mainfrom
litellm_pin_pip_2

Conversation

@yuneng-berri

@yuneng-berri yuneng-berri commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator

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, aioboto3 was listed in requirements.txt for async SageMaker calls but was never actually imported — the codebase uses httpx + botocore SigV4 instead. Its presence caused an unresolvable botocore version conflict with boto3, requiring grep -v / --no-deps workarounds 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 aioboto3 dependency and all associated botocore conflict workarounds. Bump cryptography in pyproject.toml to 46.0.5 to match Docker and address GHSA-r6ph-v2qm-q3c2.

Changes

aioboto3 removal

  • Removed aioboto3==15.5.0 and its transitive deps (aioitertools, wrapt) from requirements.txt
  • Removed grep -v '^aioboto3' + pip install --no-deps workarounds from all Dockerfiles (main, database, alpine, dev, non_root) and all CI jobs in .circleci/config.yml
  • Removed aioboto3, aioitertools, wrapt from liccheck.ini authorized licenses
  • Simplified boto3 comment (no longer needs "compatible with aioboto3" note)

Python 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; cryptography bumped from 43.0.3 to 46.0.5 (GHSA-r6ph-v2qm-q3c2)
  • Collapsed redundant grpcio python-version markers into single entry in both requirements.txt and pyproject.toml
  • scripts/health_check/health_check_requirements.txt: Pinned to match main
  • docker/build_from_pip/requirements.txt: Pinned, updated from stale dev version

Docker base images — pinned to @sha256 digests:

  • 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 Dockerfiles
  • pip install buildbuild==1.4.2 in all Dockerfiles
  • pip install --upgrade pippip==26.0.1 in all Dockerfiles
  • aurelio-sdk install uses --no-deps to prevent transitive dep floating

Dockerfile fixes

  • Dockerfile.non_root: Bypass .npmrc during UI build (min-release-age incompatible with npm 11); fixed misleading comment about --userconfig /dev/null
  • Dockerfile.custom_ui: Added --no-install-recommends to apt-get install
  • Dockerfile.health_check: Added non-root user, HEALTHCHECK instruction, upgraded to python:3.13-slim

Testing

  • Built all 3 production Docker images locally (litellm, database, non_root) — all pass cleanly without the grep -v workaround
  • Confirmed zero imports of aioboto3 or aiobotocore in the Python source
  • Extracted pip freeze from each image and diffed against v1.83.0-nightly baseline
  • Result: zero version drift (only litellm wheel hash differs due to local build)

Type

🚄 Infrastructure

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]>
@vercel

vercel Bot commented Apr 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 1, 2026 9:30pm

Request Review

@codspeed-hq

codspeed-hq Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_pin_pip_2 (d038093) with main (e4442a4)

Open in CodSpeed

@greptile-apps

greptile-apps Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the LiteLLM supply chain by pinning every Docker build dependency — base images (via @sha256 digests), Python packages (exact == versions in requirements.txt and pyproject.toml), npm (11.12.1), and build tools — and removes the unused aioboto3 dependency along with all the grep -v / --no-deps workarounds it required. This fits cleanly into the repo's infra layer and does not touch any application logic.

Key changes:

  • All Dockerfiles: Base images pinned to SHA256 digests; npm@latest[email protected]; build==1.4.2; pip==26.0.1; --no-deps added to litellm wheel install (safe because requirements.txt is installed first)
  • requirements.txt: 84+ exact pins; aioboto3 and transitive deps removed; 5 floating transitive deps explicitly pinned (aiofiles, colorlog, grpc-google-iam-v1, hf-xet, requests-toolbelt)
  • pyproject.toml: All prod/dev deps exact-pinned to match the v1.83.0-nightly baseline; cryptography bumped to 43.0.3 (note: requirements.txt uses 46.0.5 — security posture split between install paths, flagged in prior review)
  • docker/build_from_pip/Dockerfile.build_from_pip: Silently changed from python:3.13-alpine to python:3.13-slim (Debian) — an undocumented breaking OS switch, not a simple digest pin as the PR description implies
  • CI (.circleci/config.yml): All aioboto3 installs removed across ~10 job definitions; dependency versions bumped to match production
  • Test fixes: asyncio.get_event_loop().run_until_complete() modernised to asyncio.run(); routing-strategy fixtures converted to async for correct event-loop scoping of asyncio.Future objects

Confidence Score: 4/5

  • Safe to merge after confirming the Alpine → Debian change in build_from_pip is intentional and communicated to users who rely on that Dockerfile
  • The core pinning work is thorough and well-executed. One P1 finding remains: Dockerfile.build_from_pip was quietly switched from Alpine (musl) to Debian (glibc) — a breaking OS change that is misrepresented in the PR description as a simple digest pin. Several prior-thread issues (cryptography split, hf-xet platform compat, grpcio redundant markers, HEALTHCHECK weakness, --no-deps on aurelio-sdk) are acknowledged but unresolved, keeping score at 4. All application logic is untouched; risk is confined to infra/build.
  • docker/build_from_pip/Dockerfile.build_from_pip (undocumented Alpine→Debian OS change), scripts/health_check/health_check_requirements.txt (pyyaml version drift), docker/build_from_pip/requirements.txt (openai version drift)

Important Files Changed

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
Loading

Reviews (11): Last reviewed commit: "add poetry lock" | Re-trigger Greptile

Comment thread docker/Dockerfile.non_root Outdated
Comment on lines +44 to +46
# 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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:

  1. Update the comment to accurately describe the file-renaming approach used, or
  2. Replace the mv dance with the simpler --userconfig /dev/null flag directly on npm install -g and npm 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.

Comment on lines 1 to 3
# 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Comment thread docker/install_auto_router.sh
Comment on lines +20 to +21
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python /app/health_check_client.py --help || exit 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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 1

Even that only tests imports, but it's more meaningful than --help. Ideally the check should do a lightweight connectivity probe against the configured target.

Comment thread requirements.txt Outdated
Comment on lines +54 to +56
# 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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
# 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.

Comment thread requirements.txt
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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:

Suggested change
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]>
@yuneng-berri yuneng-berri temporarily deployed to integration-redis-postgres April 1, 2026 07:28 — with GitHub Actions Inactive
@yuneng-berri yuneng-berri temporarily deployed to integration-postgres April 1, 2026 07:28 — with GitHub Actions Inactive
@CLAassistant

CLAassistant commented Apr 1, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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]>
@yuneng-berri yuneng-berri temporarily deployed to integration-postgres April 1, 2026 16:51 — with GitHub Actions Inactive
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]>
Comment thread .circleci/config.yml Outdated
Comment on lines +36 to +37
# 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 -

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 --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)
  • mypy job (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]>
@yuneng-berri yuneng-berri temporarily deployed to integration-redis-postgres April 1, 2026 20:01 — with GitHub Actions Inactive
@yuneng-berri yuneng-berri temporarily deployed to integration-postgres April 1, 2026 20:01 — with GitHub Actions Inactive
yuneng-berri and others added 3 commits April 1, 2026 14:25
…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]>

@ishaan-berri ishaan-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Comment thread pyproject.toml
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuneng-berri why not have this be the same version 46.0.5?

@yuneng-berri

Copy link
Copy Markdown
Collaborator Author

nit

Poetry validates the full >=3.9 range and cryptography 46.0.5 explicitly excludes 3.9.0/3.9.1

@ishaan-berri ishaan-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Comment thread pyproject.toml
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

The appropriate place for these pins is poetry.lock, or now uv.lock.

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.

5 participants