-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #82 from depot/docs/add-python-uv
Docs/add python uv
- Loading branch information
Showing
4 changed files
with
215 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
--- | ||
title: Best practice Dockerfile for Python with poetry | ||
ogTitle: Best practice Dockerfile for Python with poetry | ||
description: A sample best practice poetry Dockerfile for Python from Depot | ||
--- | ||
|
||
Below is an example `Dockerfile` that we use and recommend at Depot when we are building Docker images for Python applications that use `poetry`as their package manager. | ||
|
||
```dockerfile | ||
FROM python:3.12-slim as base | ||
ENV POETRY_VERSION=1.6.1 \ | ||
PYTHONUNBUFFERED=1 \ | ||
PYTHONDONTWRITEBYTECODE=1 \ | ||
PIP_NO_CACHE_DIR=off \ | ||
PIP_DISABLE_PIP_VERSION_CHECK=on \ | ||
PIP_DEFAULT_TIMEOUT=100 \ | ||
POETRY_HOME="/opt/poetry" \ | ||
POETRY_VIRTUALENVS_IN_PROJECT=true \ | ||
POETRY_NO_INTERACTION=1 \ | ||
PYSETUP_PATH="/opt/pysetup" \ | ||
VENV_PATH="/opt/pysetup/.venv" | ||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" | ||
|
||
|
||
FROM base as builder | ||
RUN --mount=type=cache,target=/root/.cache \ | ||
pip install "poetry==$POETRY_VERSION" | ||
WORKDIR $PYSETUP_PATH | ||
COPY ./poetry.lock ./pyproject.toml ./ | ||
RUN --mount=type=cache,target=$POETRY_HOME/pypoetry/cache \ | ||
poetry install --no-dev | ||
|
||
|
||
FROM base as production | ||
ENV FASTAPI_ENV=production | ||
COPY --from=builder $VENV_PATH $VENV_PATH | ||
COPY ./app /app | ||
WORKDIR /app | ||
EXPOSE 8000 | ||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] | ||
``` | ||
|
||
## Explanation of the Dockerfile | ||
|
||
[Poetry](https://github.com/python-poetry/poetry) is a popular Python package manager that helps manage dependencies on your local machine using virtual environments to isolate dependency versions between projects. In a Docker environment, we don't need to use virtual environments, and we can instead use a more straightforward approach to installing and managing dependencies. Assuming your project is currently using Poetry and has a `pyproject.toml` file, you can use the following Dockerfile to build your project with multi-stage builds to produce an efficient build and optimized final image. | ||
|
||
### Stage 1: `FROM python:3.12-slim-bookworm AS base` | ||
|
||
Using a common base image for all stages 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. | ||
|
||
```dockerfile | ||
ENV POETRY_VERSION=1.6.1 \ | ||
PYTHONUNBUFFERED=1 \ | ||
PYTHONDONTWRITEBYTECODE=1 \ | ||
PIP_NO_CACHE_DIR=off \ | ||
PIP_DISABLE_PIP_VERSION_CHECK=on \ | ||
PIP_DEFAULT_TIMEOUT=100 \ | ||
POETRY_HOME="/opt/poetry" \ | ||
POETRY_VIRTUALENVS_IN_PROJECT=true \ | ||
POETRY_NO_INTERACTION=1 \ | ||
PYSETUP_PATH="/opt/pysetup" \ | ||
VENV_PATH="/opt/pysetup/.venv" | ||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" | ||
``` | ||
|
||
- `POETRY_VERSION=1.6.1` specifies the version of Poetry to install. | ||
- `PYTHONUNBUFFERED=1` tells Python to not buffer the output. This is useful for ensuring logs are output in real-time, so a crash doesn't obscure the logs that would otherwise be in a buffer. | ||
- `POETRY_HOME` specifies a deterministic location for Poetry to install itself. | ||
- `PYSETUP_PATH` specifies a deterministic location for Poetry to install the project's dependencies. | ||
- `VENV_PATH` specifies a deterministic location for the virtual environment to be created. | ||
|
||
```dockerfile | ||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" | ||
``` | ||
|
||
After setting the environment variables, we add the Poetry and virtual environment paths to the `PATH` environment variable so that we can run Poetry and the project's dependencies without specifying the full path. | ||
|
||
### Stage 2: `FROM base AS builder` | ||
|
||
The builder stage efficiently installs Poetry and the project's production dependencies with caching enabled. A similar stage can be used for development dependencies if needed by changing the `--no-dev` flag in the `poetry install` command. | ||
|
||
```dockerfile | ||
RUN --mount=type=cache,target=/root/.cache \ | ||
pip install "poetry==$POETRY_VERSION" | ||
WORKDIR $PYSETUP_PATH | ||
COPY ./poetry.lock ./pyproject.toml ./ | ||
RUN --mount=type=cache,target=$POETRY_HOME/pypoetry/cache \ | ||
poetry install --no-dev | ||
``` | ||
|
||
We use `pip` to install `poetry` so we can cache the installation. Then, we copy over only the `poetry.lock` and `pyproject.toml` files to the `$PYSETUP_PATH` directory and run `poetry install` to install the project's dependencies. By using the `--no-dev` flag, we ensure that only production dependencies are installed. | ||
|
||
### Stage 3: `FROM base AS production` | ||
|
||
In the production stage, we copy the virtual environment from the builder stage and the project source code into the final image. We then set the working directory to the project source code and expose the port the application listens on. Finally, we define the command to run the application. | ||
|
||
```dockerfile | ||
ENV FASTAPI_ENV=production | ||
COPY --from=builder $VENV_PATH $VENV_PATH | ||
COPY ./app /app | ||
WORKDIR /app | ||
EXPOSE 8000 | ||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] | ||
``` | ||
|
||
Using this Dockerfile pattern, we are able to avoid installing poetry in the final image. In fact, as we are only copying in the previously installed production dependencies and source code, the final stage is extremely fast, even in the event the project source changes. | ||
|
||
Your project may require additional tweaks to this Dockerfile, but if you are a poetry user, this is a great starting point for efficiently building your project with Docker. Consider adding an additional development stage for development dependencies and adding more stages for linting, testing, or other tasks as needed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
--- | ||
title: Best practice Dockerfile for Python with uv | ||
ogTitle: Best practice Dockerfile for Python with uv | ||
description: A sample best practice uv Dockerfile for Python from Depot | ||
--- | ||
|
||
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. | ||
|
||
```dockerfile | ||
FROM python:3.12-slim-bookworm AS base | ||
|
||
FROM base AS builder | ||
COPY --from=ghcr.io/astral-sh/uv:0.4.9 /uv /bin/uv | ||
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy | ||
WORKDIR /app | ||
COPY uv.lock pyproject.toml /app/ | ||
RUN --mount=type=cache,target=/root/.cache/uv \ | ||
uv sync --frozen --no-install-project --no-dev | ||
COPY . /app | ||
RUN --mount=type=cache,target=/root/.cache/uv \ | ||
uv sync --frozen --no-dev | ||
|
||
|
||
FROM base | ||
COPY --from=builder /app /app | ||
ENV PATH="/app/.venv/bin:$PATH" | ||
EXPOSE 8000 | ||
CMD ["uvicorn", "uv_docker_example: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: `FROM python:3.12-slim-bookworm AS base` | ||
|
||
```dockerfile | ||
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. | ||
|
||
### Stage 2: `FROM base AS builder` | ||
|
||
```dockerfile | ||
COPY --from=ghcr.io/astral-sh/uv:0.4.9 /uv /bin/uv | ||
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy | ||
``` | ||
|
||
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. | ||
|
||
```dockerfile | ||
WORKDIR /app | ||
COPY uv.lock pyproject.toml /app/ | ||
RUN --mount=type=cache,target=/root/.cache/uv \ | ||
uv sync --frozen --no-install-project --no-dev | ||
``` | ||
|
||
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. | ||
|
||
```dockerfile | ||
COPY . /app | ||
RUN --mount=type=cache,target=/root/.cache/uv \ | ||
uv sync --frozen --no-dev | ||
``` | ||
|
||
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. | ||
|
||
### Stage 3: `FROM base` (Final stage) | ||
|
||
```dockerfile | ||
FROM base | ||
COPY --from=builder /app /app | ||
ENV PATH="/app/.venv/bin:$PATH" | ||
``` | ||
|
||
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. | ||
|
||
```dockerfile | ||
EXPOSE 8000 | ||
CMD ["uvicorn", "uv_docker_example:app", "--host", "0.0.0.0", "--port", "8000"] | ||
``` | ||
|
||
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`. | ||
|
||
## References | ||
|
||
- [UV Github](https://github.com/astral-sh/uv) | ||
- [Official UV Docker documentation](https://docs.astral.sh/uv/guides/integration/docker/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
--- | ||
title: Best practice Dockerfiles for Python | ||
ogTitle: Best practice Dockerfiles for Python | ||
description: A set of best practice Dockerfiles for building Docker images for Python | ||
--- | ||
|
||
We've assembled some best practice Dockerfiles for building Docker images for Python using different package managers. These Dockerfiles are what we recommend when building Docker images for Python applications, but may require modifications based on your specific use case. | ||
|
||
## Guides | ||
|
||
- [Dockerfile for Python using `uv`](/docs/languages/python-uv-dockerfile) | ||
- [Dockerfile for Python using `poetry`](/docs/languages/python-poetry-dockerfile) |