Docker + uv: The Reality Check

I've been running uv in production Docker containers since the early versions. It's fast as hell, but Docker deployment isn't just "replace pip with uv" and call it done. You'll hit edge cases that'll make you question your life choices.

The Docker build process with uv follows a typical multi-stage pattern: dependencies first, then application code, then runtime optimization.

The big win? Our production builds went from 12+ minutes to around 3 minutes. Docker layer caching actually works now because uv's dependency resolution is deterministic. But you need to structure your Dockerfile correctly or you'll lose all those gains.

The Critical Docker Environment Variables

uv behaves differently in containers. These settings will save your ass:

ENV UV_LINK_MODE=copy \
    UV_COMPILE_BYTECODE=1 \
    UV_PYTHON_DOWNLOADS=never \
    UV_PYTHON=python3.12 \
    UV_PROJECT_ENVIRONMENT=/app \
    UV_CACHE_DIR=/tmp/uv-cache

UV_LINK_MODE=copy is crucial - Docker filesystems don't support hard links properly, and uv will fail silently without this. Spent a weekend debugging that shit before figuring it out.

UV_COMPILE_BYTECODE=1 gives you faster startup times. Noticeably improved our cold start times.

UV_PYTHON_DOWNLOADS=never prevents uv from trying to download Python builds inside containers. Use the system Python you installed.

Multi-Stage Builds That Actually Work

Docker multi-stage builds follow a pattern: build dependencies in one stage, compile/install in another, then copy only runtime artifacts to the final image. This reduces image size by 70-90% and improves security by excluding build tools from production containers.

Here's the production Dockerfile pattern I use. Note the dependency separation - this is where most people fuck up:

## Stage 1: Build dependencies only
FROM python:3.12-slim AS deps
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

ENV UV_LINK_MODE=copy UV_COMPILE_BYTECODE=1 UV_PYTHON_DOWNLOADS=never

WORKDIR /app
RUN --mount=type=cache,target=/root/.cache \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --locked --no-dev --no-install-project

## Stage 2: Add application code
COPY . /src
WORKDIR /src
RUN --mount=type=cache,target=/root/.cache \
    uv sync --locked --no-dev --no-editable

## Stage 3: Production runtime
FROM python:3.12-slim
COPY --from=deps /app /app
ENV PATH=/app/bin:$PATH
USER 1001
WORKDIR /app

The key insight: install dependencies first, then add your application code. This way, dependency layers only rebuild when uv.lock changes, not every time you modify your source.

Production Performance Gotchas

Cache mount location matters. I've seen builds fail because /root/.cache doesn't exist in the container. Use an explicit mount target.

File ownership gets fucked. If your build stage runs as root but runtime uses a different user, you'll get permission denied errors. Took down staging for 2 hours once because I forgot the chown flag. Use COPY --chown=app:app to fix ownership during copy.

Virtual environments in containers are worth it. I know it sounds redundant, but virtualenvs provide isolation and make debugging easier. Plus, uv creates them so fast it doesn't matter.

Real Error You'll Hit: "COPY failed: no such file or directory"

This happens when uv.lock or pyproject.toml isn't in your Docker build context. The fix is either:

  1. Move your Dockerfile to the project root
  2. Use .dockerignore correctly to include necessary files
  3. Or copy the files explicitly in a separate layer first

Don't try to mount them from outside the build context - Docker won't let you.

Memory Limits Will Bite You

uv's parallel dependency resolution can spike memory usage. In containerized environments with memory limits, this causes OOM kills during uv sync.

The fix: UV_CONCURRENT_DOWNLOADS=1 to serialize downloads, or increase your build container memory limits. We run builds with 2GB minimum now.

Private Package Repositories

If you're using private PyPI indexes, Docker secrets mounting works better than environment variables:

RUN --mount=type=secret,id=pip_index_url \
    UV_EXTRA_INDEX_URL=$(cat /run/secrets/pip_index_url) \
    uv sync --locked

Don't put authentication tokens directly in the Dockerfile - they end up in the image layers forever.

Essential Docker + uv Resources

Docker Deployment Problems You'll Actually Hit

Q

Why does "uv sync" fail with permission denied in Docker?

A

This happens when uv tries to write to directories it doesn't own. Two common scenarios:

  1. Build stage runs as root, runtime as non-root user: Fix with COPY --chown=user:user /app /app
  2. System Python packages directory is read-only: Use UV_PROJECT_ENVIRONMENT=/app to install to a writable location (learned this after hitting "PermissionError: [Errno 13] Permission denied" about 50 times)

The quick fix: chmod -R 755 /app in your Dockerfile, but proper file ownership is better.

Q

Docker build fails with "uv.lock not found" but the file exists locally

A

Docker build context problem. Your Dockerfile can only access files within the build context (usually the directory containing Dockerfile).

Check your .dockerignore file - it might be excluding uv.lock. Or move your Dockerfile to the project root where uv.lock lives.

Q

"Failed to build package X" in Docker but works locally

A

Usually a missing system dependency. uv doesn't install system libraries, only Python packages.

Common missing packages:

  • build-essential for packages with C extensions
  • python3.X-dev for header files
  • Library-specific stuff like libffi-dev, libssl-dev

Add them to your Dockerfile: RUN apt-get install build-essential python3.12-dev

Q

Why is my Docker image 2GB when uv is supposed to be lightweight?

A

You're probably including build tools in your final image. Use multi-stage builds:

  • Build stage: Install uv, build-essential, dev headers
  • Runtime stage: Only copy the built virtual environment and runtime libraries

Also check if you're copying unnecessary files. Use .dockerignore to exclude .git, __pycache__, etc.

Q

uv sync is slow in Docker compared to locally

A

Few things to check:

  1. No cache mounts: Add --mount=type=cache,target=/root/.cache to your RUN commands (this single change cut our build times from 8 minutes to 2)
  2. Network issues: DNS resolution is slower in containers sometimes
  3. Memory pressure: Container memory limits causing swapping

The cache mount makes the biggest difference - first build is slow, subsequent builds are fast.

Q

Container crashes with OOMKilled during dependency installation

A

uv's parallel downloads can spike memory usage. Limit concurrent operations:

ENV UV_CONCURRENT_DOWNLOADS=1
ENV UV_RESOLUTION_MEMORY_LIMIT=1GB

Or increase your container memory limits for the build stage.

Q

"Package not found" errors for packages that definitely exist

A

Networking or index configuration issue. Check if you're using private PyPI repositories and authentication is set up correctly in the container.

For corporate environments, you might need to configure SSL certificates or proxy settings:

ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
ENV UV_EXTRA_INDEX_URL=https://your-private-pypi.com/simple/
Q

Virtual environment path is wrong in production

A

Don't hardcode paths. Use UV_PROJECT_ENVIRONMENT=/app to control where uv creates the venv, then add /app/bin to PATH:

ENV UV_PROJECT_ENVIRONMENT=/app
ENV PATH=/app/bin:$PATH

This makes Python and installed CLI tools available without full paths.

Q

Build cache isn't working - rebuilds everything every time

A

Your layer ordering is probably wrong. Dependencies should be installed before application code:

  1. First: Copy only uv.lock and pyproject.toml
  2. Then: Run uv sync (this layer caches until lock file changes)
  3. Finally: Copy application source code

If you copy source first, every code change invalidates the dependency cache.

Q

Alpine Linux builds fail with weird errors

A

Don't use Alpine for Python applications. The musl libc causes compatibility issues with many Python packages. Use debian-slim or ubuntu instead.

If you must use Alpine, you'll need to install extra packages and sometimes compile from source. Spent 3 days debugging numpy imports on Alpine once. Never again. It's not worth the 50MB size savings.

Q

Production container is missing Python executable

A

When using virtual environments in containers, make sure you activate the venv or set PATH correctly.

Either:

ENV PATH=/app/bin:$PATH

Or use the full path:

CMD ["/app/bin/python", "-m", "your_app"]
Q

"uv command not found" in runtime container

A

You copied uv into the build stage but not the runtime stage. Either:

  1. Don't copy uv to runtime (preferred) - just copy the built venv
  2. Or copy uv binary: COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

Option 1 is better - runtime containers don't need package managers.

Production-Ready Dockerfile Template

Here's the Dockerfile I actually use in production.

It handles the edge cases I've hit running uv-based containers:

## syntax=docker/dockerfile:
1.9
FROM python:
3.12-slim AS build

SHELL [\"bash\", \"-euxo\", \"pipefail\", \"-c\"]
ENV DEBIAN_FRONTEND=noninteractive

## Install build dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    ca-certificates \
    curl \
    && rm -rf /var/lib/apt/lists/*

## Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv

## Configure uv for containers
ENV UV_LINK_MODE=copy \
    UV_COMPILE_BYTECODE=1 \
    UV_PYTHON_DOWNLOADS=never \
    UV_PYTHON=python3.12 \
    UV_PROJECT_ENVIRONMENT=/app

## Create non-root user for runtime
RUN groupadd -r appuser && useradd -r -g appuser appuser

## Install dependencies first (better caching)
WORKDIR /src
COPY uv.lock pyproject.toml ./

RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=cache,target=/root/.cache/pip \
    uv sync --locked --no-dev --no-install-project

## Install application code
COPY . .

RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --locked --no-dev --no-editable

## Runtime stage
FROM python:
3.12-slim AS runtime

## Runtime dependencies (no build tools)
RUN apt-get update && apt-get install -y \
    ca-certificates \
    && rm -rf /var/lib/apt/lists/* \
    && groupadd -r appuser && useradd -r -g appuser appuser

## Copy virtual environment
COPY --from=build --chown=appuser:appuser /app /app

## Set up environment
ENV PATH=\"/app/bin:$PATH\" \
    PYTHONPATH=\"/app/lib/python3.12/site-packages\" \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1

USER appuser
WORKDIR /app

## Verify installation and run smoke test
RUN python --version && \
    python -m pip list && \
    python -c \"import sys; print(f'Python executable: {sys.executable}')\"

## Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c \"import your_app; print('OK')\" || exit 1

EXPOSE 8000
CMD [\"python\", \"-m\", \"your_app\"]

Key Production Patterns

Environment variable layering (this tripped me up initially): I set UV_* variables in the build stage only.

Runtime doesn't need them, which keeps the runtime environment clean.

Cache mounting strategy: Both uv and pip caches are mounted.

This handles cases where uv falls back to pip for problematic packages.

User management: Create the user in both stages.

Some base images handle this differently, causing ownership issues when copying between stages.

Verification step: The smoke test (basic import/startup verification) catches 90% of container issues before deployment.

It's saved my ass more times than I can count.

CI/CD Integration That Works

*CI/CD workflow: Code push triggers automated build, test, and deployment stages.

Container images are built, tested for functionality, scanned for vulnerabilities, then pushed to registry if all checks pass. This ensures reliable, secure deployments.*

Here's the GitHub Actions workflow I use for building and pushing containers:

name:

 Build Container
on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:

- uses: actions/checkout@v4
    
    
- name:

 Set up Docker Buildx
      uses: docker/setup-buildx-action@v3
      
    
- name:

 Build and test
      uses: docker/build-push-action@v5
      with:
        context: .
        target: runtime
        cache-from: type=gha
        cache-to: type=gha,mode=max
        load: true
        tags: myapp:test
        
    
- name:

 Test container
      run: |
        docker run --rm myapp:test python -c \"import your_app; print('Import test passed')\"
        docker run --rm -d --name test-container myapp:test
        sleep 5
        docker logs test-container
        docker stop test-container
        
    
- name:

 Push to registry
      if: github.ref == 'refs/heads/main'
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: your-registry/myapp:latest

Cache strategy: GitHub Actions cache significantly speeds up builds.

First build usually takes a few minutes, subsequent builds are much faster.

Container testing (learned this the hard way): Always test that the container actually starts and imports work.

I've caught countless issues this way.

Monitoring and Debugging

Container size monitoring: Our production containers are around 200MB.

If yours are way bigger, you're probably including build tools in runtime.

Startup time tracking: We monitor cold start times.

With uv + bytecode compilation, startup got noticeably faster.

Memory usage patterns: uv containers use less memory than pip equivalents because of better dependency resolution.

But monitor for memory leaks in long-running containers.

Security Considerations

Minimal runtime surface: Runtime containers only include Python runtime and application dependencies.

No compilers, package managers, or dev tools.

Non-root execution: All containers run as non-root users.

File permissions are set correctly during the build stage.

Dependency scanning: We scan uv.lock files for vulnerabilities using pip-audit before building containers:

uv tool run pip-audit --requirement uv.lock --format json --output audit.json

Common Production Failures

Disk space issues: uv's cache can grow large.

In production environments, monitor disk usage and periodically clean /tmp/uv-cache.

Network timeouts: Some corporate networks have aggressive timeouts.

Increase uv's timeout settings:

ENV UV_HTTP_TIMEOUT=300 \
    UV_KEYRING_PROVIDER=disabled

SSL certificate problems: Corporate environments often have custom CA certificates.

Mount them or install them in the base image:

COPY custom-ca.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates

The bottom line: uv in Docker is production-ready, but you need to understand the specific patterns and failure modes.

This template handles the 90% case

  • modify it based on your specific requirements.

Production Deployment Resources

ContainerTools/distroless)

Docker Build Performance Comparison

Metric

pip + venv

Poetry

uv

First Build Time

8-12 minutes

6-10 minutes

2-4 minutes

Cached Build Time

3-5 minutes

2-4 minutes

15-45 seconds

Final Image Size

450-600 MB

500-700 MB

200-350 MB

Memory Usage (Build)

1-2 GB

1.5-3 GB

1-2 GB

Dependency Resolution

Manual requirements.txt

poetry.lock

uv.lock

Multi-stage Support

Manual setup

Complex

Native

Cache Efficiency

Poor

Good

Excellent

Docker and Container Resources

Related Tools & Recommendations

tool
Similar content

pyenv-virtualenv: Stop Python Environment Hell - Overview & Guide

Discover pyenv-virtualenv to manage Python environments effortlessly. Prevent project breaks, solve local vs. production issues, and streamline your Python deve

pyenv-virtualenv
/tool/pyenv-virtualenv/overview
100%
integration
Similar content

Jenkins Docker Kubernetes CI/CD: Deploy Without Breaking Production

The Real Guide to CI/CD That Actually Works

Jenkins
/integration/jenkins-docker-kubernetes/enterprise-ci-cd-pipeline
83%
tool
Similar content

uv Python Package Manager: Overview, Usage & Performance Review

Discover uv, the high-performance Python package manager. This overview details its core functionality, compares it to pip and Poetry, and shares real-world usa

uv
/tool/uv/overview
78%
tool
Similar content

Docker Security Scanners for CI/CD: Trivy & Tools That Won't Break Builds

I spent 6 months testing every scanner that promised easy CI/CD integration. Most of them lie. Here's what actually works.

Docker Security Scanners (Category)
/tool/docker-security-scanners/pipeline-integration-guide
77%
troubleshoot
Similar content

Docker 'No Space Left on Device' Error: Fast Fixes & Solutions

Stop Wasting Hours on Disk Space Hell

Docker
/troubleshoot/docker-no-space-left-on-device-fix/no-space-left-on-device-solutions
47%
tool
Recommended

pyenv-virtualenv Production Deployment - When Shit Hits the Fan

similar to pyenv-virtualenv

pyenv-virtualenv
/tool/pyenv-virtualenv/production-deployment
45%
tool
Similar content

Docker: Package Code, Run Anywhere - Fix 'Works on My Machine'

No more "works on my machine" excuses. Docker packages your app with everything it needs so it runs the same on your laptop, staging, and prod.

Docker Engine
/tool/docker/overview
43%
troubleshoot
Similar content

Fix Docker Build Context Too Large: Optimize & Reduce Size

Learn practical solutions to fix 'Docker Build Context Too Large' errors. Optimize your Docker builds, reduce context size from GBs to MBs, and speed up develop

Docker Engine
/troubleshoot/docker-build-context-too-large/context-optimization-solutions
43%
tool
Similar content

Django Troubleshooting Guide: Fix Production Errors & Debug

Stop Django apps from breaking and learn how to debug when they do

Django
/tool/django/troubleshooting-guide
43%
tool
Similar content

Python 3.12 Migration Guide: Faster Performance, Dependency Hell

Navigate Python 3.12 migration with this guide. Learn what breaks, what gets faster, and how to avoid dependency hell. Real-world insights from 7 app upgrades.

Python 3.12
/tool/python-3.12/migration-guide
41%
howto
Similar content

Pyenv: Master Python Versions & End Installation Hell

Stop breaking your system Python and start managing versions like a sane person

pyenv
/howto/setup-pyenv-multiple-python-versions/overview
41%
troubleshoot
Similar content

Fix Trivy & ECR Container Scan Authentication Issues

Trivy says "unauthorized" but your Docker login works fine? ECR tokens died overnight? Here's how to fix the authentication bullshit that keeps breaking your sc

Trivy
/troubleshoot/container-security-scan-failed/registry-access-authentication-issues
40%
howto
Similar content

Bun Production Deployment Guide: Docker, Serverless & Performance

Master Bun production deployment with this comprehensive guide. Learn Docker & Serverless strategies, optimize performance, and troubleshoot common issues for s

Bun
/howto/setup-bun-development-environment/production-deployment-guide
38%
tool
Similar content

pandas Performance Troubleshooting: Fix Production Issues

When your pandas code crashes production at 3AM and you need solutions that actually work

pandas
/tool/pandas/performance-troubleshooting
38%
tool
Similar content

Python Overview: Popularity, Performance, & Production Insights

Easy to write, slow to run, and impossible to escape in 2025

Python
/tool/python/overview
37%
tool
Similar content

LangChain: Python Library for Building AI Apps & RAG

Discover LangChain, the Python library for building AI applications. Understand its architecture, package structure, and get started with RAG pipelines. Include

LangChain
/tool/langchain/overview
37%
tool
Similar content

Python 3.13: GIL Removal, Free-Threading & Performance Impact

After 20 years of asking, we got GIL removal. Your code will run slower unless you're doing very specific parallel math.

Python 3.13
/tool/python-3.13/overview
37%
integration
Recommended

Deploying MERN Apps Without Losing Your Mind

The deployment guide I wish existed 5 years ago

MongoDB
/integration/mern-stack-production-deployment/production-cicd-pipeline
37%
tool
Similar content

Podman: Rootless Containers, Docker Alternative & Key Differences

Runs containers without a daemon, perfect for security-conscious teams and CI/CD pipelines

Podman
/tool/podman/overview
35%
troubleshoot
Similar content

Git Fatal Not a Git Repository: Enterprise Security Solutions

When Git Security Updates Cripple Enterprise Development Workflows

Git
/troubleshoot/git-fatal-not-a-git-repository/enterprise-security-scenarios
35%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization