Metadata-Version: 2.4
Name: pdm-dockerize
Version: 0.7.2
Summary: Help generating docker images from PDM projects
Keywords: pdm,docker
Author-Email: Axel Haustant <noirbizarre@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Plugins
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Code Generators
Classifier: Typing :: Typed
Project-URL: Homepage, https://github.com/noirbizarre/pdm-dockerize
Project-URL: Documentation, https://github.com/noirbizarre/pdm-dockerize#readme
Project-URL: Repository, https://github.com/noirbizarre/pdm-dockerize
Project-URL: Issues, https://github.com/noirbizarre/pdm-dockerize/issues
Requires-Python: >=3.10
Requires-Dist: pdm>=2.27
Description-Content-Type: text/markdown

# pdm-dockerize

[![CI](https://github.com/noirbizarre/pdm-dockerize/actions/workflows/ci.yml/badge.svg)](https://github.com/noirbizarre/pdm-dockerize/actions/workflows/ci.yml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/noirbizarre/pdm-dockerize/main.svg)](https://results.pre-commit.ci/latest/github/noirbizarre/pdm-dockerize/main)
[![PyPI](https://img.shields.io/pypi/v/pdm-dockerize)](https://pypi.org/project/pdm-dockerize/)
[![PyPI - License](https://img.shields.io/pypi/l/pdm-dockerize)](https://pypi.org/project/pdm-dockerize/)
[![codecov](https://codecov.io/gh/noirbizarre/pdm-dockerize/graph/badge.svg?token=P949azXcf0)](https://codecov.io/gh/noirbizarre/pdm-dockerize)

Help generating docker image from PDM projects.

## Installation

Install `pdm-dockerize`:

### With `uv`

If you installed `pdm` with `uv`:

```console
uv tool install pdm --with pdm-dockerize
```

### With `pipx`

If you installed `pdm` with `pipx` and want to have the command for all projects:

```console
pipx inject pdm pdm-dockerize
```

### With `pip`

If you manually installed `pdm` with `pip`, just install the extra dependency in the same environment:

```console
pip install pdm-dockerize
```

### With `pdm`

You can also install it as a standard `pdm` plugin.

Either globally:

```console
pdm self add pdm-dockerize
```

Either as a local plugin in your project:

```toml
[tool.pdm]
plugins = [
    "pdm-dockerize",
]
```

Then:

```console
pdm install --plugins
```

## Usage

Just use `pdm dockerize` in your multistage build:

```dockerfile
# syntax=docker/dockerfile:1
ARG PY_VERSION=3.12

##
# Build stage: build and install dependencies
##
FROM python:${PY_VERSION} AS builder

ARG VERSION=0.dev
ENV PDM_BUILD_SCM_VERSION=${VERSION}

WORKDIR /project

# install PDM
RUN pip install -U pip setuptools wheel
RUN pip install pdm pdm-dockerize

RUN --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=bind,source=pdm.lock,target=pdm.lock \
    --mount=type=cache,target=$HOME/.cache,uid=$UUID \
    pdm dockerize --prod -v

##
# Run stage: create the final runtime container
##
FROM python:${PY_VERSION} AS runtime

WORKDIR /app

# Fetch built dependencies
COPY --from=builder /project/dist/docker /app
# Copy needed files from your project (filter using `.dockerignore`)
COPY  . /app

ENTRYPOINT ["/app/entrypoint"]
CMD ["your-default-command"]
```

### Using `uv`

When PDM is configured with `use_uv = true`, `pdm-dockerize` automatically uses `uv pip install --target`
instead of the default pip-based installer for faster dependency installation.
If `uv` is not found at runtime, it falls back gracefully to the pip-based installer.

Lockfiles generated by `uv` (which store extras as separate entries) are automatically handled.

Here is an example Dockerfile using `uv`:

```dockerfile
# syntax=docker/dockerfile:1
ARG PY_VERSION=3.12

##
# Build stage: build and install dependencies
##
FROM python:${PY_VERSION} AS builder

ARG VERSION=0.dev
ENV PDM_BUILD_SCM_VERSION=${VERSION}

WORKDIR /project

# install uv, PDM and pdm-dockerize
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
RUN uv pip install --system pdm pdm-dockerize

# enable uv as PDM downloader
RUN pdm config use_uv true

RUN --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    --mount=type=bind,source=pdm.lock,target=pdm.lock \
    --mount=type=cache,target=$HOME/.cache,uid=$UUID \
    pdm dockerize --prod -v

##
# Run stage: create the final runtime container
##
FROM python:${PY_VERSION} AS runtime

WORKDIR /app

# Fetch built dependencies
COPY --from=builder /project/dist/docker /app
# Copy needed files from your project (filter using `.dockerignore`)
COPY  . /app

ENTRYPOINT ["/app/entrypoint"]
CMD ["your-default-command"]
```

### Command line options

```text
pdm dockerize [OPTIONS] [TARGET]
```

| Option                                | Description                                          |
|---------------------------------------|------------------------------------------------------|
| `TARGET`                              | Output directory (default: `dist/docker`)            |
| `--prod` / `--production`             | Select production dependencies only                  |
| `-G GROUP` / `--group GROUP`          | Select a specific dependency group (can be repeated) |
| `--no-default`                        | Do not include the default dependencies              |
| `--dry-run`                           | Preview what would be installed without writing      |
| `-L LOCKFILE` / `--lockfile LOCKFILE` | Use an alternative lockfile                          |

### Selecting scripts

By default, the `dockerize` command will render a script without any command as it does not select any script by default.

You can select scripts with the `include` and `exclude` properties of the `tool.pdm.dockerize` section.
Those properties are optional, can be either a string or list of string.
Each string is a [`fnmatch` filter pattern](https://docs.python.org/3/library/fnmatch.html)

Dockerize first select script based on the include patterns and then filter-out those matching with any exclude pattern.

#### Include all scripts

```toml
[tool.pdm.dockerize]
include = "*"
```

#### Include some specific scripts

```toml
[tool.pdm.dockerize]
include = ["my-script", "my-other-script"]
```

#### Include all scripts excluding those matching `prefix-*`

```toml
[tool.pdm.dockerize]
include = "*"
exclude = "prefix-*"
```

#### Include all scripts matching a prefix but two

```toml
[tool.pdm.dockerize]
include = "prefix-*"
exclude = ["prefix-not-you", "prefix-you-neither"]
```

### Selecting binaries

By default, the `dockerize` command will not copy any python executable provided by your dependencies.
You can select binaries with the `include_bins` and `exclude_bins` properties of the `tool.pdm.dockerize` section.
Syntax and behavior are exactly the exact sames than `include`/`exclude` for script selection.

#### Include all python executables

```toml
[tool.pdm.dockerize]
include_bins = "*"
```

#### Include some specific executables

Most of the time, it will look like this:

```toml
[tool.pdm.dockerize]
include_bins = ["uvicorn"]
```

### Generated entrypoint

The `dockerize` command generates a POSIX `sh` entrypoint script alongside the installed packages.
This entrypoint exposes selected PDM scripts as subcommands. Here is what it does:

- **`PYTHONPATH`**: automatically set to include `lib/`. For `pdm.backend`-based projects using src-layout, the package source directory is also added.
- **`PATH`**: automatically set to include `bin/`, so any installed console-script binaries are available.
- **Pre/post scripts**: if you define `pre_<script>` or `post_<script>` hooks in your PDM scripts, they are executed before and after the main script respectively.
- **`{args}` interpolation**: the PDM `{args}` and `{args:defaults}` placeholder syntax is supported. Arguments passed to the entrypoint subcommand are forwarded through `"$@"`. When no `{args}` placeholder is present, arguments are appended automatically.
- **Script kinds**: all PDM script kinds are supported: `cmd`, `call`, `shell`, and `composite`.

### Controlling environment

`pdm-dockerize` respects defined environment variables:

- scripts `env` variables are properly set
- shared `_.env` variables are properly set
- scripts `env_file` are properly loaded
- shared `_.env_file` are properly loaded

In addition, you can define some docker-only environment variables using the `tool.pdm.dockerize.env` table
or some docker-only `.env` files using `tool.pdm.dockerize.env_file`

#### Defining docker-only environment variables

Those environment variables will only be effective in the docker entrypoint.

```toml
[tool.pdm.dockerize.env]
VAR = "value"
```

#### Loading docker-only environment files

This file will only be loaded in the docker entrypoint.

```toml
[tool.pdm.dockerize]
env_file = "docker.env"
```

## Internals

This plugin works by subclassing some `pdm` classes to reuse the installation process:

- `DockerizeEnvironment`, a `pdm` `PythonLocalEnvironment` targeting the dockerize output directory
- `DockerizeInstallManager`, a `pdm` `InstallManager` filtering binaries
- `DockerizeSynchronizer`, a `pdm` `Synchronizer` using a `DockerizeInstallManager` as `InstallManager`
- `DockerizeUvSynchronizer`, an alternative synchronizer that invokes `uv pip install --target` as a subprocess when `use_uv` is enabled
- `FilteringDestination`, a `pdm` `InstallDestination` filtering binaries

This way, the dockerization reuses the same installation process, just tuned for docker and augmented with `pdm-dockerize` specifics.

## Contributing

Read the [dedicated contributing guidelines](./CONTRIBUTING.md).
