Docs chevron_right Build packages

Build OCI images

Produce small, multi-architecture images with cached CI builds, checksums, SBOMs, and attestations.

Build OCI images

cpak consumes standard OCI images. Include the application, its runtime libraries, declared desktop files, and required assets. The cpak runtime stays on the host.

Use multiple stages

Compile or unpack software in a builder stage, then copy the runtime result into a clean final stage:

FROM golang:1.26-bookworm AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /out/example ./cmd/example

FROM debian:13-slim
COPY --from=build /out/example /usr/bin/example
ENTRYPOINT ["/usr/bin/example"]

Build tools and package manager caches remain outside the final image. This matters even when layers are shared because every unique byte still has to be downloaded and stored once.

Publish with GitHub Actions

Official Containerpak packages build images in GitHub Actions. A typical workflow publishes amd64 and arm64, uses the GitHub Actions cache, and attaches provenance and an SBOM:

name: Publish

on:
  push:
    branches: [main]
    paths:
      - Containerfile
      - cpak.json
      - .github/workflows/publish.yml

permissions:
  contents: read
  packages: write
  attestations: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-qemu-action@v3
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          file: Containerfile
          platforms: linux/amd64,linux/arm64
          push: true
          pull: true
          tags: ghcr.io/example/example:main
          cache-from: type=gha
          cache-to: type=gha,mode=max
          provenance: mode=max
          sbom: true

Add application-specific checks after the build. Verify every architecture that the workflow claims to publish.

Keep pull: true when the final image follows a platform tag. This makes the build resolve the current platform digest even when the GitHub Actions cache contains an older base.

Base images

Choose a maintained base that supplies the ABI and runtime packages your application expects. The Containerpak images and wine repositories provide reusable environments for official packages with shared needs. Application payloads stay in their package images.

Keep the distribution release explicit. A floating distribution tag can replace libraries without a corresponding package review.

ImageBase distributionIntended useRecipe
ghcr.io/containerpak/foundation:mainUbuntu 26.04Pinned, single-layer Ubuntu foundation with certificates and the cpak APT policyplatform/foundation
ghcr.io/containerpak/base:mainUbuntu 26.04Compatibility name for the foundationplatform/base
ghcr.io/containerpak/locales:mainUbuntu 26.04Compiled locale data selected by cpak at installation time; not an application baseplatform/locales
ghcr.io/containerpak/mesa64:mainUbuntu 26.0464-bit OpenGL, Vulkan, Wayland, and common font runtimeplatform/mesa64
ghcr.io/containerpak/mesa-multilib:mainUbuntu 26.04mesa64 with 32-bit graphics librariesplatform/mesa-multilib
ghcr.io/containerpak/mesa:mainUbuntu 26.04Multilib graphics runtime with Mesa and Vulkan command-line toolsplatform/mesa
ghcr.io/containerpak/gtk3:mainUbuntu 26.04GTK 3 desktop applications with audio and 64-bit graphicsplatform/gtk3
ghcr.io/containerpak/webkitgtk:mainUbuntu 26.04GTK 3 applications that use WebKitGTK 4.1platform/webkitgtk
ghcr.io/containerpak/gtk4:mainUbuntu 26.04GTK 4 desktop applicationsplatform/gtk4
ghcr.io/containerpak/adwaita:mainUbuntu 26.04GTK 4 applications that use libadwaitaplatform/adwaita
ghcr.io/containerpak/webkitgtk6:mainUbuntu 26.04GTK 4 and libadwaita applications that use WebKitGTK 6platform/webkitgtk6
ghcr.io/containerpak/desktop:mainUbuntu 26.04Complete desktop runtime for applications that require GTK 3, GTK 4, libadwaita, and WebKitGTK 4.1platform/desktop
ghcr.io/containerpak/gtk:mainUbuntu 26.04Compatibility name for desktop; new packages should select a versioned GTK branchplatform/gtk
ghcr.io/containerpak/wine:mainUbuntu 26.04Host libraries for application images that supply Wine or Proton, including 32-bit graphics, audio, input, and multimediaContainerpak/wine

SDK images follow the same split:

ImageIntended useRecipe
ghcr.io/containerpak/base-sdk:mainGeneral C and C++ build environmentsdk/base
ghcr.io/containerpak/mesa64-sdk:main64-bit graphics headers and build toolssdk/mesa64
ghcr.io/containerpak/mesa-sdk:mainMultilib graphics headers and toolssdk/mesa
ghcr.io/containerpak/gtk3-sdk:mainGTK 3 development headerssdk/gtk3
ghcr.io/containerpak/webkitgtk-sdk:mainGTK 3 and WebKitGTK 4.1 development headerssdk/webkitgtk
ghcr.io/containerpak/gtk4-sdk:mainGTK 4 and libadwaita development headerssdk/gtk4
ghcr.io/containerpak/desktop-sdk:mainComplete SDK for GTK 3, GTK 4, libadwaita, and WebKitGTK 4.1sdk/desktop
ghcr.io/containerpak/gtk-sdk:mainCompatibility name for desktop-sdksdk/gtk

The main tag follows the current platform build. ubuntu-26.04 follows the current build for that Ubuntu release. Tags such as ubuntu-26.04.20260814.3 and sha-<revision> identify one published platform state and are suitable for reproducible builds.

Official platform images identify the matching locale image in their OCI configuration. cpak reads the user's locale, imports only the required compiled directories, and adds the resulting shared layer to the application. Packages retain their own translation catalogs and do not need to include locales-all.

The distribution choice defines the ABI and library versions available to the application. Pick the smallest base that already matches the software, pin the final application image by digest through cpak, and review base updates in CI before publishing them.

Install additional runtime libraries and clean APT in the same layer:

FROM ghcr.io/containerpak/gtk3:ubuntu-26.04

RUN apt-get update && \
    apt-get install -y libexample1 && \
    cpak-clean-junk

The platform APT policy already disables recommended and suggested packages, downloaded package retention, manuals, package reports, and build documentation. Do not replace that policy or install locales-all in each application image. Keep application translation catalogs in the package; cpak supplies the compiled host locale through the shared locale layer.

Layer layout

Group stable runtime content before frequently changing application content. OCI registries and cpak address layers by digest, so unchanged base layers can be shared by many packages and retained across updates.

Avoid a single giant RUN step when it causes an application update to invalidate unrelated runtime content. Avoid many tiny layers that exist only to mirror individual shell commands. Split at boundaries that are likely to change independently.

Design for two-level deduplication

Shared base images are useful beyond build consistency. cpak stores OCI layers by digest, so applications built on the same unchanged base reuse one downloaded and stored layer. A new layer is unpacked only when its digest is absent.

cpak streams a new layer directly into the global FVS content store. FVS splits file contents into content-defined blocks and reuses blocks already referenced by another layer. This catches equal ranges even when two images placed them in different files or produced different OCI digests.

OCI digest match     -> reuse the complete layer
New OCI layer        -> verify and decode as one stream
FVS block match      -> reference the existing content block
Unique block         -> store one new content block

The first level reuses stable bases and matching layer boundaries. The second level finds repeated content across different layer layouts. Both run automatically during image pull. A successful import retains the FVS representation used by installed packages.

For registries and CDNs that preserve byte-range responses, zstd:chunked lets cpak read the layer table of contents first and skip compressed file ranges whose complete content already exists in FVS. cpak uses one complete stream for a cold store and switches to ranges only when known content makes that cheaper. A normal gzip or zstd pull remains the automatic fallback. Read Choose and operate an OCI registry before enabling it in CI.

External artifacts

Prefer downloading build inputs in CI and checking their vendor-provided checksum before use. If an artifact must be installed on the user's machine, declare it through runtime_sources with its HTTPS URL, exact size, SHA-256, and a tar, dpkg, deb-extract, rpm, or file installer. This is also the correct boundary when the vendor permits users to download an application but does not permit the Store to republish its payload. See Runtime sources for the archive and file contracts and CI checks.

Verify the result

After the image is published:

cpak validate cpak.json
cpak lock cpak.json
cpak test cpak.json

Run at least one declared binary. Desktop packages also need a real visual launch through cpak on each supported display path.

Projects and technologies around cpak