iTranslated by AI
Working with uv on Docker
TL;DR
The code described in this article can be downloaded from the repository below. (I am looking forward to your ⭐)
Running the container:
uv_on_docker_playground (main) » docker run -it uv_on_docker
Bytecode compiled 1 file in 59ms
Hello World
Using it as a development container:
$ docker compose watch app
$ docker compose exec app bash
$ root@393b1996df7c:/app# uv run hello
Bytecode compiled 1 file in 39ms
# Hello World
Preface
Previously, I posted an article about using uv on Docker.
At that point, I posted it as a "just tried it out" level, but it has been receiving regular traffic. Looking at it now, there are several parts I would like to correct, so I have decided to rewrite it.

- Note: This article does not explain what uv is or how to use uv commands. Please read the documentation on GitHub.
Dockerfile
Next is the configuration for a development container. (I will introduce configurations effective for production later.)
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
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-dev
ADD . /app
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen
ENV PATH="/app/.venv/bin:$PATH"
ENTRYPOINT []
CMD ["uv", "run", "hello"]
Docker Image
Regarding Docker images, useful ones are being distributed. There are multiple options available, such as distroless, alpine, and debian. In this case, I am using debian.
# distroless
ghcr.io/astral-sh/uv:latest
ghcr.io/astral-sh/uv:{major}.{minor}.{patch}, e.g., ghcr.io/astral-sh/uv:0.6.14
ghcr.io/astral-sh/uv:{major}.{minor}, e.g., ghcr.io/astral-sh/uv:0.6 (the latest patch version)
# alpine:3.20:
ghcr.io/astral-sh/uv:alpine
ghcr.io/astral-sh/uv:alpine3.20
# Based on debian:bookworm-slim:
ghcr.io/astral-sh/uv:debian-slim
ghcr.io/astral-sh/uv:bookworm-slim
# Based on buildpack-deps:bookworm:
ghcr.io/astral-sh/uv:debian
ghcr.io/astral-sh/uv:bookworm
# Based on python3.x-alpine:
ghcr.io/astral-sh/uv:python3.13-alpine
ghcr.io/astral-sh/uv:python3.12-alpine
...
# Based on python3.x-bookworm:
ghcr.io/astral-sh/uv:python3.13-bookworm
ghcr.io/astral-sh/uv:python3.12-bookworm
...
# Based on python3.x-slim-bookworm:
ghcr.io/astral-sh/uv:python3.13-bookworm-slim
ghcr.io/astral-sh/uv:python3.12-bookworm-slim
...
To check all available images, please refer to the following link:
Cache
uv sync --no-install-project installs only the dependencies, not the project itself. Unlike the project code, dependencies are generally static, so I am using this configuration.
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-dev
Additionally, the --no-editable option is useful when running in production environments. Using this allows you to include the project in a virtual environment synced in one stage and then copy only the virtual environment to the final image, rather than the source code.
Example of multi-stage build
# Install uv
FROM python:3.12-slim AS builder
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Change the working directory to the `app` directory
WORKDIR /app
# Install dependencies
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 the project into the intermediate image
ADD . /app
# Sync the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-editable
FROM python:3.12-slim
# Copy the environment, but not the source code
COPY --from=builder --chown=app:app /app/.venv /app/.venv
# Run the application
CMD ["/app/.venv/bin/hello"]
ByteCode Compilation
In uv, you can specify whether to build to the Python interpreter's intermediate representation during sync. While this increases the execution time during installation due to the build process, it can speed up the first call of the package.
RUN uv sync --compile-bytecode
Also, by writing it as follows, all instructions executed within the Dockerfile will be affected by this setting, and as a result, the entire environment will be targeted for compilation.
ENV UV_COMPILE_BYTECODE=1
compose.yml
services:
app:
build: .
tty: true
working_dir: /app
command: ["tail", "-f", "/dev/null"]
develop:
watch:
- action: sync
path: .
target: /app
ignore:
- .venv/
- action: rebuild
path: ./uv.lock
The watch part is optional. It monitors file changes and automatically triggers a sync or rebuild when it detects differences.
pyproject.toml
[project]
name = "app"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project.scripts]
hello = "app:hello"
This is a configuration without any specific modifications. The project introduced this time was initialized with an older version of uv, so it uses hello.py. Currently (>=v0.6.0), running uv init project generates main.py.
Prior to v0.6.0, uv created a file named hello.py instead of main.py.
Dependabot
You can easily manage dependencies with Dependabot. Dependabot supports uv.lock, the version management file that includes uv dependencies. You can set it up by configuring .github/dependabot.yml as follows:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "monthly"
allow:
- dependency-name: "pydantic"
ignore:
- dependency-name: "pydantic"
update-types:
- "version-update:semver-patch"
This feature was released on March 13, 2025.
Afterword
I hope this serves as a helpful reference for those who have started using uv.
By the way, lately I often develop using nix develop. I recommend developing with Nix because it also makes distribution easy.
{
description = "Development shell for uv environment (Python 3.13)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in {
devShell = pkgs.mkShell {
buildInputs = [
pkgs.python313
pkgs.uv
];
shellHook = ''
echo "uv version: $(uv --version)"
echo "python version: $(python --version)"
'';
};
});
}
References
Discussion