# Getting started with the Python Chainguard Container

URL: https://chainguard-docs-preview-git-fix-ai-metadata-year.chainguard.app/chainguard/containers/getting-started/languages-and-runtimes/python.md
Last Modified: September 28, 2026
Tags: Chainguard Containers

Learn how to use Chainguard's Python container images for secure Python applications with minimal CVEs, distroless design, and comprehensive supply chain security features


Chainguard's Python container images provide a more secure foundation for Python applications through distroless design, containing significantly fewer CVEs compared to traditional Python images. These production-ready images are optimized for building and running Python workloads.

Two variants of Chainguard Python images are available: a minimal runtime image containing only Python and its standard library, and a `-dev` variant that includes pip and a shell for development purposes. Since most Python applications require third-party packages, the recommended approach is using a [multi-stage Docker build](https://docs.docker.com/build/building/multi-stage/) with the `-dev` image for dependency installation and the minimal image for runtime.

This guide covers two examples of Python container images based on Wolfi as a runtime. The first uses the minimal image containing just Python, which has access to the [Python standard library](https://docs.python.org/3/library/). The second demonstrates a multi-stage build.

**What is distroless?**


Distroless container images are minimal container images containing only essential software required to build or execute an application. That means no package manager, no shell, and no bloat from software that only makes sense on bare metal servers.





**What is Wolfi?**


[Wolfi](https://github.com/wolfi-dev/) is a community Linux undistro created specifically for containers. This brings distroless to a new level, including additional features targeted at securing the software supply chain of your application environment: comprehensive SBOMs, signatures, daily updates, and timely CVE fixes.




**What are multi-stage builds?**


<p><a href="https://docs.docker.com/build/building/multi-stage/">Multi-stage builds</a> are a Docker feature that allow you to use multiple <code>FROM</code> statements in a single Dockerfile, where each statement begins a new build stage. In a typical pattern, an early stage uses a full-featured builder image to compile code or generate artifacts, while a later stage uses a minimal runtime image and copies in only what's needed to run the application. Only what you explicitly copy from one stage carries forward — everything else is discarded when that stage completes.</p>

<p>This approach has significant security benefits. Build tools like compilers, shells, and package managers are broadly exploitable general-purpose utilities that expand an image's attack surface. By leaving them behind in the builder stage, the runtime image has fewer packages, fewer potential CVEs, and a smaller blast radius in the event of a compromise. Reducing unnecessary components also improves observability and makes risk assessment easier, since every package in the final image can be directly tied to a runtime requirement.</p>

<p>Chainguard's container images are designed with this pattern in mind. Most have a <a href="/chainguard/containers/concepts/container-variants/"><code>:latest-dev</code> development variant</a> suited for use as a builder stage, and a corresponding <code>:latest</code> (or <code>-slim</code>) standard image for the distroless runtime. For example, a Go application can be compiled in the <code>go:latest-dev</code> builder stage and its binary copied into a <code>static</code> or <code>glibc-dynamic</code> runtime image — with no Go toolchain in the final container.</p>




**Chainguard Containers**


[Chainguard Containers](https://www.chainguard.dev/chainguard-images?utm_source=cg-academy&utm_medium=referral&utm_campaign=dev-enablement) is a mix of distroless and development container images based on Wolfi. Daily builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.




## Example 1 — Minimal Python Chainguard Container

In this example, you'll build and run a distroless Python Chainguard Container in a single-stage build process. You'll first make a demonstration app, then build and run it.

### Step 1: Setting up a demo application

Start by creating a basic command-line Python application to serve as a demo. This app generates random octopus facts based on a list in a text file, using the `random` module from the Python standard library.

First, create a directory for your app. You can use any meaningful name and path; this example uses `octo-facts/`.

```shell
mkdir ~/octo-facts/ && cd $_
```

Create a new file named `main.py` to serve as the application entry point. The following Python script defines a light CLI app that takes in a text file, `facts.txt`, and returns a random line from that file.

```shell
cat > main.py <<'EOF'
'''Import random module to implement random.choice() function'''
import random


def random_line(text):
    '''Opens and reads lines of a UTF-8 encoded file, returning a random line'''
    with open(text, 'r', encoding='UTF-8') as file:
        line = file.readlines()
        return random.choice(line)

def main():
    '''Prints random line from facts.txt; verify your path'''
    print(random_line('facts.txt'))

if __name__ == "__main__":
    main()
EOF
```

Next, pull down the `facts.txt` file with `curl`. [Inspect the URL](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/octo-facts/facts.txt) before downloading it to ensure it is safe to do so. Make sure you are still in the same directory where your `main.py` script is.

```shell
curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/octo-facts/facts.txt
```

At this point, you can run the script and be sure you are satisfied with the functionality. We recommend that you use a Python programming environment. Determine whether your system uses the `python` or `python3` command.

```shell
python main.py
```

You should receive the output of a randomized octopus fact.

```
The wolfi octopus was discovered in 1913.
```

The demo application is now ready. In the next step, you’ll create a Dockerfile to run your app.

### Step 2: Creating the Dockerfile

For this single-stage build, you'll only need one `FROM` line in your Dockerfile. The resulting container is based on the distroless Python Wolfi container image, which means it doesn’t come with a package manager or even a shell.

Begin by creating a Dockerfile. The following Dockerfile:

1. Starts a build stage based on the `python:latest` image;
2. Declares the working directory;
3. Copies the script and the text file that's being read;
4. Sets up the application as entry point for this container.

```shell
cat > Dockerfile <<'EOF'
FROM cgr.dev/chainguard/python:latest

WORKDIR /octo-facts

COPY main.py facts.txt ./

ENTRYPOINT [ "python", "/octo-facts/main.py" ]
EOF
```

You can now build the container image. If you receive an error, try again with `sudo`.

```shell
docker build . --pull -t octo-facts
```

Once the build is finished, run the container.

```shell
docker run --rm octo-facts
```

And you should get output similar to what you got before, with a random octopus fact.

```
Octopuses can breathe and see through their skin.
```

You have successfully completed the single-stage Python Chainguard Container. At this point, you can continue to the [multi-stage example](#example-2-multi-stage-build-for-python-chainguard-container) or [advanced usage](#advanced-usage).

## Example 2 — Multi-stage build for Python Chainguard Container

In this example, you'll build and run a multi-stage Python Chainguard Container. The build image includes pip and a shell, and the final distroless image leaves out these development tools for production.

### Step 1: Setting up a demo application

Start by creating a Python application that takes in an image file and converts it to ANSI escape sequences on the CLI to render an image.

To begin, create a directory for your app. You can use any meaningful name and path that resonates with you; this example uses `linky/`.

```shell
mkdir ~/linky/ && cd $_
```

First, write out the requirements for your app in a file named `requirements.txt`. This installs version 0.2.2 of [climage](https://pypi.org/project/climage/), which converts images into ANSI escape sequences:

```shell
cat > requirements.txt <<'EOF'
climage==0.2.2
EOF
```

Create a file named `linky.py` to hold your Python code. It defines a CLI app that takes in an
image file, `linky.png`, and prints a representation of that file to the terminal:

```shell
cat > linky.py <<'EOF'
'''import climage module to display images on terminal'''
from climage import convert


def main():
    '''Take in PNG and output as ANSI to terminal'''
    output = convert('linky.png', is_unicode=True)
    print(output)

if __name__ == "__main__":
    main()
EOF
```

Next, pull down the `linky.png` image file with `curl`. [Inspect the URL](https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/linky/linky.png) before downloading it to ensure it is safe to do so. Make sure you are still in the same directory where your `linky.py` script is.

```shell
curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/linky/linky.png
```

With your demo application ready, you can move on to the container stage.

### Step 2: Creating the Dockerfile

To keep the final container distroless while still being able to install dependencies with pip, the build consists of two stages: first, you’ll build the application using the `python:latest-dev` image variant, a Wolfi-based image that includes pip and other useful tools for development. Then, you’ll create a separate stage for the final image. The resulting container is based on the distroless Python Wolfi container image, which means it doesn’t come with pip or even a shell.

Begin by creating a Dockerfile. The following Dockerfile:

1. Starts a new build stage based on the `python:latest-dev` container image and calls it `builder`;
2. Creates a new virtual environment to cleanly hold the application's dependencies, using `--without-pip` to keep pip out of the environment and therefore out of the final image;
3. Copies `requirements.txt` from the current directory to the `/linky` location in the container;
4. Runs `pip --python /linky/venv/bin/python install --no-cache-dir -r requirements.txt` to install dependencies, where `--python` points the builder's own pip at the virtual environment;
5. Starts a new build stage based on the `python:latest` image;
6. Copies the dependencies in the virtual environment from the builder stage, and the source code from the current directory;
7. Sets up the application as the entry point for this container.

Write this configuration to your own Dockerfile:

```shell
cat > Dockerfile <<'EOF'
FROM cgr.dev/chainguard/python:latest-dev AS builder

ENV LANG=C.UTF-8
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/linky/venv/bin:$PATH"

WORKDIR /linky

RUN python -m venv --without-pip /linky/venv
COPY requirements.txt .

RUN pip --python /linky/venv/bin/python install --no-cache-dir -r requirements.txt

FROM cgr.dev/chainguard/python:latest

WORKDIR /linky

ENV PYTHONUNBUFFERED=1
ENV PATH="/venv/bin:$PATH"

COPY linky.py linky.png ./
COPY --from=builder /linky/venv /venv

ENTRYPOINT [ "python", "/linky/linky.py" ]
EOF
```

You can now build the container image. If you receive a permission error, try running under `sudo`.

```shell
docker build . --pull -t linky
```

Once the build is finished, run the image with:

```shell
docker run --rm linky
```

And you should get output similar to what you got before, with a printed Linky on the command line.

## Advanced usage

<p>If your project requires a more specific set of packages that aren't included within the general-purpose Python Chainguard Container, you'll first need to check if the package you want is already available on the <a href="https://github.com/wolfi-dev/os" title="Wolfi on GitHub">wolfi-os repository</a>. </p>

<blockquote>
    <p><b>Note</b>: If you're building on top of a container image other than the wolfi-base container image, the image will run as a non-root user. Because of this, if you need to install packages with <code>apk add</code> you need to use the <code>USER root</code> directive.</p>
</blockquote>

<p>If the package is available, you can use the <a href="https://images.chainguard.dev/directory/image/wolfi-base/overview" title="Overview of wolfi-base image">wolfi-base</a> image in a Dockerfile and install what you need with <code>apk</code>, then use the resulting image as the base for your app.
For a worked example, see the <a href="/chainguard/containers/using-and-deploying/using-containers/#using-the-wolfi-base-container-image-within-dockerfiles">Using the wolfi-base container image within Dockerfiles</a> section of our <a href="/chainguard/containers/using-and-deploying/using-containers/" title="How to use Chainguard Containers">guide to using Chainguard Containers</a>.
</p>

<p>If the packages you need are not available, you can build your own apks using <a href="/open-source/build-tools/melange/">melange</a>. To get started, see <a href="/open-source/build-tools/melange/getting-started-with-melange/">Getting started with melange</a>.</p>


