Getting Started with the Python Chainguard Image

Tutorial on the distroless Python Chainguard Image

The Python images based on Wolfi and maintained by Chainguard provide distroless images that are suitable for building and running Python workloads.

Chainguard offers both a minimal runtime image containing just Python, and a development image that contains a package manager and a shell. Because Python applications typically require the installation of third-party dependencies via the Python package installer pip, you may need to implement a multi-stage Docker build that uses the Python -dev image to set up the application.

In this guide, we’ll cover two examples to showcase Python container images based on Wolfi as a runtime. In the first, we’ll use the minimal image containing just Python (which has access to the Python standard library), and in the second we’ll demonstrate a multi-stage build.

What is distroless Distroless images are minimalist 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.
Chainguard Images Chainguard Images are a mix of distroless and development images based on Wolfi. Nightly builds make sure images are up-to-date with the latest package versions and patches from upstream Wolfi.

Example 1 — Minimal Python Chainguard Image

In this example, we’ll build and run a distroless Python Chainguard Image in a single-stage build process. We’ll first make a demonstration app and then build and run it.

Step 1: Setting up a Demo Application

We’ll start by creating a basic command-line Python application to serve as a demo. This app will generate random octopus facts based on a list in a text file. This app will use the random module from the Python standard library.

First, create a directory for your app. You can use any meaningful name and path for you, our example will use octo-facts/.

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

Create a new file to serve as the application entry point. We’ll use main.py. You can edit this file in whatever code editor you would like. We’ll use Nano as an example.

nano main.py

The following Python script defines a light CLI app that takes in a text file, octo-facts.txt, and returns a random line from that file.

'''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()

Copy this code to your main.py script, save and close the file.

Next, 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.txt

At this point, you can run the script and be sure you are satisfied with the functionality. It is recommended that you use a Python programming environment. Ensure whether you will be using the python or python3 command.

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, we’ll only use one FROM line in our Dockerfile. Our resulting image will be based on the distroless Python Wolfi image, which means it doesn’t come with a package manager or even a shell.

We’ll begin by creating a Dockerfile. Again, you can use any code editor of your choice, we’ll use Nano for demonstation purposes.

nano Dockerfile

The following Dockerfile will:

  1. Start a build stage based on the python:latest image;
  2. Declare the working directory;
  3. Copy the script and the text file that’s being read;
  4. Set up the application as entry point for this image.
FROM cgr.dev/chainguard/python:latest

WORKDIR /octo-facts

COPY main.py facts.txt ./

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

Save the file when you’re finished.

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

docker build . -t octo-facts

Once the build is finished, run the image.

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 Image. At this point, you can continue to the multi-stage example or advanced usage.

Example 2 — Multi-Stage Build for Python Chainguard Image

In this example, we’ll build and run a multi-stage Python Chainguard Image. We’ll have a build image that includes pip and a shell before creating a final distroless image without these development tools for production.

Step 1: Setting up a Demo Application

We’ll start by creating a Python application that will take in an image file and convert 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, our example will use inky/.

mkdir ~/inky/ && cd $_

We’ll first write out the requirements for our app in a new file, for example we named our file requirements.txt. You can edit this file in your preferred code editor, in our case we will use Nano.

nano requirements.txt

We’ll use version 68.2.2 of Python setuptools and also install climage. We need to use a slightly older version of setuptools for compatability with climage. Add the following text to the file:

setuptools==68.2.2
climage==0.2.0

Save the file and we will next create a new file with our python code called inky.py. You can edit this file in whatever code editor you would like. We’ll use Nano as an example.

nano inky.py

Add the following Python code which defines a CLI app that takes in an image file, inky.png, and prints a representation of that file to the terminal:

'''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('inky.png', is_unicode=True)
    print(output)

if __name__ == "__main__":
    main()

Next, pull down the inky.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 inky.py script is.

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

If you have python and pip installed in your local environment, you can now install the dependencies with pip and run our program. Don’t worry if you don’t have python installed, you can simply skip this step and move onto the Dockerfile.

pip install -r requirements.txt
python inky.py

You’ll receive a representation of the Chainguard Inky logo on the command line. With your demo application ready, you’re ready to move onto the container stage.

Step 2: Creating the Dockerfile

To make sure our final image is distroless while still being able to install dependencies with pip, our build will consist of two stages: first, we’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, we’ll create a separate stage for the final image. The resulting image will be based on the distroless Python Wolfi image, which means it doesn’t come with pip or even a shell.

Begin by editing a Dockerfile, with Nano for instance.

nano Dockerfile

The following Dockerfile will:

  1. Start a new build stage based on the python:latest-dev image and call it builder;
  2. Create a new virtual environment to cleanly hold the application’s dependencies;
  3. Copy requirements.txt from the current directory to the /inky location in the container;
  4. Run pip install --no-cache-dir -r requirements.txt to install dependencies;
  5. Start a new build stage based on the python:latest image;
  6. Copy the dependencies in the virtual environment from the builder stage, and the source code from the current directory;
  7. Set up the application as the entry point for this image.

Copy this configuration to your own Dockerfile:

FROM cgr.dev/chainguard/python:latest-dev as builder

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

WORKDIR /inky

RUN python -m venv /inky/venv
COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

FROM cgr.dev/chainguard/python:latest

WORKDIR /inky

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

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

ENTRYPOINT [ "python", "/inky/inky.py" ]

Save the file when you’re finished.

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

docker build -t inky .

Once the build is finished, run the image with:

docker run --rm inky

And you should get output similar to what you got before, with a printed Inky 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 Image, 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 an image other than the wolfi-base image, the image will run as a non-root user. Because of this, if you need to install packages with apk install 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 base for your app. Check the "Using the wolfi-base Image" section of our images quickstart guide for more information.

If the packages you need are not available, you can build your own apks using melange. Please refer to this guide for more information.

Last updated: 2023-11-02 13:46