CLI Container Images: Standard and Builder Variants#
Overview#
The aetherpak/cli repository publishes two container image variants to the GitHub Container Registry (GHCR), each providing the aetherpak binary alongside a complete Flatpak toolchain:
ghcr.io/aetherpak/cli:<version>— the standard CLI image, suitable for all non-build operationsghcr.io/aetherpak/cli:<version>-builder— the builder CLI image, required wheneverflatpak-buildermust run
Both images ship the same statically-linked aetherpak binary compiled from the same source commit, with the difference between them lying entirely in the base image they are built upon . The standard variant is built on ghcr.io/aetherpak/flatpak; the builder variant is built on ghcr.io/aetherpak/flatpak-builder. This layered design means that every CLI release inherits its Flatpak toolchain from a specific snapshot of the aetherpak/flatpak-containers images.
Both variants are published as multi-architecture OCI manifests covering linux/amd64 and linux/arm64. Container runtimes (Docker, Podman) automatically select the correct variant for the host machine when pulling by tag. Architecture-specific tags (e.g. v0.15.1-amd64, v0.15.1-builder-arm64) are also available for use cases that require an explicit architecture pin.
The aetherpak/actions reusable workflow (aetherpak/actions/.github/workflows/publish.yml@v3) uses these images internally for all pipeline jobs — no additional toolchain setup is required when running inside these containers . Understanding the two variants and their intended use cases helps you configure custom workflows and debug pipeline behaviour.
These images are for running the CLI, not developing it. If you are developing the
aetherpakCLI itself and need a local build environment, use the underlyingghcr.io/aetherpak/flatpakorghcr.io/aetherpak/flatpak-builderimages directly — see Utility Images: flatpak and flatpak-builder for details.
Image Variants#
Comparison#
| Property | Standard (<version>) | Builder (<version>-builder) |
|---|---|---|
| Full image reference | ghcr.io/aetherpak/cli:<version> | ghcr.io/aetherpak/cli:<version>-builder |
| Containerfile stage | cli | cli-builder |
| Base image | ghcr.io/aetherpak/flatpak | ghcr.io/aetherpak/flatpak-builder |
aetherpak binary | ✓ | ✓ |
Flatpak tooling (flatpak, ostree, git, jq, gpg) | ✓ | ✓ (inherited) |
flatpak-builder | ✗ | ✓ |
flatpak-builder-lint | ✗ | ✓ |
elfutils, appstream, desktop-file-utils | ✗ | ✓ |
gobject-introspection, cairo, patch | ✗ | ✓ |
| Privileged mode required | No | Yes |
| Architecture support | linux/amd64, linux/arm64 | linux/amd64, linux/arm64 |
Standard image use cases: plan, prep-bundle, publish-oci, publish-site — any step that only requires the Flatpak client, OSTree tooling, and the aetherpak binary .
Builder image use cases: build-manifest — the step that compiles a Flatpak application from a manifest using flatpak-builder .
When the builder tag is REQUIRED: any time you need to compile a Flatpak application from a manifest using
flatpak-builder. The builder image requires--privilegedmode becauseflatpak-builderneeds to write to the system Flatpak installation to install runtimes and SDKs from the pre-configured Flathub remote. Running as root inside a privileged container letsflatpak-builderwrite directly to the system install location without requiring the polkit/dbus system helper — which is not available in a container environment . Non-build operations do not require--privileged.
Multi-Architecture and Single-Architecture Tags#
Both images are published as multi-arch OCI manifest lists. Architecture-specific tags are also available for workflows that must target an explicit architecture :
| Tag pattern | Description |
|---|---|
ghcr.io/aetherpak/cli:v0.15.1 | Multi-arch manifest (auto-selects amd64 or arm64) |
ghcr.io/aetherpak/cli:v0.15.1-builder | Multi-arch builder manifest |
ghcr.io/aetherpak/cli:v0.15.1-amd64 | Standard image, amd64 only |
ghcr.io/aetherpak/cli:v0.15.1-arm64 | Standard image, arm64 only |
ghcr.io/aetherpak/cli:v0.15.1-builder-amd64 | Builder image, amd64 only |
ghcr.io/aetherpak/cli:v0.15.1-builder-arm64 | Builder image, arm64 only |
For most workflows, pull the multi-arch manifest tag. Single-architecture tags are most useful when you know the runner architecture in advance and want to avoid the manifest-list resolution step.
Architecture: How CLI Images Use Utility Images#
The CLI images are not built from scratch. They are a thin layer on top of the foundational utility images from the aetherpak/flatpak-containers repository. The Containerfile in aetherpak/cli defines two build arguments that reference them directly :
ARG BASE_IMAGE=ghcr.io/aetherpak/flatpak:latest
ARG BUILDER_IMAGE=ghcr.io/aetherpak/flatpak-builder:latest
Multi-Stage Build Structure#
The cli Containerfile uses three build stages :
Stage 1 — cli-binary-builder: Compiles the aetherpak binary from source using golang:1.26-alpine. The binary is built with CGO_ENABLED=0 for full static linking and stripped of debug symbols with -ldflags="-s -w", producing a compact, dependency-free binary at /bin/aetherpak.
Stage 2 — cli: Starts from ${BASE_IMAGE} (ghcr.io/aetherpak/flatpak) and copies the compiled binary to /usr/local/bin/aetherpak. This produces the standard CLI image.
Stage 3 — cli-builder: Starts from ${BUILDER_IMAGE} (ghcr.io/aetherpak/flatpak-builder) and copies the same compiled binary. This produces the builder CLI image.
FROM ${BASE_IMAGE} AS cli
LABEL org.opencontainers.image.title="AetherPak CLI" \
org.opencontainers.image.description="AetherPak image with the pre-baked aetherpak CLI"
COPY /bin/aetherpak /usr/local/bin/aetherpak
WORKDIR /workspace
CMD ["/bin/bash"]
FROM ${BUILDER_IMAGE} AS cli-builder
LABEL org.opencontainers.image.title="AetherPak Builder CLI" \
org.opencontainers.image.description="AetherPak Builder image with the pre-baked aetherpak CLI"
COPY /bin/aetherpak /usr/local/bin/aetherpak
WORKDIR /workspace
CMD ["/bin/bash"]
The compiled binary is identical across both variants . The only difference is the base image each stage inherits from — and with it, the toolchain that is available in the container.
What the Utility Images Provide#
The utility images are the authoritative source of the Flatpak toolchain in both CLI variants. Both are based on registry.fedoraproject.org/fedora-minimal with the base digest pinned for reproducibility. A pre-configured Flathub remote is added during image build so that runtimes and SDKs can be resolved without any additional setup. See Utility Images: flatpak and flatpak-builder for the full package manifests and architecture details.
Flathub Parity#
A core design goal is parity with the Flathub GitHub Actions builder environment. The builder image installs flatpak-builder-lint directly from the canonical flathub-infra upstream source (not from a versioned PyPI release), ensuring that lint behaviour in AetherPak CI matches what Flathub itself expects . This parity is inherited transitively by the cli-builder image.
Image Lineage Summary#
registry.fedoraproject.org/fedora-minimal (pinned digest)
└── ghcr.io/aetherpak/flatpak
│ └── ghcr.io/aetherpak/cli:<version> (standard)
└── ghcr.io/aetherpak/flatpak-builder
└── ghcr.io/aetherpak/cli:<version>-builder (builder)
Using CLI Containers in GitHub Actions#
Container Jobs vs. aetherpak/setup-cli#
There are two ways to run aetherpak in a GitHub Actions workflow:
-
Container-based: Declare a
container:block in the job definition referencing the appropriate CLI image. The job's steps execute inside the container, which already has theaetherpakbinary and all toolchain dependencies pre-installed. -
aetherpak/setup-cli@v1: Run on a standard runner (e.g.ubuntu-latest) and use the action to download and install the CLI binary, optionally installing system dependencies viaapt-get,dnf, ormicrodnf.
The aetherpak/actions reusable workflow uses containers internally for all its jobs . When you call the reusable workflow, you are already running inside the CLI container — no additional setup is needed.
When to prefer containers over setup-cli#
- Consistent, pre-configured environment. The CLI image bundles all required system libraries and Flatpak tooling. There is no
apt-get installstep, no version drift, and no risk of a package manager outage affecting your build. - Faster startup. Frequently used image layers are cached by the runner. Subsequent workflow runs skip the download entirely for cached layers.
- Locked toolchain version. The image tag pins the
aetherpakbinary, the Flatpak version,ostree, and every other dependency together into a single, immutable unit. - Alignment with the reusable workflow. If you are writing a custom workflow that mirrors what the reusable workflow does, using the same container images ensures identical behaviour.
When to prefer setup-cli#
- Self-hosted or custom runners. Container-based jobs require a container runtime on the runner. If your runners don't support Docker or Podman,
setup-cliis the right choice. - Mixed workflows. If your job needs to interleave AetherPak steps with tools that are not available inside the CLI container, the bare-runner approach gives you more flexibility.
- Additional tooling. When your workflow needs to install extra dependencies beyond what the CLI image provides, it is easier to layer them on a standard runner than to customise the container image.
- No matching container image. If you specify a
cli-versionthat does not have a published container image tag, non-build jobs automatically fall back toaetherpak/setup-cli. Thesetup-clifallback runs onubuntu-latestand installs dependencies viaapt-get.
Fallback behaviour. If a specified
cli-versionhas no corresponding published container image tag, non-build jobs automatically fall back tosetup-cli. The build job (build-manifest) does not have a graceful fallback — it always requires the builder container image.
Basic Job Definition#
Use the container: key in your job definition to run inside the CLI image:
jobs:
publish-oci:
name: Push to registry
runs-on: ubuntu-latest
container:
image: ghcr.io/aetherpak/cli:v0.15.1
steps:
- uses: actions/checkout@v6
- run: aetherpak publish-oci --repo-path _repo
For a build-manifest job that needs to compile a Flatpak, use the builder image with --privileged :
jobs:
build:
name: Build Flatpak
runs-on: ubuntu-latest
container:
image: ghcr.io/aetherpak/cli:v0.15.1-builder
options: --privileged
steps:
- uses: actions/checkout@v6
- run: |
aetherpak build-manifest \
--manifest org.example.App.json \
--arch x86_64 \
--repo-path _repo
Complete Workflow Example#
The following example mirrors how the reusable workflow structures its jobs — a plan job on the standard image feeding a build job on the builder image:
name: Publish Flatpak
on:
push:
tags: ['v*']
jobs:
plan:
name: Plan build matrix
runs-on: ubuntu-latest
container:
image: ghcr.io/aetherpak/cli:v0.15.1
outputs:
matrix: ${{ steps.plan.outputs.matrix }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Plan
id: plan
run: aetherpak plan --output-file "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.app-id }} (${{ matrix.arch }})
needs: plan
runs-on: ubuntu-latest
strategy:
matrix:
include: ${{ fromJSON(needs.plan.outputs.matrix) }}
container:
image: ghcr.io/aetherpak/cli:v0.15.1-builder
options: --privileged
steps:
- uses: actions/checkout@v6
- name: Build
run: |
aetherpak build-manifest \
--manifest ${{ matrix.manifest }} \
--arch ${{ matrix.arch }} \
--repo-path _repo
- uses: actions/upload-artifact@v7
with:
name: repo-${{ matrix.app-id }}-${{ matrix.arch }}
path: _repo
Use the reusable workflow when you can. The
aetherpak/actions/.github/workflows/publish.yml@v3reusable workflow handles plan → build → publish orchestration including matrix expansion, artifact passing, and site deployment. Use a custom container job only when you need control that the reusable workflow doesn't expose.
Using CLI Containers in GitLab CI/CD#
The CLI container images are published to GHCR as public packages and can be pulled without authentication, making them straightforward to use as the image: in any GitLab CI/CD pipeline job.
GitLab CI Support in the CLI#
The aetherpak binary has explicit support for GitLab CI environments. Channel resolution, output formatting, and configuration all work without GitHub-specific plumbing:
Channel resolution via GitLab environment variables. The CLI detects GitLab CI environment variables to determine the channel to publish to :
| Variable | Used for |
|---|---|
CI_COMMIT_TAG | Identifies a tag event → resolves to the stable channel |
CI_COMMIT_BRANCH | Identifies a branch push → resolves to the branch name (or beta for the default branch) |
CI_DEFAULT_BRANCH | Identifies the default branch for beta channel resolution |
The resolution priority order is: AETHERPAK_* overrides → GitHub Actions variables → GitLab CI variables → defaults. This means you can override channel behaviour in any CI platform using AETHERPAK_REF_TYPE, AETHERPAK_REF_NAME, and AETHERPAK_DEFAULT_BRANCH environment variables .
Dotenv output compatible with GitLab artifacts. The CLI's --output-file flag writes KEY=VALUE dotenv lines using the pkg/ciout package . GitLab's artifacts:reports:dotenv consumes this format natively, allowing step outputs to pass between jobs as artifact variables — the same mechanism as $GITHUB_OUTPUT on GitHub Actions.
AETHERPAK_-prefixed environment variables. Any aetherpak.yaml configuration key can be overridden via an AETHERPAK_-prefixed environment variable (e.g. AETHERPAK_REGISTRY, AETHERPAK_OCI_REPOSITORY). This lets you drive the full CLI configuration from GitLab CI/CD variables without committing secrets to the repository.
Privileged Mode for Build Jobs#
For jobs that invoke flatpak-builder (i.e. jobs using the builder image), the GitLab Runner must be configured with privileged = true in its config.toml. The requirement is identical to GitHub Actions: flatpak-builder must write to the system Flatpak installation when resolving and installing runtimes and SDKs from the pre-configured Flathub remote, which requires root access to the container's system directories.
Non-build jobs (plan, publish) using the standard image do not require privileged mode.
Example .gitlab-ci.yml#
The following example shows a two-job pipeline: a plan job using the standard image to resolve the channel, followed by a build job using the builder image to compile the Flatpak:
variables:
AETHERPAK_REGISTRY: "registry.gitlab.com"
AETHERPAK_OCI_REPOSITORY: "$CI_PROJECT_PATH"
stages:
- plan
- build
- publish
plan:
stage: plan
image: ghcr.io/aetherpak/cli:v0.15.1
script:
- aetherpak plan --output-file plan.env
artifacts:
reports:
dotenv: plan.env
build:
stage: build
image: ghcr.io/aetherpak/cli:v0.15.1-builder
# GitLab Runner must have privileged = true in config.toml
before_script:
- echo "Building for arch ${ARCH}"
script:
- aetherpak build-manifest
--manifest org.example.App.json
--arch x86_64
--repo-path _repo
artifacts:
paths:
- _repo/
needs:
- job: plan
artifacts: true
publish:
stage: publish
image: ghcr.io/aetherpak/cli:v0.15.1
script:
- aetherpak publish-oci --repo-path _repo
needs:
- job: build
artifacts: true
GitLab Runner privileged mode. The
buildjob above requires the GitLab Runner to be registered withprivileged = true. This is a runner-level configuration, not a per-job setting. Consult your GitLab administrator or runner configuration if you are unsure whether privileged mode is enabled for your project's runners.
Image Tags and Versioning#
Tagging Scheme#
CLI image tags follow the same version scheme as the aetherpak CLI releases. Each release creates a corresponding pair of image tags — one for each variant :
| Tag | Description |
|---|---|
v0.15.1 | Standard image, CLI v0.15.1 |
v0.15.1-builder | Builder image, CLI v0.15.1 |
latest | Standard image, most recent release |
latest-builder | Builder image, most recent release |
main | Standard image, latest commit on main branch |
main-builder | Builder image, latest commit on main branch |
v0.15.1-amd64 | Standard image, CLI v0.15.1, amd64 only |
v0.15.1-arm64 | Standard image, CLI v0.15.1, arm64 only |
v0.15.1-builder-amd64 | Builder image, CLI v0.15.1, amd64 only |
v0.15.1-builder-arm64 | Builder image, CLI v0.15.1, arm64 only |
During container image development, pull request builds create temporary pr-{N} and pr-{N}-builder tags. These are not pushed to the registry — they exist only for local testing purposes .
When Tags Are Published#
- Version tags (
v*): Published when a commit is pushed with arefs/tags/v*ref. Bothlatestand the specific version tag (e.g.v0.15.1) are applied simultaneously . maintag: Published on every push to themainbranch.latestrefresh: Thelatestandlatest-buildertags are also rebuilt weekly (every Sunday at midnight UTC) via therefresh-latestworkflow to pick up updates to the underlyingghcr.io/aetherpak/flatpak-containersbase images — even without a new CLI release .
Pinning Recommendations#
| Use case | Recommended tag |
|---|---|
| Production / reproducible builds | v0.15.1 / v0.15.1-builder (specific version) |
| Always on the latest stable release | latest / latest-builder |
| Tracking development builds | main / main-builder |
| Explicit architecture targeting | v0.15.1-amd64, v0.15.1-builder-arm64, etc. |
Pin to a specific version in production. The
latesttag moves forward with each CLI release, which may introduce behaviour changes. For reproducible CI pipelines, pin to a specific version tag (e.g.v0.15.1) so that your toolchain does not change unexpectedly between workflow runs. Theaetherpak/actionsreusable workflow accepts acli-versioninput for exactly this purpose .