Migrating to Node.js Chainguard Containers
Guidance on how to migrate Node.js Dockerfile workloads to use Chainguard Containers
This guide is a high-level overview for migrating an existing containerized Python application to Chainguard Containers.
Chainguard Containers are built on Wolfi, a distroless Linux distribution designed for security and a reduced attack surface. Chainguard Containers are smaller and have low to no CVE. Our Chainguard Containers for Python are built nightly for extra freshness, so they’re always up-to-date with the latest remediations.
Because Chainguard Containers aim to be minimal, adapting your containerized application requires that you consider some additional factors that will be discussed below.
We distribute two versions of our Python container image: a development image that includes shells such as ash/bash and package managers such as pip and a standard image that removes these tools for increased security. Our public standard images are tagged as latest
, while our public development images are tagged as latest-dev
.
When migrating your Python application , keep in mind these differences between the Chainguard Container for Python and the official Docker image.
/usr/bin/python
. When running either the latest
or latest-dev
versions of the image interactively, you’ll be working in the Python interpreter. When using CMD
in your Dockerfiles, provided commands will be passed to python
by default. If you change the path to include binaries from a virtual environment , you should manually set the entrypoint or your Dockerfile will continue to use the included system Python as the entrypoint and you will not have access to installed packages in the virtual environment.nonroot
user by default. If you need elevated permissions, such as to add packages with apk
, run the image as --user root
. You should not use the root user in a production scenario./home
and /home/nonroot
directories are owned by the nonroot user.python:latest
Chainguard Container intended for production does not include a sh
, ash
, or bash
. See the Debugging Distroless guide for advice on resolving issues without the use of these shells.python:latest
Chainguard Container does not contain package managers such as pip
or apk
. See the sections below for guidance on multi-stage builds (recommended)or building your own images on Wolfi (advanced usage).When migrating most containerized Python applications, we recommend building a virtual environment with any needed Python packages using our provided development images, then copying over the virtual environment to our stripped-down standard image. Chainguard Academy hosts detailed instructions for a multi-stage build for a CLI-based Python script.
The below Dockerfile provides an example of such a multi-stage build for a simple Flask application. You can view a version of this Dockerfile with included sample Flask application and requirements.txt
in this repository, and the original unmigrated application in the v0 branch. A more complex setup with reverse proxy orchestrated with Docker Compose is provided in the next section.
# syntax=docker/dockerfile:1
FROM cgr.dev/chainguard/python:latest-dev AS dev
WORKDIR /flask-app
RUN python -m venv venv
ENV PATH="/flask-app/venv/bin":$PATH
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
FROM cgr.dev/chainguard/python:latest
WORKDIR /flask-app
COPY app.py app.py
COPY --from=dev /flask-app/venv /flask-app/venv
ENV PATH="/flask-app/venv/bin:$PATH"
EXPOSE 8000
ENTRYPOINT ["python", "-m", "gunicorn", "-b", "0.0.0.0:8000", "app:app"]
When running an application containerized with the above Dockerfile, the application should be visible on 0.0.0.0:8000
.
As you can see, the primary difference in this Flask application compared to the pre-migration application is the use of a multistage build. In the initial stage, we copy our requirements into the development version of the Python Chainguard Image, initialize a virtual environment, and install needed packages with pip. In the second stage, we copy the virtual environment from the development image, copy the application from the host, set exposed port metadata, and run the application with the Gunicorn WSGI server.
By default, the entrypoint for the Python Chainguard Container is /usr/bin/python
rather than bash
. However, if you shadow the included system python
with the virtual environment python
on the path as we do above, you should set the entrypoint explicitly. Otherwise, you will not have access to the packages included in your virtual environment.
We recommend that you pin dependencies to specific versions in your own application. The example Flask application script linked above also enables debug mode, which should be turned off in a production scenario.
You may wish to include the following environmental variables in your Dockerfile. The first prevents the buffering of output, meaning that all messages printed to standard output are immediately printed rather than being held in a cache. The second prevents the creation of cached bytecode, which can marginally reduce image size.
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
We provide an nginx Chainguard Container, also with low to no CVEs, that can be used as a secure and performant reverse proxy to serve your application. You can view an example orchestration of a Flask application and nginx using Chainguard Containers at the linked repository. The compose.yml
file is provided as a reference below.
services:
flask-app:
build:
context: flask-app
restart: always
ports:
- 8000:8000
networks:
- backnet
- frontnet
nginx:
build: nginx
restart: always
ports:
- 80:80
depends_on:
- flask-app
networks:
- frontnet
networks:
backnet:
frontnet:
The backnet and frontnet networks are provided in anticipation of other backend services such as a database container. View the sample repository branch for a full orchestration example with nginx configuration.
If your project image requires a set of packages that cannot be installed with pip using the multi-stage approach above, you can consider building your application on the Wolfi base image and installing additional Python and non-Python packages as APKs.
You may wish to refer to the Python microservice example in the porting a sample application guide as an additional useful reference while migrating your application.
Debugging distroless containers can be a challenge given their lack of interactive tools such as shells. If you’re having difficulty debugging issues with your multi-stage build, you may find the Debugging Distroless guide a useful resource.
The following blog posts and videos may also assist with migrating your Python application:
Last updated: 2024-05-02 15:06