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.
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.
FROM python:3.12-slim-bookworm AS base
For optimal caching, we use the same base image for all of our stages. This ensures compatibility between the build and deployment stages and allows us to take advantage of Docker's layer caching to produce fewer layers in the build. An -alpine
image can also be used for an even smaller final image, but some projects may require additional dependencies to be installed.
FROM base AS builder
In the builder we copy in the uv
binary from the official UV image at a specific version tag.
UV_COMPILE_BYTECODE=1
tells uv
to compile Python files to .pyc
bytecode files. This takes a little longer to install (part of the build process), but often speeds up the application's startup time in the container.
UV_LINK_MODE=copy
tells uv
to copy the Python files into the container from the cache mount, resolving any issues from symlinks.
After setting the working directory, we copy in only the uv.lock
and pyproject.toml
files to the /app
directory and run uv sync
to install the dependencies. This allows us to take advantage of Docker's layer caching to cache the dependencies, which change less often, before copying in the rest of the application code.
After the dependencies are installed and the layer is cached, we copy in the rest of the application code and run uv sync
again, without the --no-install-project
flag, to install the application. If the application source code changes, this layer will be invalidated, but the dependencies will not need to be reinstalled.
FROM base
(Final stage)The final stage starts from our minimal base image and copies in the /app
directory from the builder stage. In this case, we set the PATH
environment variable to include the virtual environment's bin
directory so that we can run the application without specifying the full path to the uvicorn
executable.
After copying in your application, you can expose whichever port your application listens on and set the default command to run your application. In this case, we are running a uvicorn
application on port 8000
.