From 4ddd380657a067253d2e46ea85f3a6ead8038c86 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra <4995967+baszalmstra@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:30:11 +0200 Subject: [PATCH 1/5] feat: add a minimal runtime image --- .github/workflows/runtime.yml | 97 +++++++++++++++++++++++++++++++++++ Dockerfile.runtime | 52 +++++++++++++++++++ README.md | 51 ++++++++++++++++-- example/Dockerfile | 4 +- tests/runtime.py | 68 ++++++++++++++++++++++++ 5 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/runtime.yml create mode 100644 Dockerfile.runtime create mode 100644 tests/runtime.py diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml new file mode 100644 index 0000000..ffec8b6 --- /dev/null +++ b/.github/workflows/runtime.yml @@ -0,0 +1,97 @@ +name: Runtime + +on: + push: + branches: [main] + paths: + - Dockerfile.runtime + - .github/workflows/runtime.yml + - example/** + - tests/runtime.py + pull_request: + paths: + - Dockerfile.runtime + - .github/workflows/runtime.yml + - example/** + - tests/runtime.py + +concurrency: + group: runtime-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + architecture: amd64 + runner: ubuntu-latest + - platform: linux/arm64 + architecture: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Build runtime and example + env: + PLATFORM: ${{ matrix.platform }} + run: | + docker build --platform="$PLATFORM" -f Dockerfile.runtime -t pixi-runtime:test \ + --label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \ + --label "org.opencontainers.image.revision=$GITHUB_SHA" . + docker build --platform="$PLATFORM" --build-arg RUNTIME_IMAGE=pixi-runtime:test \ + -f example/Dockerfile -t pixi-runtime:example example + - name: Test copied environment and provenance + run: | + docker run --rm --read-only --tmpfs /tmp --user=65532:65532 \ + --mount "type=bind,source=$PWD/tests/runtime.py,target=/runtime.py,readonly" \ + pixi-runtime:example python /runtime.py + - name: Export tested runtime + env: + ARCHITECTURE: ${{ matrix.architecture }} + run: docker save --output "runtime-$ARCHITECTURE.tar" pixi-runtime:test + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: runtime-${{ matrix.architecture }} + path: runtime-${{ matrix.architecture }}.tar + if-no-files-found: error + + publish: + needs: test + if: github.event_name == 'push' && github.ref_name == 'main' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + pattern: runtime-* + path: images + merge-multiple: true + - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Publish tested platform images and manifest + env: + IMAGE: ghcr.io/prefix-dev/pixi + run: | + for architecture in amd64 arm64; do + docker load --input "images/runtime-$architecture.tar" + tag="$IMAGE:runtime-$GITHUB_SHA-$architecture" + docker tag pixi-runtime:test "$tag" + docker push "$tag" + done + docker buildx imagetools create \ + --tag "$IMAGE:runtime-$GITHUB_SHA" \ + --tag "$IMAGE:runtime" \ + "$IMAGE:runtime-$GITHUB_SHA-amd64" \ + "$IMAGE:runtime-$GITHUB_SHA-arm64" diff --git a/Dockerfile.runtime b/Dockerfile.runtime new file mode 100644 index 0000000..0061a41 --- /dev/null +++ b/Dockerfile.runtime @@ -0,0 +1,52 @@ +FROM cgr.dev/chainguard/wolfi-base@sha256:918a593b8268c222afd4e2c4f06860ac984e60719b4697e4c71d796bc8fcd042 AS source + +RUN apk add --no-cache \ + bash=5.3-r13 \ + ncurses=6.6.20260905-r0 \ + ncurses-terminfo-base=6.6.20260905-r0 + +# Extract runtime files and retain the source package SBOM for each component. +# The SBOMs describe upstream packages, not complete packages installed here. +RUN set -eu; \ + mkdir -p /rootfs/bin /rootfs/lib /rootfs/lib64 /rootfs/usr/lib /rootfs/etc /rootfs/tmp \ + /rootfs/usr/share/pixi-runtime/sbom; \ + chmod 1777 /rootfs/tmp; \ + copy_runtime_file() { \ + cp -L "$1" "$2"; \ + package="$(apk info --who-owns "$(readlink -f "$1")")"; \ + package="${package##* }"; \ + cp "/var/lib/db/sbom/${package}.spdx.json" /rootfs/usr/share/pixi-runtime/sbom/; \ + printf '%s\t%s\t%s\n' "${2#/rootfs}" "$1" "$package" \ + >> /rootfs/usr/share/pixi-runtime/origins.tsv; \ + }; \ + apk --repositories-file /dev/null info -L glibc ld-linux > /tmp/glibc-files; \ + while IFS= read -r file; do \ + case "$file" in \ + usr/lib/*/*.so*) ;; \ + usr/lib/*.so*) copy_runtime_file "/$file" "/rootfs/lib/${file##*/}" ;; \ + esac; \ + done < /tmp/glibc-files; \ + for loader in /rootfs/lib/ld-linux-*.so.*; do \ + test -f "$loader"; \ + ln -s "/lib/${loader##*/}" "/rootfs/lib64/${loader##*/}"; \ + done; \ + find /usr/lib/locale -type f > /tmp/locale-files; \ + while IFS= read -r file; do \ + mkdir -p "/rootfs${file%/*}"; \ + copy_runtime_file "$file" "/rootfs$file"; \ + done < /tmp/locale-files; \ + for file in passwd group nsswitch.conf; do \ + copy_runtime_file "/etc/$file" "/rootfs/etc/$file"; \ + done; \ + copy_runtime_file /usr/bin/bash /rootfs/bin/bash; \ + copy_runtime_file /usr/lib/libtinfo.so.6 /rootfs/lib/libtinfo.so.6; \ + copy_runtime_file /usr/lib/libgcc_s.so.1 /rootfs/lib/libgcc_s.so.1; \ + cd /rootfs; \ + find bin lib usr/lib etc -type f -exec sha256sum {} + > /tmp/runtime-checksums; \ + LC_ALL=C sort /tmp/runtime-checksums > usr/share/pixi-runtime/files.sha256 + +FROM scratch +COPY --from=source /rootfs/ / +ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +ENV LANG=C.UTF-8 +CMD ["/bin/bash"] diff --git a/README.md b/README.md index d982a89..cd054ad 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ This repository contains the docker configuration for the pixi container image. The pixi container image is based on different base images, depending on the use case. -All images have pixi installed in `/usr/local/bin/pixi` and are ready to use. +The Ubuntu, Debian and CUDA images have pixi installed in `/usr/local/bin/pixi`. +The `runtime` image contains only the system libraries and Bash needed to run a copied environment. ## Pulling the images @@ -20,12 +21,13 @@ There are different tags for different base images available: - `bullseye` - based on `debian:bullseye` - `noble-cuda-12.9.1` - based on `nvidia/cuda:12.9.1-base-ubuntu24.04` - `noble-cuda-13.0.0` - based on `nvidia/cuda:13.0.0-base-ubuntu24.04` +- `runtime` - a runtime-only image built from selected Wolfi files, without Pixi - ... and more ## Usage with shell-hook The following example uses the pixi docker image as a base image for a multi-stage build. -It also makes use of the `shell-hook` feature of pixi to define a convenient entry point (after executing the `shell-hook` script, the environment is activated. +The `shell-hook` entrypoint activates the environment before executing the container command. ```Dockerfile FROM ghcr.io/prefix-dev/pixi:0.40.0 AS build @@ -44,7 +46,7 @@ RUN pixi shell-hook -e prod > /shell-hook.sh # extend the shell-hook script to run the command passed to the container RUN echo 'exec "$@"' >> /shell-hook.sh -FROM ubuntu:24.04 AS production +FROM ghcr.io/prefix-dev/pixi:runtime AS production # only copy the production environment into prod container # please note that the "prefix" (path) needs to stay the same as in the build container @@ -60,10 +62,53 @@ ENTRYPOINT ["/bin/bash", "/shell-hook.sh"] CMD ["start-server"] ``` +Copy any application files and activation scripts referenced by the shell hook as well. +The runtime image does not contain system utilities; see its requirements below. + ## Images There are images based on `ubuntu`, `debian` and `nvidia/cuda` available. +### Minimal runtime + +`ghcr.io/prefix-dev/pixi:runtime` is built from [`Dockerfile.runtime`](Dockerfile.runtime) +for `linux/amd64` and `linux/arm64`. It contains glibc and its loader, UTF-8 locale +data, Bash, `libtinfo`, `libgcc_s`, and user/group and name-service configuration. +It supports running as UID/GID `65532:65532`. + +This image is for the final stage of a multi-stage build, not for installing environments. +It contains no Pixi, package manager, coreutils, `/bin/sh`, or system CA bundle. +Bash is included for non-interactive shell hooks; terminal descriptions (terminfo) +are not included. Add terminal data to the environment if the application needs it. +Include the application's other dependencies, including `ca-certificates` for HTTPS, +in the Pixi environment. Some clients need `SSL_CERT_FILE` pointed at the environment's +`ssl/cacert.pem`. Use an Ubuntu or Debian base when the application or its activation +scripts require system utilities or `/bin/sh`. + +Runtime images are published on changes to the runtime build, separately from Pixi +releases. The tags are `runtime` and `runtime-`. Pin an image digest +for deployments. + +To build the runtime and example locally: + +```bash +docker build -f Dockerfile.runtime -t pixi-runtime:local . +docker build --build-arg RUNTIME_IMAGE=pixi-runtime:local -t pixi-example example +docker run --rm -p 8000:8000 pixi-example +``` + +#### SBOM provenance + +The image retains metadata in `/usr/share/pixi-runtime/`: + +- `sbom/` contains the upstream SPDX package SBOMs for the extracted components. +- `origins.tsv` maps each extracted file to its source path and package version. +- `files.sha256` records the extracted files' SHA-256 checksums. + +These are source-package SBOMs, not a claim that the complete APK packages are +installed. The APK database is not copied. Generate a final-image SBOM that also +includes the conda environment and application when building your application image. + ### Ubuntu The [`ubuntu:noble`](https://hub.docker.com/_/ubuntu) (24.04) based image is the default base image. It is used for the `latest` and `0.x.y` tag. diff --git a/example/Dockerfile b/example/Dockerfile index fc55fcb..13fc8cc 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -1,3 +1,5 @@ +ARG RUNTIME_IMAGE=ghcr.io/prefix-dev/pixi:runtime + FROM ghcr.io/prefix-dev/pixi:0.18.0 AS build # copy source code, pixi.toml and pixi.lock to the container @@ -14,7 +16,7 @@ RUN pixi shell-hook -e prod > /shell-hook.sh # extend the shell-hook script to run the command passed to the container RUN echo 'exec "$@"' >> /shell-hook.sh -FROM ubuntu:22.04 AS production +FROM ${RUNTIME_IMAGE} AS production # only copy the production environment into prod container # please note that the "prefix" (path) needs to stay the same as in the build container diff --git a/tests/runtime.py b/tests/runtime.py new file mode 100644 index 0000000..1e66090 --- /dev/null +++ b/tests/runtime.py @@ -0,0 +1,68 @@ +"""Exercise a copied Pixi environment and verify its base image provenance.""" + +import grp +import hashlib +import json +import locale +import os +from pathlib import Path +import pwd +import sqlite3 +import subprocess +import tempfile +import time +import urllib.error +import urllib.request + + +assert os.getpid() == 1, "The shell hook must exec the application" +pwd.getpwuid(os.getuid()) +grp.getgrgid(os.getgid()) +locale.setlocale(locale.LC_ALL, "") +assert locale.getencoding().lower().replace("-", "") == "utf8" +with tempfile.TemporaryFile() as temporary_file: + temporary_file.write(b"runtime") +with sqlite3.connect(":memory:") as connection: + assert connection.execute("SELECT 6 * 7").fetchone() == (42,) +with urllib.request.urlopen("https://prefix.dev", timeout=30) as response: + assert response.status == 200 + +metadata = Path("/usr/share/pixi-runtime") +checksums = {} +for line in (metadata / "files.sha256").read_text().splitlines(): + checksum, filename = line.split(" ", 1) + path = Path("/") / filename + assert hashlib.sha256(path.read_bytes()).hexdigest() == checksum, path + checksums[str(path)] = checksum + +origins = {} +for line in (metadata / "origins.tsv").read_text().splitlines(): + destination, source, package = line.split("\t") + origins[destination] = source + sbom = json.loads((metadata / "sbom" / f"{package}.spdx.json").read_text()) + assert any( + f"{component['name']}-{component.get('versionInfo', '')}" == package + for component in sbom["packages"] + ), package +assert origins.keys() == checksums.keys(), "Every extracted file needs provenance" + +server = subprocess.Popen( + ["gunicorn", "-w", "1", "docker_project:app", "--bind", "127.0.0.1:8000"] +) +try: + deadline = time.monotonic() + 30 + while time.monotonic() < deadline: + assert server.poll() is None, "Gunicorn exited before serving a request" + try: + with urllib.request.urlopen("http://127.0.0.1:8000", timeout=5) as response: + assert response.read() == b"Hello, Pixi server!" + break + except (urllib.error.URLError, TimeoutError): + time.sleep(0.1) + else: + raise RuntimeError("Gunicorn did not become ready") +finally: + server.terminate() + server.wait(timeout=10) +assert server.returncode == 0, "Gunicorn must shut down cleanly on SIGTERM" +print("Runtime, HTTPS, HTTP server, and file/SBOM provenance checks passed") From 4652fdcdb74f098698009ce24340ddff4a5e0047 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra <4995967+baszalmstra@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:48:12 +0200 Subject: [PATCH 2/5] chore: omit libgcc from the runtime base --- Dockerfile.runtime | 1 - README.md | 4 +++- tests/runtime.py | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Dockerfile.runtime b/Dockerfile.runtime index 0061a41..edb68b5 100644 --- a/Dockerfile.runtime +++ b/Dockerfile.runtime @@ -40,7 +40,6 @@ RUN set -eu; \ done; \ copy_runtime_file /usr/bin/bash /rootfs/bin/bash; \ copy_runtime_file /usr/lib/libtinfo.so.6 /rootfs/lib/libtinfo.so.6; \ - copy_runtime_file /usr/lib/libgcc_s.so.1 /rootfs/lib/libgcc_s.so.1; \ cd /rootfs; \ find bin lib usr/lib etc -type f -exec sha256sum {} + > /tmp/runtime-checksums; \ LC_ALL=C sort /tmp/runtime-checksums > usr/share/pixi-runtime/files.sha256 diff --git a/README.md b/README.md index cd054ad..f238130 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ There are images based on `ubuntu`, `debian` and `nvidia/cuda` available. `ghcr.io/prefix-dev/pixi:runtime` is built from [`Dockerfile.runtime`](Dockerfile.runtime) for `linux/amd64` and `linux/arm64`. It contains glibc and its loader, UTF-8 locale -data, Bash, `libtinfo`, `libgcc_s`, and user/group and name-service configuration. +data, Bash, `libtinfo`, and user/group and name-service configuration. It supports running as UID/GID `65532:65532`. This image is for the final stage of a multi-stage build, not for installing environments. @@ -84,6 +84,8 @@ Include the application's other dependencies, including `ca-certificates` for HT in the Pixi environment. Some clients need `SSL_CERT_FILE` pointed at the environment's `ssl/cacert.pem`. Use an Ubuntu or Debian base when the application or its activation scripts require system utilities or `/bin/sh`. +The environment must also provide a discoverable `libgcc_s` when the application +uses glibc thread cancellation or `pthread_exit`; the base image does not include it. Runtime images are published on changes to the runtime build, separately from Pixi releases. The tags are `runtime` and `runtime-`. Pin an image digest diff --git a/tests/runtime.py b/tests/runtime.py index 1e66090..526a10e 100644 --- a/tests/runtime.py +++ b/tests/runtime.py @@ -1,5 +1,6 @@ """Exercise a copied Pixi environment and verify its base image provenance.""" +import ctypes import grp import hashlib import json @@ -27,6 +28,23 @@ with urllib.request.urlopen("https://prefix.dev", timeout=30) as response: assert response.status == 200 +# Run pthread_exit entirely in native code, without unwinding a Python callback. +libc = ctypes.CDLL("libc.so.6") +thread = ctypes.c_ulong() +libc.pthread_create.argtypes = [ + ctypes.POINTER(ctypes.c_ulong), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, +] +libc.pthread_create.restype = ctypes.c_int +libc.pthread_join.argtypes = [ctypes.c_ulong, ctypes.POINTER(ctypes.c_void_p)] +libc.pthread_join.restype = ctypes.c_int +assert libc.pthread_create( + ctypes.byref(thread), None, ctypes.cast(libc.pthread_exit, ctypes.c_void_p), None +) == 0 +assert libc.pthread_join(thread, None) == 0 + metadata = Path("/usr/share/pixi-runtime") checksums = {} for line in (metadata / "files.sha256").read_text().splitlines(): From b5a9ce37f381dd583c8f26f658987c8a16e34b06 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra <4995967+baszalmstra@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:28:28 +0200 Subject: [PATCH 3/5] feat: publish the runtime as pixi-runtime:glibc-bash --- .github/workflows/runtime.yml | 12 ++++++------ README.md | 19 +++++++++++++------ example/Dockerfile | 2 +- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index ffec8b6..db9ce51 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -82,16 +82,16 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish tested platform images and manifest env: - IMAGE: ghcr.io/prefix-dev/pixi + IMAGE: ghcr.io/prefix-dev/pixi-runtime run: | for architecture in amd64 arm64; do docker load --input "images/runtime-$architecture.tar" - tag="$IMAGE:runtime-$GITHUB_SHA-$architecture" + tag="$IMAGE:glibc-bash-$GITHUB_SHA-$architecture" docker tag pixi-runtime:test "$tag" docker push "$tag" done docker buildx imagetools create \ - --tag "$IMAGE:runtime-$GITHUB_SHA" \ - --tag "$IMAGE:runtime" \ - "$IMAGE:runtime-$GITHUB_SHA-amd64" \ - "$IMAGE:runtime-$GITHUB_SHA-arm64" + --tag "$IMAGE:glibc-bash-$GITHUB_SHA" \ + --tag "$IMAGE:glibc-bash" \ + "$IMAGE:glibc-bash-$GITHUB_SHA-amd64" \ + "$IMAGE:glibc-bash-$GITHUB_SHA-arm64" diff --git a/README.md b/README.md index f238130..404fad6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository contains the docker configuration for the pixi container image. The pixi container image is based on different base images, depending on the use case. The Ubuntu, Debian and CUDA images have pixi installed in `/usr/local/bin/pixi`. -The `runtime` image contains only the system libraries and Bash needed to run a copied environment. +The separate `pixi-runtime` image contains only the system libraries and Bash needed to run a copied environment. ## Pulling the images @@ -21,9 +21,16 @@ There are different tags for different base images available: - `bullseye` - based on `debian:bullseye` - `noble-cuda-12.9.1` - based on `nvidia/cuda:12.9.1-base-ubuntu24.04` - `noble-cuda-13.0.0` - based on `nvidia/cuda:13.0.0-base-ubuntu24.04` -- `runtime` - a runtime-only image built from selected Wolfi files, without Pixi - ... and more +The runtime-only image is published as a separate package, +[`ghcr.io/prefix-dev/pixi-runtime`](https://github.com/prefix-dev/pixi-docker/pkgs/container/pixi-runtime), +because it does not contain pixi: + +```bash +docker pull ghcr.io/prefix-dev/pixi-runtime:glibc-bash +``` + ## Usage with shell-hook The following example uses the pixi docker image as a base image for a multi-stage build. @@ -46,7 +53,7 @@ RUN pixi shell-hook -e prod > /shell-hook.sh # extend the shell-hook script to run the command passed to the container RUN echo 'exec "$@"' >> /shell-hook.sh -FROM ghcr.io/prefix-dev/pixi:runtime AS production +FROM ghcr.io/prefix-dev/pixi-runtime:glibc-bash AS production # only copy the production environment into prod container # please note that the "prefix" (path) needs to stay the same as in the build container @@ -71,7 +78,7 @@ There are images based on `ubuntu`, `debian` and `nvidia/cuda` available. ### Minimal runtime -`ghcr.io/prefix-dev/pixi:runtime` is built from [`Dockerfile.runtime`](Dockerfile.runtime) +`ghcr.io/prefix-dev/pixi-runtime:glibc-bash` is built from [`Dockerfile.runtime`](Dockerfile.runtime) for `linux/amd64` and `linux/arm64`. It contains glibc and its loader, UTF-8 locale data, Bash, `libtinfo`, and user/group and name-service configuration. It supports running as UID/GID `65532:65532`. @@ -88,8 +95,8 @@ The environment must also provide a discoverable `libgcc_s` when the application uses glibc thread cancellation or `pthread_exit`; the base image does not include it. Runtime images are published on changes to the runtime build, separately from Pixi -releases. The tags are `runtime` and `runtime-`. Pin an image digest -for deployments. +releases. The tags are `glibc-bash` and `glibc-bash-`, naming the +runtime components the image provides. Pin an image digest for deployments. To build the runtime and example locally: diff --git a/example/Dockerfile b/example/Dockerfile index 13fc8cc..530ec46 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -1,4 +1,4 @@ -ARG RUNTIME_IMAGE=ghcr.io/prefix-dev/pixi:runtime +ARG RUNTIME_IMAGE=ghcr.io/prefix-dev/pixi-runtime:glibc-bash FROM ghcr.io/prefix-dev/pixi:0.18.0 AS build From 62fbec7b57126cb73f9b1b06567cd6d284547e36 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra <4995967+baszalmstra@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:54:50 +0200 Subject: [PATCH 4/5] feat: publish the runtime as conda-runtime:glibc-bash --- .github/workflows/runtime.yml | 14 ++++++------- Dockerfile.runtime | 8 +++---- README.md | 39 ++++++++++++++++++++++++++--------- example/Dockerfile | 2 +- tests/runtime.py | 2 +- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/.github/workflows/runtime.yml b/.github/workflows/runtime.yml index db9ce51..e0e736e 100644 --- a/.github/workflows/runtime.yml +++ b/.github/workflows/runtime.yml @@ -41,20 +41,20 @@ jobs: env: PLATFORM: ${{ matrix.platform }} run: | - docker build --platform="$PLATFORM" -f Dockerfile.runtime -t pixi-runtime:test \ + docker build --platform="$PLATFORM" -f Dockerfile.runtime -t conda-runtime:test \ --label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \ --label "org.opencontainers.image.revision=$GITHUB_SHA" . - docker build --platform="$PLATFORM" --build-arg RUNTIME_IMAGE=pixi-runtime:test \ - -f example/Dockerfile -t pixi-runtime:example example + docker build --platform="$PLATFORM" --build-arg RUNTIME_IMAGE=conda-runtime:test \ + -f example/Dockerfile -t conda-runtime:example example - name: Test copied environment and provenance run: | docker run --rm --read-only --tmpfs /tmp --user=65532:65532 \ --mount "type=bind,source=$PWD/tests/runtime.py,target=/runtime.py,readonly" \ - pixi-runtime:example python /runtime.py + conda-runtime:example python /runtime.py - name: Export tested runtime env: ARCHITECTURE: ${{ matrix.architecture }} - run: docker save --output "runtime-$ARCHITECTURE.tar" pixi-runtime:test + run: docker save --output "runtime-$ARCHITECTURE.tar" conda-runtime:test - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: runtime-${{ matrix.architecture }} @@ -82,12 +82,12 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Publish tested platform images and manifest env: - IMAGE: ghcr.io/prefix-dev/pixi-runtime + IMAGE: ghcr.io/prefix-dev/conda-runtime run: | for architecture in amd64 arm64; do docker load --input "images/runtime-$architecture.tar" tag="$IMAGE:glibc-bash-$GITHUB_SHA-$architecture" - docker tag pixi-runtime:test "$tag" + docker tag conda-runtime:test "$tag" docker push "$tag" done docker buildx imagetools create \ diff --git a/Dockerfile.runtime b/Dockerfile.runtime index edb68b5..f7fd7e6 100644 --- a/Dockerfile.runtime +++ b/Dockerfile.runtime @@ -9,15 +9,15 @@ RUN apk add --no-cache \ # The SBOMs describe upstream packages, not complete packages installed here. RUN set -eu; \ mkdir -p /rootfs/bin /rootfs/lib /rootfs/lib64 /rootfs/usr/lib /rootfs/etc /rootfs/tmp \ - /rootfs/usr/share/pixi-runtime/sbom; \ + /rootfs/usr/share/conda-runtime/sbom; \ chmod 1777 /rootfs/tmp; \ copy_runtime_file() { \ cp -L "$1" "$2"; \ package="$(apk info --who-owns "$(readlink -f "$1")")"; \ package="${package##* }"; \ - cp "/var/lib/db/sbom/${package}.spdx.json" /rootfs/usr/share/pixi-runtime/sbom/; \ + cp "/var/lib/db/sbom/${package}.spdx.json" /rootfs/usr/share/conda-runtime/sbom/; \ printf '%s\t%s\t%s\n' "${2#/rootfs}" "$1" "$package" \ - >> /rootfs/usr/share/pixi-runtime/origins.tsv; \ + >> /rootfs/usr/share/conda-runtime/origins.tsv; \ }; \ apk --repositories-file /dev/null info -L glibc ld-linux > /tmp/glibc-files; \ while IFS= read -r file; do \ @@ -42,7 +42,7 @@ RUN set -eu; \ copy_runtime_file /usr/lib/libtinfo.so.6 /rootfs/lib/libtinfo.so.6; \ cd /rootfs; \ find bin lib usr/lib etc -type f -exec sha256sum {} + > /tmp/runtime-checksums; \ - LC_ALL=C sort /tmp/runtime-checksums > usr/share/pixi-runtime/files.sha256 + LC_ALL=C sort /tmp/runtime-checksums > usr/share/conda-runtime/files.sha256 FROM scratch COPY --from=source /rootfs/ / diff --git a/README.md b/README.md index 404fad6..0429c8e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This repository contains the docker configuration for the pixi container image. The pixi container image is based on different base images, depending on the use case. The Ubuntu, Debian and CUDA images have pixi installed in `/usr/local/bin/pixi`. -The separate `pixi-runtime` image contains only the system libraries and Bash needed to run a copied environment. +The separate `conda-runtime` image contains only the system libraries and Bash needed to run a copied environment. ## Pulling the images @@ -24,11 +24,11 @@ There are different tags for different base images available: - ... and more The runtime-only image is published as a separate package, -[`ghcr.io/prefix-dev/pixi-runtime`](https://github.com/prefix-dev/pixi-docker/pkgs/container/pixi-runtime), -because it does not contain pixi: +[`ghcr.io/prefix-dev/conda-runtime`](https://github.com/prefix-dev/pixi-docker/pkgs/container/conda-runtime), +because it does not contain pixi and runs any copied conda environment: ```bash -docker pull ghcr.io/prefix-dev/pixi-runtime:glibc-bash +docker pull ghcr.io/prefix-dev/conda-runtime:glibc-bash ``` ## Usage with shell-hook @@ -53,7 +53,7 @@ RUN pixi shell-hook -e prod > /shell-hook.sh # extend the shell-hook script to run the command passed to the container RUN echo 'exec "$@"' >> /shell-hook.sh -FROM ghcr.io/prefix-dev/pixi-runtime:glibc-bash AS production +FROM ghcr.io/prefix-dev/conda-runtime:glibc-bash AS production # only copy the production environment into prod container # please note that the "prefix" (path) needs to stay the same as in the build container @@ -70,7 +70,7 @@ CMD ["start-server"] ``` Copy any application files and activation scripts referenced by the shell hook as well. -The runtime image does not contain system utilities; see its requirements below. +You can also keep `ubuntu:24.04` here; see [Choosing a final base image](#choosing-a-final-base-image). ## Images @@ -78,7 +78,7 @@ There are images based on `ubuntu`, `debian` and `nvidia/cuda` available. ### Minimal runtime -`ghcr.io/prefix-dev/pixi-runtime:glibc-bash` is built from [`Dockerfile.runtime`](Dockerfile.runtime) +`ghcr.io/prefix-dev/conda-runtime:glibc-bash` is built from [`Dockerfile.runtime`](Dockerfile.runtime) for `linux/amd64` and `linux/arm64`. It contains glibc and its loader, UTF-8 locale data, Bash, `libtinfo`, and user/group and name-service configuration. It supports running as UID/GID `65532:65532`. @@ -101,14 +101,14 @@ runtime components the image provides. Pin an image digest for deployments. To build the runtime and example locally: ```bash -docker build -f Dockerfile.runtime -t pixi-runtime:local . -docker build --build-arg RUNTIME_IMAGE=pixi-runtime:local -t pixi-example example +docker build -f Dockerfile.runtime -t conda-runtime:local . +docker build --build-arg RUNTIME_IMAGE=conda-runtime:local -t pixi-example example docker run --rm -p 8000:8000 pixi-example ``` #### SBOM provenance -The image retains metadata in `/usr/share/pixi-runtime/`: +The image retains metadata in `/usr/share/conda-runtime/`: - `sbom/` contains the upstream SPDX package SBOMs for the extracted components. - `origins.tsv` maps each extracted file to its source path and package version. @@ -118,6 +118,25 @@ These are source-package SBOMs, not a claim that the complete APK packages are installed. The APK database is not copied. Generate a final-image SBOM that also includes the conda environment and application when building your application image. +#### Choosing a final base image + +Both final stages work with the same `shell-hook` setup, so this is a tradeoff, not a migration. + +| | `conda-runtime:glibc-bash` | `ubuntu:24.04` | +|---|---|---| +| Image size, without the environment | 7.8 MB on amd64, 7.2 MB on arm64 | 78 MB on amd64 | +| Attack surface | glibc, Bash, locale data | full distribution, including a shell, coreutils, and apt | +| Debugging inside the container | Bash only; no `ls`, `cat`, `curl`, or package manager | usual utilities available, plus `apt-get install` | +| System CA bundle | absent; use the environment's certificates | `ca-certificates` at the usual path | +| Non-POSIX activation scripts | supported, Bash is present | supported | +| Security updates for the base | rebuild this repository's runtime image | Ubuntu's own update stream | +| Third-party tooling expecting a distribution | may break on missing `/bin/sh` and utilities | works | + +Use `conda-runtime` for small production images where the conda environment provides +everything the application needs. Use Ubuntu or Debian when you want shell access for +debugging, need system packages next to the environment, or run agents and sidecars +that expect a distribution layout. + ### Ubuntu The [`ubuntu:noble`](https://hub.docker.com/_/ubuntu) (24.04) based image is the default base image. It is used for the `latest` and `0.x.y` tag. diff --git a/example/Dockerfile b/example/Dockerfile index 530ec46..fd9e414 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -1,4 +1,4 @@ -ARG RUNTIME_IMAGE=ghcr.io/prefix-dev/pixi-runtime:glibc-bash +ARG RUNTIME_IMAGE=ghcr.io/prefix-dev/conda-runtime:glibc-bash FROM ghcr.io/prefix-dev/pixi:0.18.0 AS build diff --git a/tests/runtime.py b/tests/runtime.py index 526a10e..522d852 100644 --- a/tests/runtime.py +++ b/tests/runtime.py @@ -45,7 +45,7 @@ ) == 0 assert libc.pthread_join(thread, None) == 0 -metadata = Path("/usr/share/pixi-runtime") +metadata = Path("/usr/share/conda-runtime") checksums = {} for line in (metadata / "files.sha256").read_text().splitlines(): checksum, filename = line.split(" ", 1) From e107f946d8f056d7827a7cacbde8f09bb5a5a45b Mon Sep 17 00:00:00 2001 From: Bas Zalmstra <4995967+baszalmstra@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:55:14 +0200 Subject: [PATCH 5/5] docs: correct the CA bundle comparison --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0429c8e..fc2814f 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ Both final stages work with the same `shell-hook` setup, so this is a tradeoff, | Image size, without the environment | 7.8 MB on amd64, 7.2 MB on arm64 | 78 MB on amd64 | | Attack surface | glibc, Bash, locale data | full distribution, including a shell, coreutils, and apt | | Debugging inside the container | Bash only; no `ls`, `cat`, `curl`, or package manager | usual utilities available, plus `apt-get install` | -| System CA bundle | absent; use the environment's certificates | `ca-certificates` at the usual path | +| System CA bundle | absent, and no way to add one | also absent, but `apt-get install ca-certificates` adds it | | Non-POSIX activation scripts | supported, Bash is present | supported | | Security updates for the base | rebuild this repository's runtime image | Ubuntu's own update stream | | Third-party tooling expecting a distribution | may break on missing `/bin/sh` and utilities | works |