Skip to content

Container Images

Reference documentation for the container image build system. This covers the Dockerfile hierarchy, base image contents, release configuration file formats, named build context patterns, constraint override tooling, and local build instructions.

Dockerfile Hierarchy

Container images follow a three-layer hierarchy. Each layer builds on the previous one, separating concerns between runtime base, build tooling, and service-specific code:

text
ubuntu:noble
├── python-base          Runtime base: Python 3.12, system libs, openstack user
│   ├── venv-builder     Build stage: compilers, uv, virtualenv with common packages
│   │   └── keystone     Stage 1 (build): install Keystone into virtualenv
│   └── keystone          Stage 2 (runtime): copy virtualenv, add runtime apt packages

The venv-builder image is used only as a build stage — it never runs in production. Service images (e.g., keystone) use a multi-stage build: stage 1 extends venv-builder to install the service, then stage 2 extends python-base and copies only the virtualenv from stage 1. This ensures the final image contains no build tools.

Base Images

python-base

Location: images/python-base/Dockerfile

The foundational runtime image for all OpenStack service containers.

PropertyValue
Base imageubuntu:noble (Ubuntu 24.04 LTS)
Python3.12 (from Ubuntu Noble package repository)
Useropenstack (UID 42424, GID 42424, shell /usr/sbin/nologin)
Home directory/var/lib/openstack

Environment variables:

VariableValuePurpose
PATH/var/lib/openstack/bin:$PATHEnsures virtualenv binaries take precedence
LANGC.UTF-8Consistent locale for Python string handling

Runtime packages:

PackagePurpose
ca-certificatesTLS certificate verification
netbase/etc/protocols and /etc/services for network operations
python3Python 3.12 runtime
sudoPrivilege escalation for entrypoint scripts
tzdataTimezone data for datetime operations

User convention: All service images share a single openstack user (UID/GID 42424) rather than creating per-service users. This is a deliberate deviation from the architecture document — see Design Deviations for rationale.

OCI labels: The Dockerfile includes static LABEL instructions for baseline OCI Image Spec annotations (title, description, licenses, vendor). These are always present on locally-built images. In CI, docker/metadata-action supplements these with dynamic labels (created, revision, source, url, version) — see Build Images Workflow — OCI Annotations.

venv-builder

Location: images/venv-builder/Dockerfile

Build-stage image that extends python-base with compilation tools and a prepared Python virtualenv. This image is never deployed — it exists only as a FROM target for multi-stage service builds.

PropertyValue
Base imagepython-base (local)
Package manageruv 0.6.3 (copied from ghcr.io/astral-sh/uv:0.6.3)
Virtualenv path/var/lib/openstack

Build-time packages:

PackagePurpose
build-essentialC compiler and make (for building Python C extensions)
gitFetching Python packages from git repositories
libffi-devcffi/cryptography compilation
libpq-devpsycopg2 compilation (PostgreSQL client)
libssl-devcryptography/pyOpenSSL compilation
python3-devPython headers for C extensions
python3-venvvenv module for virtualenv creation

Pre-installed common packages:

The virtualenv includes four packages shared by all OpenStack services:

PackagePurpose
cryptographyTLS, token encryption, Fernet keys
pymysqlMySQL/MariaDB database driver
python-memcachedMemcached client for caching
uwsgiWSGI application server

These packages are installed without the --constraint flag. The venv-builder image is release-independent — version constraints are release-specific and applied only in service Dockerfiles when installing the actual service.

OCI labels: Same static LABEL pattern as python-base — title, description, licenses, and vendor are embedded in the Dockerfile for local build visibility.

Service Images

keystone

Location: images/keystone/Dockerfile

The Keystone identity service image uses a two-stage build:

Stage 1 (build) — extends venv-builder:

  • Declares ARG PIP_EXTRAS and ARG PIP_PACKAGES for build-time injection of extras and additional packages from extra-packages.yaml (passed by the CI workflow)
  • Mounts upper-constraints.txt and the Keystone source tree via named build contexts
  • Installs Keystone with extras into the virtualenv using uv pip install --constraint

Stage 2 (runtime) — extends python-base:

  • Declares ARG EXTRA_APT_PACKAGES for build-time injection of runtime system packages from extra-packages.yaml (passed by the CI workflow)
  • Copies /var/lib/openstack from the build stage using COPY --from=build --link (the --link flag enables parallel layer extraction and deduplication)
  • Installs runtime system packages via apt-get install ${EXTRA_APT_PACKAGES}
  • Sets USER openstack for non-root execution

Runtime packages:

PackagePurpose
libapache2-mod-wsgi-py3Apache WSGI module for serving Keystone
libldap2LDAP client library (python-ldap runtime dependency)
libsasl2-2SASL authentication library (LDAP SASL bind support)
libxml2XML parsing library (lxml runtime dependency)

Final image properties:

  • Runs as openstack user (UID 42424, GID 42424)
  • Contains no build tools (gcc, python3-dev, build-essential, uv are absent)
  • Virtualenv at /var/lib/openstack with all Keystone dependencies
  • keystone-manage CLI available via PATH

OCI labels: The LABEL instruction is placed in Stage 2 (runtime) before the USER instruction. Labels added in Stage 1 (build) are discarded by Docker's multi-stage build process — only the runtime stage labels appear on the final image. In CI, docker/metadata-action overrides org.opencontainers.image.version with the upstream OpenStack release version from source-refs.yaml via a type=raw tag strategy.

Named Build Contexts

Service Dockerfiles use Docker's named build context feature (--build-context) to inject release-specific files without embedding them in the Dockerfile or using COPY from the build directory. This keeps Dockerfiles release-independent.

The Keystone build requires two named build contexts:

Context nameContentsMounted as
upper-constraintsRelease directory containing upper-constraints.txt/tmp/upper-constraints.txt
keystoneKeystone source tree (git checkout at the version from source-refs.yaml)/tmp/keystone

These are passed to docker build via --build-context flags:

bash
docker build images/keystone \
  --build-context keystone=src/keystone \
  --build-context upper-constraints=releases/2025.2/

Inside the Dockerfile, named build contexts are consumed via --mount=type=bind,from=. Extras are injected via ARG PIP_EXTRAS (comma-separated, e.g. ldap,oauth1) which the CI workflow reads from extra-packages.yaml:

dockerfile
ARG PIP_EXTRAS=""
ARG PIP_PACKAGES=""

RUN --mount=type=bind,from=upper-constraints,source=upper-constraints.txt,target=/tmp/upper-constraints.txt \
    --mount=type=bind,from=keystone,target=/tmp/keystone \
    PKG="/tmp/keystone" && \
    if [ -n "$PIP_EXTRAS" ]; then PKG="${PKG}[${PIP_EXTRAS}]"; fi && \
    uv pip install --prefix /var/lib/openstack \
        --constraint /tmp/upper-constraints.txt \
        "$PKG" $PIP_PACKAGES

The from=upper-constraints directive tells Docker to resolve the file from the named build context rather than the Dockerfile's primary build context. The source= parameter selects a specific file within that context.

Release Configuration

All release-specific configuration lives under releases/<release>/ (e.g., releases/2025.2/). These files are the single source of truth for what gets built. Adding a new service or updating a version requires editing only these files — not Dockerfiles.

source-refs.yaml

Location: releases/<release>/source-refs.yaml

Maps each OpenStack component to a git ref (tag, branch, or commit SHA) specifying the version to build.

Format:

yaml
# SPDX-FileCopyrightText: Copyright 2026 SAP SE or an SAP affiliate company
#
# SPDX-License-Identifier: Apache-2.0

keystone: "28.0.0"

Each key is a service name matching the Dockerfile directory under images/. Values are quoted strings representing git refs — typically release tags (e.g., "28.0.0").

To add a new service, add a single line: <service>: "<git-ref>".

upper-constraints.txt

Location: releases/<release>/upper-constraints.txt

Contains pinned Python dependency versions from the OpenStack requirements repository (stable/<release> branch). This file is committed as-is from the upstream repository to enable Renovate tracking and git diff for constraint changes.

Format:

text
cryptography===44.0.0
oslo.limit===2.8.0
keystonemiddleware===10.9.0

Each line pins a single package using the === (arbitrary equality) operator. This is the standard format used by OpenStack's global requirements process.

Source: https://raw.githubusercontent.com/openstack/requirements/stable/<release>/upper-constraints.txt

extra-packages.yaml

Location: releases/<release>/extra-packages.yaml

Defines per-service Python extras and runtime system packages that are not part of the core OpenStack package.

Format:

yaml
# SPDX-FileCopyrightText: Copyright 2026 SAP SE or an SAP affiliate company
#
# SPDX-License-Identifier: Apache-2.0

keystone:
  pip_extras:
    - ldap
    - oauth1
  pip_packages: []
  apt_packages:
    - libapache2-mod-wsgi-py3
    - libldap2
    - libsasl2-2
    - libxml2
KeyPurpose
<service>.pip_extrasBare Python extra names combined with the service name to form install arguments (e.g. keystone[ldap,oauth1]). Passed as the PIP_EXTRAS build arg.
<service>.pip_packagesAdditional pip packages to install alongside the service (space-separated in the build arg PIP_PACKAGES). Use an empty list ([]) when none are needed.
<service>.apt_packagesRuntime system packages installed via apt in the final image. Passed as the EXTRA_APT_PACKAGES build arg.

To add packages for a new service, add a new top-level key matching the service name with both pip_extras and apt_packages lists.

Constraint Overrides

The constraint override system allows selective modification of individual package pins in upper-constraints.txt without replacing the entire file. This is useful for applying security fixes or version bumps for individual packages.

Override Format

Override files are placed at overrides/<release>/constraints.txt. Each line is one of three types:

SyntaxActionExample
package===versionReplace the existing pin for packagecryptography===44.0.1
-packageRemove package from constraints entirely-oslo.messaging
# comment or blankSkipped (no action)# Security fix for CVE-2025-1234

Example override file (overrides/2025.2/constraints.txt):

text
# Security fix: bump cryptography for CVE-2025-1234
cryptography===44.0.1

# Remove oslo.messaging pin to allow newer version
-oslo.messaging

Script Usage

Location: scripts/apply-constraint-overrides.sh

bash
# Apply overrides for the 2025.2 release
./scripts/apply-constraint-overrides.sh 2025.2

Behavior:

ConditionResult
overrides/<release>/constraints.txt existsEach line is processed: replacements via sed, removals via sed -d
overrides/<release>/constraints.txt does not existScript exits with code 0, no changes made (idempotent)

The script reads releases/<release>/upper-constraints.txt relative to the current working directory (must be invoked from the repository root) and modifies it in-place. It uses GNU sed -i for modifications (default on Ubuntu/CI runners — BSD sed is not supported).

Arguments:

ArgumentRequiredDescription
<release>YesRelease identifier (e.g., 2025.2), used to locate overrides/<release>/constraints.txt

Local Build Instructions

Build the complete image chain locally for development and verification:

Step 1: Build base images

bash
# Build python-base (tag must match FROM python-base in downstream Dockerfiles)
docker build images/python-base -t python-base

# Build venv-builder (tag must match FROM venv-builder in keystone Stage 1)
docker build images/venv-builder -t venv-builder

The tag names (python-base, venv-builder) must match the FROM directives in downstream Dockerfiles. Docker resolves FROM python-base to the local image.

To also apply canonical registry tags, add a second -t flag:

bash
docker build images/python-base -t python-base -t c5c3/python-base:3.12-noble
docker build images/venv-builder -t venv-builder -t c5c3/venv-builder:3.12-noble

Step 2: Clone the service source

bash
git clone --branch 28.0.0 --depth 1 \
  https://github.com/openstack/keystone.git src/keystone

The branch/tag must match the version specified in releases/2025.2/source-refs.yaml.

Step 3: Build the service image

Extras are read from extra-packages.yaml and passed as --build-arg:

bash
docker build images/keystone \
  -t c5c3/keystone:28.0.0 \
  --build-arg PIP_EXTRAS=ldap,oauth1 \
  --build-arg "EXTRA_APT_PACKAGES=libapache2-mod-wsgi-py3 libldap2 libsasl2-2 libxml2" \
  --build-context keystone=src/keystone \
  --build-context upper-constraints=releases/2025.2/

Step 4: Verify the image

bash
# Verify Keystone CLI is functional
docker run --rm c5c3/keystone:28.0.0 keystone-manage --version

# Verify non-root execution
docker run --rm c5c3/keystone:28.0.0 whoami
# Expected output: openstack

# Verify no build tools in final image
docker run --rm c5c3/keystone:28.0.0 which gcc \
  && echo "FAIL: gcc found in image" \
  || echo "PASS: gcc not found"

Design Deviations

The implementation deviates from the architecture document (architecture/docs/08-container-images/01-build-pipeline.md) in one area, documented with # DEVIATION comments in the affected Dockerfiles:

Generic openstack user instead of per-service users:

The architecture document's Keystone Dockerfile example creates a per-service user (e.g., groupadd keystone / useradd keystone). The implementation uses a single generic openstack user (UID/GID 42424) defined in python-base and shared by all service images. This reduces complexity and image layers — each service image inherits the user via USER openstack without needing its own user creation step.

The # DEVIATION comment appears in both images/python-base/Dockerfile (where the user is created) and images/keystone/Dockerfile (where it is used instead of a per-service user).