We use cookies to understand how people use Depot.
Container Builds

Optimal Dockerfile for Python with uv

Below is an example Dockerfile that we use and recommend at Depot when we are building Docker images for Python applications that use uv as their package manager.

# syntax=docker/dockerfile:1

FROM python:3.13-slim AS build

COPY --from=ghcr.io/astral-sh/uv:0.8.21 /uv /uvx /bin/

WORKDIR /app

ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

COPY uv.lock pyproject.toml ./

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

COPY . .

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

FROM python:3.13-slim AS runtime

ENV PATH="/app/.venv/bin:$PATH"

RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser

WORKDIR /app

COPY --from=build --chown=appuser:appgroup /app .

USER appuser

ENTRYPOINT ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Explanation of the Dockerfile

Using a multi-stage build, we can separate our build from our deployment, taking full advantage of Docker's layer caching to speed up our builds and produce a smaller final image.

Stage 1: Build Stage (FROM python:3.13-slim AS build)

FROM python:3.13-slim AS build

COPY --from=ghcr.io/astral-sh/uv:0.8.21 /uv /uvx /bin/

WORKDIR /app

ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

We use Python 3.13 slim for a smaller base image. We copy the uv binary from the official uv container image, which is more efficient than installing it via pip.

Key environment variables:

  • UV_COMPILE_BYTECODE=1: Tells uv to compile Python files to bytecode for faster startup
  • UV_LINK_MODE=copy: Ensures uv copies files instead of creating symlinks

Dependency installation

COPY uv.lock pyproject.toml ./

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

First, we copy the lock file and project configuration, then install dependencies without the project itself. This layer caches dependencies separately from application code.

Project installation

COPY . .

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

After copying the full application, we install the project itself using the frozen lock file to ensure reproducible builds.

Stage 2: Runtime Stage (FROM python:3.13-slim AS runtime)

FROM python:3.13-slim AS runtime

ENV PATH="/app/.venv/bin:$PATH"

RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser

WORKDIR /app

COPY --from=build --chown=appuser:appgroup /app .

USER appuser

ENTRYPOINT ["python", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

The runtime stage uses a clean slim image and creates a non-root user for security. We copy the entire application including the virtual environment from the build stage and set proper ownership.

Understanding BuildKit Cache Mounts

Cache mounts are one of the most powerful features for optimizing Docker builds with Depot. This Dockerfile uses the following cache mount syntax:

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

Cache Mount Parameters Explained

  • type=cache: Specifies this is a cache mount that persists across builds.
  • target=/root/.cache/uv: The mount point for uv's cache directory where downloaded packages and compiled wheels are stored.

For more information regarding uv cache mounts, please visit the official uv documentation.