Skip to content

Container Images

Reference documentation for the container image build system (CC-0006). 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 (CC-0031): 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 (CC-0031): 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 (CC-0031): 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).