Docker Images for Production ¶
This guide uses uv as the package manager with pyproject.toml. See the uv Docker integration guide for comprehensive documentation.
Multi-Stage Build with UV ¶
This is the recommended approach for production. It separates dependency installation from project installation to maximize Docker layer caching:
# Dockerfile
FROM python:3.13-slim-bookworm AS builder
ENV PYTHONUNBUFFERED=1
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
# Install dependencies only (cached layer)
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project
# Copy source code
COPY . /app
# Sync project (includes the application itself)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
# Final stage
FROM python:3.13-slim-bookworm
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv
# Make sure scripts in .venv are in PATH
ENV PATH="/app/.venv/bin:$PATH"
# Copy application code
COPY --from=builder /app /app
EXPOSE 8000
# Disable access log for production falling back to default stario logging
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]
Single-Stage Build (Development-Friendly) ¶
For simpler setups or development environments:
FROM python:3.13-slim-bookworm
ENV PYTHONUNBUFFERED=1
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
# Copy project files
COPY pyproject.toml uv.lock ./
# Install dependencies and project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
# Copy application
COPY . .
EXPOSE 8000
# Disable access log for production falling back to default stario logging
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]
Security Best Practices ¶
If you're running a production environment, you should run the container as a non-root user.
This is a good practice for security and isolation.
You can do this by adding a USER directive to the Dockerfile.
FROM python:3.13-slim-bookworm
ENV PYTHONUNBUFFERED=1
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
# Don't run as root
RUN useradd -m -u 1000 appuser
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project
COPY --chown=appuser:appuser . .
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
USER appuser
EXPOSE 8000
ENV PATH="/app/.venv/bin:$PATH"
# Disable access log for production falling back to default stario logging
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]
Non-Editable Install (Smaller Final Image) ¶
For production where you don't need editable installs:
FROM python:3.13-slim-bookworm AS builder
ENV PYTHONUNBUFFERED=1
ENV UV_COMPILE_BYTECODE=1
WORKDIR /app
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-project --no-editable
COPY . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-editable
# Final stage - only copy venv, not source
FROM python:3.13-slim-bookworm
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8000
# Disable access log for production falling back to default stario logging
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]
Docker Compose for Production ¶
version: "3.8"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql+asyncpg://user:pass@db:5432/app
LOG_LEVEL: info
depends_on:
- db
restart: always
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- db_data:/var/lib/postgresql/data
restart: always
volumes:
db_data:
Build and Push ¶
# Build
docker build -t myapp:latest .
# Push to registry
docker push registry.example.com/myapp:latest
# Run with environment
docker run -p 8000:8000 \
-e DATABASE_URL=postgresql://... \
myapp:latest
Key UV Docker Benefits ¶
- Faster builds - Bytecode compilation with
UV_COMPILE_BYTECODE=1 - Smaller images - No pip overhead, efficient caching
- Better caching - Lockfile-based builds with
--frozen - Reproducibility -
uv.lockensures exact versions - No editable installs -
--no-editablefor smaller final images
See uv Docker Integration Guide for more details on caching strategies and optimization.
