# Getting started with the Python Chainguard Container

URL: https://deploy-preview-4014--ornate-narwhal-088216.netlify.app/chainguard/containers/getting-started/languages-and-runtimes/python.md
Last Modified: July 23, 2025
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&rsquo;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 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. 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 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?Multi-stage builds are a Docker feature that allow you to use multiple FROM 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.
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.
Chainguard Containers are designed with this pattern in mind. Most have a :latest-dev development variant suited for use as a builder stage, and a corresponding :latest (or -slim) standard image for the distroless runtime. For example, a Go application can be compiled in the go:latest-dev builder stage and its binary copied into a static or glibc-dynamic runtime image — with no Go toolchain in the final container.
Chainguard ContainersChainguard Containers are 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&rsquo;ll build and run a distroless Python Chainguard Container in a single-stage build process. You&rsquo;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/.
mkdir ~/octo-facts/ &amp;&amp; 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.
cat &gt; main.py &lt;&lt;&#39;EOF&#39; &#39;&#39;&#39;Import random module to implement random.choice() function&#39;&#39;&#39; import random def random_line(text): &#39;&#39;&#39;Opens and reads lines of a UTF-8 encoded file, returning a random line&#39;&#39;&#39; with open(text, &#39;r&#39;, encoding=&#39;UTF-8&#39;) as file: line = file.readlines() return random.choice(line) def main(): &#39;&#39;&#39;Prints random line from facts.txt; verify your path&#39;&#39;&#39; print(random_line(&#39;facts.txt&#39;)) if __name__ == &#34;__main__&#34;: main() EOFNext, pull down the facts.txt file with curl. Inspect the URL 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.
curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/octo-facts/facts.txtAt 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.
python main.pyYou 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&rsquo;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:
Starts a build stage based on the python:latest image; Declares the working directory; Copies the script and the text file that&rsquo;s being read; Sets up the application as entry point for this container. cat &gt; Dockerfile &lt;&lt;&#39;EOF&#39; FROM cgr.dev/chainguard/python:latest WORKDIR /octo-facts COPY main.py facts.txt ./ ENTRYPOINT [ &#34;python&#34;, &#34;/octo-facts/main.py&#34; ] EOFYou can now build the container image. If you receive an error, try again with sudo.
docker build . --pull -t octo-factsOnce the build is finished, run the container.
docker run --rm octo-factsAnd 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 or advanced usage.
Example 2 — Multi-stage build for Python Chainguard Container In this example, you&rsquo;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/.
mkdir ~/linky/ &amp;&amp; cd $_First, write out the requirements for your app in a file named requirements.txt. This installs version 0.2.2 of climage, which converts images into ANSI escape sequences:
cat &gt; requirements.txt &lt;&lt;&#39;EOF&#39; climage==0.2.2 EOFCreate 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:
cat &gt; linky.py &lt;&lt;&#39;EOF&#39; &#39;&#39;&#39;import climage module to display images on terminal&#39;&#39;&#39; from climage import convert def main(): &#39;&#39;&#39;Take in PNG and output as ANSI to terminal&#39;&#39;&#39; output = convert(&#39;linky.png&#39;, is_unicode=True) print(output) if __name__ == &#34;__main__&#34;: main() EOFNext, pull down the linky.png image file with curl. Inspect the URL 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.
curl -O https://raw.githubusercontent.com/chainguard-dev/edu-images-demos/main/python/linky/linky.pngWith 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:
Starts a new build stage based on the python:latest-dev container image and calls it builder; Creates a new virtual environment to cleanly hold the application&rsquo;s dependencies, using --without-pip to keep pip out of the environment and therefore out of the final image; Copies requirements.txt from the current directory to the /linky location in the container; Runs pip --python /linky/venv/bin/python install --no-cache-dir -r requirements.txt to install dependencies, where --python points the builder&rsquo;s own pip at the virtual environment; Starts a new build stage based on the python:latest image; Copies the dependencies in the virtual environment from the builder stage, and the source code from the current directory; Sets up the application as the entry point for this container. Write this configuration to your own Dockerfile:
cat &gt; Dockerfile &lt;&lt;&#39;EOF&#39; FROM cgr.dev/chainguard/python:latest-dev AS builder ENV LANG=C.UTF-8 ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 ENV PATH=&#34;/linky/venv/bin:$PATH&#34; 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=&#34;/venv/bin:$PATH&#34; COPY linky.py linky.png ./ COPY --from=builder /linky/venv /venv ENTRYPOINT [ &#34;python&#34;, &#34;/linky/linky.py&#34; ] EOFYou can now build the container image. If you receive a permission error, try running under sudo.
docker build . --pull -t linkyOnce the build is finished, run the image with:
docker run --rm linkyAnd you should get output similar to what you got before, with a printed Linky on the command line.
Advanced usage 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 wolfi-os repository. Note: 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 apk add you need to use the USER root directive.
If the package is available, you can use the wolfi-base image in a Dockerfile and install what you need with apk, then use the resulting image as the base for your app. For a worked example, see the Using the wolfi-base container image within Dockerfiles section of our guide to using Chainguard Containers. If the packages you need are not available, you can build your own apks using melange. To get started, see Getting started with melange.

