SKA Base Images
This repository builds the SKAO base-image catalogue used by application images, CI jobs, static webserver deployments, and CUDA workloads.
The repository model is:
slim, single-version runtime images for deployable Python and Node applications
multi-version build images for CI and test matrices
generated and committed Dockerfiles driven by
versions.yamlone root
.releaseshared by every image in the catalogueCI builds the full image DAG per Ubuntu-base subtree in sequenced stages, passing parent images between jobs with
BASE_IMAGEand reusing layers through a BuildKit registry cache
Runtime And Build Model
versions.yaml is the source of truth for Ubuntu bases, Python and Node
versions, tool pins, parent relationships, and test expectations. The generated
Dockerfiles are committed so a user can inspect and build a normal Dockerfile
without running the generator first.
---
config:
layout: dagre
theme: mc
---
flowchart LR
V["versions.yaml<br/>(single source of truth)"] --> GEN["scripts/apply_templates.py<br/>orchestrates render + drift-check<br/>(generate_templates · generate_ci · template_models)"]
T["templates/*.Dockerfile.j2"] --> GEN
GEN --> DF["committed Dockerfiles<br/>images/*/Dockerfile"]
GEN --> CI[".gitlab/images.generated.yml<br/>build + test DAG"]
R["root .release<br/>one catalogue version"] --> GEN
DF --> RT["slim runtime images<br/>ska-python-py313 · ska-node-ubuntu24 · ..."]
DF --> BLD["multi-version build images<br/>ska-build-python · ska-build-node"]
CI --> BUILD["CI builds and tests<br/>amd64 + arm64"]
classDef source stroke:#6d28d9,stroke-width:2px,fill:#ede9fe,color:#3b0764
classDef generated stroke:#1d4ed8,stroke-width:2px,fill:#dbeafe,color:#1e3a8a
classDef runtime stroke:#15803d,stroke-width:2px,fill:#dcfce7,color:#14532d
classDef build stroke:#c2410c,stroke-width:2px,fill:#ffedd5,color:#7c2d12
class V,T,R source
class GEN,DF,CI generated
class RT runtime
class BLD,BUILD build
Image Families
Family |
Images |
Purpose |
|---|---|---|
Ubuntu base |
|
CIS hardened Ubuntu bases |
Python runtime |
|
Slim deploy images with one Python runtime |
Python build |
|
CI/build images with the native Python and managed extra Python versions |
Node runtime |
|
Slim deploy images with Node 22 plus npm, yarn, and pnpm, one per Ubuntu base |
Node build |
|
CI/build images with Node 22 plus npm, yarn, pnpm, and browser test dependencies, one per Ubuntu base |
CI / deploy toolbox |
|
CI/deploy image replacing the deprecated |
Utility |
|
Kubernetes utility image and Nginx static webserver image (both on Ubuntu 26.04) |
CUDA |
|
NVIDIA CUDA runtime and build images |
The current runtime matrix is:
Ubuntu base |
Python runtime images |
Python build image |
|---|---|---|
22.04 |
|
|
24.04 |
|
|
26.04 |
|
|
Ubuntu base |
Node runtime image |
Node build image |
|---|---|---|
22.04 |
|
|
24.04 |
|
|
26.04 |
|
|
Image Build Graph
The generated build graph follows the parent relationships in versions.yaml.
Changing a parent image causes every descendant to rebuild and test in CI.
---
config:
layout: dagre
theme: mc
---
flowchart TB
U22["ubuntu:22.04"] --> B22["ska-base"]
U24["ubuntu:24.04"] --> B24["ska-base-ubuntu24"]
U26["ubuntu:26.04"] --> B26["ska-base-ubuntu26"]
B22 --> P22["ska-python<br/>3.10"]
B22 --> P312["ska-python-py312"]
B22 --> P313["ska-python-py313"]
B22 --> P314["ska-python-py314"]
B22 --> BP22["ska-build-python"]
B22 --> N22["ska-node"]
B22 --> BN22["ska-build-node"]
B22 --> BD22["ska-cicd-runner"]
B24 --> P24["ska-python-ubuntu24"]
B24 --> P24_313["ska-python-ubuntu24-py313"]
B24 --> P24_314["ska-python-ubuntu24-py314"]
B24 --> BP24["ska-build-python-ubuntu24"]
B24 --> N24["ska-node-ubuntu24"]
B24 --> BN24["ska-build-node-ubuntu24"]
B26 --> P26["ska-python-ubuntu26"]
B26 --> BP26["ska-build-python-ubuntu26"]
B26 --> N26["ska-node-ubuntu26"]
B26 --> BN26["ska-build-node-ubuntu26"]
B26 --> SU["ska-ser-utils"]
P26 --> W["ska-webserver"]
classDef upstream stroke:#4b5563,stroke-width:2px,fill:#e5e7eb,color:#111827
classDef base stroke:#6d28d9,stroke-width:2px,fill:#ede9fe,color:#3b0764
classDef runtime stroke:#15803d,stroke-width:2px,fill:#dcfce7,color:#14532d
classDef build stroke:#c2410c,stroke-width:2px,fill:#ffedd5,color:#7c2d12
class U22,U24,U26 upstream
class B22,B24,B26 base
class P22,P312,P313,P314,N22,N24,N26,SU,W,P24,P24_313,P24_314,P26 runtime
class BP22,BN22,BN24,BN26,BP24,BP26,BD22 build
CUDA images are hand-written because they follow NVIDIA-published CUDA/Ubuntu pairs rather than the generated language-runtime matrix.
---
config:
layout: dagre
theme: mc
---
flowchart TB
C12["nvidia/cuda:12.9.1-runtime-ubuntu22.04"] --> R12["ska-cuda"]
D12["nvidia/cuda:12.9.1-cudnn-devel-ubuntu22.04"] --> B12["ska-build-cuda"]
C13["nvidia/cuda:13.2.0-runtime-ubuntu24.04"] --> R13["ska-cuda-ubuntu24"]
D13["nvidia/cuda:13.2.0-cudnn-devel-ubuntu24.04"] --> B13["ska-build-cuda-ubuntu24"]
C11["nvidia/cuda:11.8.0-devel-ubuntu22.04"] --> B11["ska-build-cuda-11"]
classDef upstream stroke:#4b5563,stroke-width:2px,fill:#e5e7eb,color:#111827
classDef runtime stroke:#15803d,stroke-width:2px,fill:#dcfce7,color:#14532d
classDef build stroke:#c2410c,stroke-width:2px,fill:#ffedd5,color:#7c2d12
class C12,D12,C13,D13,C11 upstream
class R12,R13 runtime
class B12,B13,B11 build
Generated Image Definitions
Language and utility Dockerfiles are generated from versions.yaml and
templates/*.Dockerfile.j2. The generator writes:
images/*/Dockerfile.gitlab/images.generated.yml
The generator is split across focused modules that scripts/apply_templates.py
orchestrates:
scripts/template_models.py— parses and validatesversions.yamlscripts/generate_templates.py— renders the per-image Dockerfilesscripts/generate_ci.py— renders the CI build/test DAGscripts/apply_templates.py— the entry point that ties them together and runs the drift check
To update runtime or tool versions, edit versions.yaml, regenerate, and
commit the rendered files:
uv run python scripts/apply_templates.py
uv run python scripts/apply_templates.py --check
CI runs the check form to catch template drift.
The generated CI DAG groups image builds by Ubuntu-base subtree and runs those
groups one at a time in dedicated stages (build-ubuntu22, build-ubuntu24,
build-ubuntu26, build-cuda). Serialising the groups keeps peak load bounded
so the QEMU-emulated arm64 leg does not exhaust the runner. Within a group each
base build exports a BuildKit registry cache (--cache-to) that its
descendants consume (--cache-from) to reuse warm layers. The cache is written
to a dedicated buildcache-<sha> tag, kept separate from the published image
tag so that plain docker pull in the scan/compliance jobs is unaffected.
Generated Dockerfiles keep ARG BASE_IMAGE=<ref> defaults set to the current
root release. versions.yaml supports an optional parent_refs map to pin a
parent to an exact published ref instead of the release-derived default (useful
when a parent is not yet published for the release being built); it is currently
commented out, so all images default to the root release. CI overrides
BASE_IMAGE for child jobs so descendants can build from parent images produced
earlier in the same pipeline.
All images use the root .release file. Per-image .release files are not
used.
Provenance and image labels
Image provenance is recorded in three complementary layers. The previous
approach of hand-injecting int.skao.image.version/created/tags labels from
the Makefile and writing a skao.metadata side-file has been removed.
OCI standard annotations — committed in every Dockerfile. Each image
carries the cross-ecosystem org.opencontainers.image.* labels that
registries, vulnerability scanners, and Docker Scout read. Static fields are
baked in; version/revision come from build args the build passes (empty on
a bare docker build). Generated images share templates/_provenance.j2; the
hand-written CUDA Dockerfiles carry the same block inline.
Label |
Value |
|---|---|
|
image name |
|
short description |
|
|
|
|
|
repository URLs |
|
the |
|
|
|
|
org.opencontainers.image.created is intentionally not baked in — a build
timestamp would make layers non-reproducible. Build time is captured by
CI_COMMIT_TIMESTAMP (below) and by buildx attestations.
SKA identity labels — committed in every Dockerfile. Kept for SKA tooling:
int.skao.image.team, int.skao.image.url, int.skao.image.source
(images/<image>/Dockerfile), and int.skao.image.baseImage (the BASE_IMAGE
arg).
Dynamic build provenance — added at build time by .make. make oci-build
(the target every CI job runs) attaches version and CI-context labels
automatically via the SKA OCI support, so nothing is set per image:
Label |
Source |
|---|---|
|
catalogue version from the root |
|
published tag ( |
|
short commit SHA |
|
GitLab commit metadata |
|
GitLab build context |
These replace the old int.skao.image.version/tags/created labels
(→ SKA_BUILD_VERSION/SKA_BUILD_RELEASE, SKA_BUILD_VERSION, and
CI_COMMIT_TIMESTAMP respectively).
Build attestations. Pipeline builds attach SLSA build provenance and an
SBOM to each pushed image index (buildx --provenance=mode=max --sbom=true).
These are signed build evidence (in-toto format) rather than metadata; inspect
them with docker buildx imagetools inspect <ref>, and they are consumed by
vulnerability scanners and policy engines.
Building Images
Build every image through the .make OCI targets:
make oci-build-all
Build a single generated image locally from its committed Dockerfile:
cd images/ska-python
docker build .
To build a child image from a locally produced or CI-produced parent, override
BASE_IMAGE:
docker build --build-arg BASE_IMAGE=registry.example/ska-base:dev .
Maintenance Scripts
Two maintenance scripts share scripts/config.json as their configuration
source:
scripts/bump_base_images.py— clones the downstreamrepositorieslisted in the config and patches their Dockerfiles to the latest publishedbase_imagestags.scripts/deprecate_harbor_base_images.py— movesbase_imagesartefacts older thandeprecation.timedelta_daysfrom the source Harbor project to the deprecated project (Harbor URL, projects, and robot-account credentials are supplied via environment variables). Run from the repository root, as the scheduleddeprecate-imagesCI job does.
scripts/config.json holds:
base_images— the catalogue images shared by both scriptsrepositories— downstream repos patched by the bump scriptdeprecation.timedelta_days— the age threshold for deprecation
ska-webserver
The ska-webserver differs from the other images as it is offering a tailored solution for teams to easily create OCI images to serve static web content while being able to proxy calls to backend APIs using best-practices, much like what is provided by NextJS and similar solutions. The main goal is to avoid having teams crafting their own nginx.conf files, while giving them an easy-to-setup yet configurable image.
The Nginx configuration is done by templating the jinja2 template with a custom configuration. The configuration context is created by providing one or more YAML configuration files, and mounting them to ${NGINX_ENVSUBST_TEMPLATE_DIR}/conf.d/. The configuration files will be loaded in alphabetical order and merged together. Note that as it is jinja2 based, we also template the configuration itself, meaning we can have variables in configuration files referring to itself.
The default configuration contains the full spec with examples, commenting what is not necessary as a default.
An example configuration file could be:
config:
log_level: debug
source_env:
- name: NAMESPACE
default: some-namespace
- name: BACKEND_URL
default: http://some-backend.some-namespace.svc
disable_default_locations: true
before_snippets:
- |
location ^~ /{{ env.NAMESPACE }}/static {
alias {{ config.root }}/static/$1;
expires {{ config.static_file_cache_expiration }};
add_header Pragma public;
add_header Cache-Control "public";
try_files $uri $uri/ =404;
}
locations:
- location: ~* ^/{{ env.NAMESPACE }}/api(.*)$
proxy_pass: "{{ env.BACKEND_URL }}/api$1$is_args$args"
proxy_read_timeout: 120s
- location: ~* ^/{{ env.NAMESPACE }}/another/api(.*)$
proxy_pass: "{{ env.BACKEND_URL }}/api$1$is_args$args"
proxy_buffering: 'on'
Most notably, through proper configuration, this image allows:
Using references in the configuration to other variables in the configuration (recursive, has some limitations) using
{{ config.<variable> }}
before_snippets:
- |
location ^~ /static {
alias {{ config.root }}/static/$1;
expires {{ config.static_file_cache_expiration }};
add_header Pragma public;
add_header Cache-Control "public";
try_files $uri $uri/ =404;
}
Capability of “importing” environment variables and giving them a default value
source_env:
- name: NAMESPACE
default: some-namespace
- name: BACKEND_URL
default: http://some-backend.some-namespace.svc
locations:
- location: ~* ^/{{ env.NAMESPACE }}/api(.*)$
proxy_pass: "{{ env.BACKEND_URL }}/api$1$is_args$args"
Any number of locations with configurable
locationexpression,proxy_passand other proxy configurationsAdd snippets to Nginx configuration before the defaults and locations with
before_snippetsand after withafter_snippets
If you want to test what the nginx configuration would look like, you can:
mkdir -p /tmp/ska-webserver-configs
cp images/ska-webserver/resources/conf.yml /tmp/ska-webserver-configs/0-conf.yml
# Create any number of configuration files
vi /tmp/ska-webserver-configs/[...].yml
python3 images/ska-webserver/resources/generate.py -i images/ska-webserver/resources/nginx.conf.j2 -c /tmp/ska-webserver-configs -o /tmp/nginx.conf
cat /tmp/nginx.conf
Other dynamic procedures
As this image installs Nginx from native sources, we also carried the docker-entrypoint capabilities the official Nginx images have. If you wish to do other dynamic procedures that do not involve generating the nginx.conf file, you can mount any bash script (note the alphabetical order of execution) to /docker-entrypoint.d.
A common use-case for this is to replace in the static HTML, JS and CSS files, paths to other resources by including the namespace of the deployment as a prefix.
CUDA Images
These images are not built using ska-base; they are based directly on NVIDIA CUDA images. The CUDA runtime and build images are maintained manually because their supported Ubuntu/CUDA combinations are determined by NVIDIA.
Deprecations and removals
The
ska-buildimage was removed in favor of having language/platform specific base images
Beta images
All images based on the Ubuntu 24.04 and Ubuntu 26.04 base images are currently in beta and won’t be supported by the System Team. We do welcome any bug reports on their usage, but they are not guaranteed to be stable or production-ready.
Development
This repository uses uv for dependency management and Ruff for linting and formatting. (These replace the previous Poetry + black/isort/flake8/pylint toolchain.)
Install uv (see the uv installation docs), then set up the environment from the lockfile:
uv sync --all-groups
Lint and format the Python sources (Ruff configuration lives in pyproject.toml):
uv run ruff format . # apply formatting
uv run ruff check . # lint
make python-lint # format --check + lint (the CI entrypoint)
Build the documentation locally:
uv sync --group docs
uv run sphinx-build -b html docs/src docs/_build/html