Documents
Getting Started with the AetherPak CLI
Getting Started with the AetherPak CLI
Type
Document
Status
Published
Created
Jun 4, 2026
Updated
Jun 4, 2026
Updated by
Dosu Bot

Getting Started with the AetherPak CLI#

1. Introduction#

AetherPak CLI is a standalone, performance-focused command-line tool for building, publishing, and managing Flatpak applications using OCI-backed distribution via GitHub Container Registry (GHCR) and static index hosting via GitHub Pages . Rather than treating your Flatpak workflow as a black box, the CLI exposes the full pipeline as composable, scriptable commands you can mix and match in any environment — local workstations, CI runners, or custom automation.

Plumbing vs. Porcelain#

The CLI follows a Plumbing vs. Porcelain design philosophy, modelled directly on Git :

  • Plumbing (Primitives) — Independent, highly-scoped, single-responsibility commands suited for complex workflows and custom CI matrices. These include plan, build, import, push-oci, build-site, inspect-repo, and resolve-channel.
  • Porcelain (Convenience Wrappers) — High-level commands that coordinate multiple plumbing modules automatically in-memory, providing a streamlined experience for the most common workflows. These include status, add, publish, and release.

The separation means you can use the high-level publish command for day-to-day work, but drop down to build + push-oci individually whenever you need fine-grained control — for example, to insert custom steps between build and publish, or to drive a dynamic CI matrix.

Configuration is Optional#

A key design principle of the CLI is that most commands work without a configuration file. You can pass all parameters as CLI flags or AETHERPAK_-prefixed environment variables (e.g. AETHERPAK_REGISTRY) . The aetherpak.yaml file becomes valuable when you want to manage multiple apps, persist defaults, or drive the automated plan/release cycle — but it is never required for one-off operations or quick testing.

2. Installation#

The AetherPak CLI is a single statically-linked binary for Linux (amd64 and arm64). There are two ways to get it: via the aetherpak/setup-cli GitHub Action for CI environments, or by downloading a release archive manually.

The setup-cli action downloads, extracts, and adds the aetherpak binary to $GITHUB_PATH in a single step . All composite actions in aetherpak/actions call this automatically when the CLI isn't already on PATH, but you can invoke it directly to pin a specific version.

- name: Set up AetherPak CLI
  uses: aetherpak/setup-cli@v1
  with:
    version: 'latest' # or a specific tag, e.g. 'v0.15.1'
    install-dependencies: 'true' # installs flatpak, ostree, flatpak-builder, etc.

Inputs :

InputDefaultDescription
versionlatestCLI version tag to install (e.g. v0.15.1). Use latest to always pull the newest release.
repoaetherpak/cliGitHub repository to download releases from.
install-dependenciestrueWhen true, installs missing system packages: flatpak, ostree, flatpak-builder, patch, unzip, bzip2, and zstd. Uses apt-get, dnf, or microdnf depending on the runner.

The action is idempotent — if the requested version is already on PATH, it skips the download entirely . It supports Linux runners only (amd64 and arm64) and installs the binary to $RUNNER_TEMP/aetherpak-bin .

Tip: When you're using the aetherpak/actions reusable workflow, all composite actions call setup-cli automatically if the CLI isn't already on PATH. You only need to invoke setup-cli manually when pinning to a specific version or running on a bare ubuntu-latest runner without the AetherPak container.

To pin to a specific version and skip dependency installation (e.g. inside a container that already has the toolchain):

- uses: aetherpak/setup-cli@v1
  with:
    version: v0.15.1
    install-dependencies: 'false'

Manual Installation#

Pre-built release archives are published to github.com/aetherpak/cli/releases for every tagged release . Archives are named aetherpak-linux-amd64.tar.gz and aetherpak-linux-arm64.tar.gz.

# Example: download and install the latest release for amd64
VERSION=$(curl -s https://api.github.com/repos/aetherpak/cli/releases/latest \
  | grep '"tag_name"' | cut -d'"' -f4)
curl -sL "https://github.com/aetherpak/cli/releases/download/${VERSION}/aetherpak-linux-amd64.tar.gz" \
  | tar -xz
chmod +x aetherpak
sudo mv aetherpak /usr/local/bin/

Verify the installation:

aetherpak --version
aetherpak status

3. Zero-Config Commands#

Many AetherPak CLI commands work without a configuration file. Rather than requiring an aetherpak.yaml, they accept all parameters as CLI flags or AETHERPAK_-prefixed environment variables — for example, AETHERPAK_REGISTRY=ghcr.io or AETHERPAK_OCI_REPOSITORY=myorg/myrepo . This makes them ideal for one-off operations, quick local testing, and CI/CD pipelines where configuration is supplied by the environment rather than a committed file.

The commands below are listed roughly in the order you'd use them through the pipeline.


aetherpak status#

Validates that required system dependencies are present, checks any configuration file, and optionally decrypts and verifies GPG keys .

# Quick dependency check
aetherpak status

# Verify a GPG signing key
aetherpak status --gpg-key /path/to/key.asc --gpg-key-passphrase "$GPG_PASSPHRASE"

# Machine-readable JSON output (useful in CI scripts)
aetherpak status --json

Key flags :

FlagDescription
--gpg-key <path>GPG private key block(s) or path(s) to verify signing setup
--gpg-key-passphrase <value>Passphrase to test key decryption
--jsonOutput raw diagnostics as JSON for script parsing

aetherpak build#

Wraps flatpak-builder in a managed sandbox. It automatically installs/resolves flatpak-builder-lint, runs pre-build linting on the manifest, executes the build with ccache/state management, and runs post-build linting on the generated OSTree repository .

# Build for x86_64 (default)
aetherpak build --manifest apps/org.example.App/manifest.json --arch x86_64

# Build for aarch64 with linting enabled
aetherpak build --manifest apps/org.example.App/manifest.json --arch aarch64 --run-linter

# Build with an extra flatpak remote and SDK pre-installed
aetherpak build \
  --manifest manifest.json \
  --arch x86_64 \
  --flatpak-remote flathub=https://dl.flathub.org/repo/flathub.flatpakrepo \
  --flatpak-dep flathub:org.gnome.Sdk//47

Key flags :

FlagDefaultDescription
--manifest <path>Path to the Flatpak manifest (JSON or YAML)
--arch <arch>x86_64Target CPU architecture
--app-id <id>(from manifest)Reverse-DNS app ID (auto-detected from manifest)
--branch <branch>Flatpak channel/branch
--run-linterRun flatpak-builder-lint before and after build
--repo-path <path>repoDestination OSTree repository path
--ccache-dir <path>.ccacheCCache directory
--flatpak-remote <name=url>Register a Flatpak remote before building (repeatable)
--flatpak-dep <remote:ref>Install a Flatpak dependency before building (repeatable)

Note: build is a plumbing command. For most use cases, prefer aetherpak publish --manifest <path>, which chains build + push-oci in a single step.


aetherpak import#

Ingests a prebuilt .flatpak bundle into a local OSTree repository. The app ID, architecture, and branch are auto-detected from the bundle's metadata when not specified — so you usually only need to provide the URL .

# Download and import a bundle (app-id/arch/branch auto-detected)
aetherpak import --bundle-url https://example.com/org.example.App_x86_64.flatpak

# Verify checksum and set an explicit branch
aetherpak import \
  --bundle-url https://example.com/app.flatpak \
  --bundle-sha256 abc123...64hexchars \
  --branch stable

# Import a local bundle file
aetherpak import --bundle-path ./build/org.example.App.flatpak

Key flags :

FlagDefaultDescription
--bundle-url <url>HTTP URL of the bundle to download (repeatable)
--bundle-path <path>Local path to a .flatpak file; supports globs (repeatable)
--bundle-sha256 <hex>Expected SHA-256 checksum (verified; computed if omitted)
--app-id <id>(from bundle)Override the app ID
--arch <arch>(from bundle)Override the architecture
--branch <branch>(from bundle)Override the Flatpak channel
--repo-path <path>repoDestination OSTree repository path

aetherpak push-oci#

Converts a local OSTree repository branch to an OCI container image and pushes it to a registry. This is the plumbing command underlying the OCI publish step .

# Push to GHCR (unsigned — useful for local dev)
aetherpak push-oci \
  --app-id org.example.App \
  --registry ghcr.io \
  --oci-repository myorg/myrepo \
  --no-sign

# Push with GPG signing
aetherpak push-oci \
  --app-id org.example.App \
  --registry ghcr.io \
  --oci-repository myorg/myrepo \
  --gpg-key /path/to/key.asc

Key flags :

FlagDefaultDescription
--app-id <id>Reverse-DNS app ID
--arch <arch>x86_64Target architecture
--branch <branch>Flatpak channel/branch
--registry <host>OCI registry host (e.g. ghcr.io)
--oci-repository <path>Registry repository path (e.g. myorg/myrepo)
--repo-path <path>repoLocal OSTree repository path
--gpg-key <path>GPG private key for signing OCI manifests
--no-signDisable GPG signing entirely
--allow-unsignedAllow pushing without a signing key

aetherpak publish#

A porcelain command that chains build (or import) and push-oci sequentially in memory. This is the recommended starting point for one-off or zero-config publishing — use it instead of calling build and push-oci separately unless you need to insert custom steps between them .

# Build from a manifest and push to GHCR
aetherpak publish \
  --manifest apps/org.example.App/manifest.json \
  --registry ghcr.io \
  --oci-repository myorg/myrepo

# Import a bundle and push
aetherpak publish \
  --bundle https://example.com/app.flatpak \
  --registry ghcr.io \
  --oci-repository myorg/myrepo

# Disable signing for local testing
aetherpak publish \
  --manifest manifest.json \
  --registry ghcr.io \
  --oci-repository myorg/myrepo \
  --no-sign

aetherpak plan#

A plumbing command that computes the build matrix — which apps and architectures need to be built. When given a manifest directly, it expands it into the requested architectures. When given a config file with --base-sha, it compares git history to find only the apps that changed .

# Compute a build matrix for a single manifest
aetherpak plan --manifest apps/manifest.json --arch x86_64 --arch aarch64

# Config-driven planning with change detection
aetherpak plan --config aetherpak.yaml --base-sha "$GITHUB_SHA" --output matrix

# Output as a GitHub Actions matrix JSON
aetherpak plan --manifest manifest.json --arch x86_64 --output matrix

Key flags :

FlagDescription
--manifest <path>Single manifest path (bypasses config)
--arch <arch>Architectures to include (repeatable)
--base-sha <sha>Git commit SHA to diff against for change detection
--force <value>all to rebuild everything, or a specific app ID
--output <format>json, matrix, matrix-manifest, matrix-bundle, or apps
--branch <branch>Branch/channel to use (defaults to stable)
--disable-linterDisable linting for all planned apps

4. Bootstrapping a Multi-App Repository#

When you want to manage multiple Flatpak apps from a single repository, AetherPak uses an aetherpak.yaml configuration file. There is no init command — instead, you use aetherpak add repeatedly to incrementally build up the configuration, one app at a time .

aetherpak add#

The add command creates or modifies aetherpak.yaml by adding one application entry from one of three source types :

  1. Local manifest — the app ID is auto-detected from the manifest file
  2. Bundle URL — the bundle is downloaded, fingerprinted, and its SHA-256 is recorded
  3. Git repository — the repo is added as a recursively-initialized submodule with the manifest auto-detected inside it

After resolving the app, a colored diff is displayed showing exactly what will be written to aetherpak.yaml. Changes are only applied after you confirm. Pass -y / --confirm to skip the prompt in non-interactive scripts.

When run on a TTY without any source flags, add launches an interactive wizard that walks you through choosing the source type and filling in the required details .

Adding from a local manifest#

# App ID is detected automatically from the manifest
aetherpak add --manifest apps/org.example.App/manifest.json

# Skip the diff confirmation
aetherpak add --manifest apps/org.example.App/manifest.json -y

# Specify arch and branch explicitly
aetherpak add \
  --manifest apps/org.example.App/manifest.json \
  --arch x86_64 --arch aarch64 \
  --branch stable

Adding from a bundle URL#

# SHA-256 is computed automatically on first download and recorded
aetherpak add \
  --bundle-url https://example.com/org.example.App_x86_64.flatpak \
  --app-id org.example.App

# Provide the checksum explicitly (skips download for verification)
aetherpak add \
  --bundle-url https://example.com/org.example.App_x86_64.flatpak \
  --app-id org.example.App \
  --bundle-sha256 abc123...64hexchars

Adding from a Git repository#

# The repo is added as a submodule; manifest is auto-detected inside it
aetherpak add --git https://github.com/org/app-manifest.git

# Specify where to place the submodule and which manifest to use
aetherpak add \
  --git https://github.com/org/app-manifest.git \
  --submodule-path manifests/app-manifest \
  --git-manifest org.example.App.json

If the user declines the diff confirmation after a --git add, the submodule addition is automatically rolled back .


Resulting aetherpak.yaml#

After running aetherpak add for a few apps, the resulting configuration might look like this:

registry: ghcr.io
pages_url: https://myorg.github.io/myrepo
oci_repository: myorg/myrepo

apps:
  # Manifest-based app: built from source on each push
  - id: org.example.AppOne
    manifest: apps/org.example.AppOne/org.example.AppOne.json
    arches: [x86_64, aarch64]
    branch: stable

  # Bundle-based app: ingested from upstream prebuilt binaries
  - id: com.example.AppTwo
    branch: stable
    bundles:
      x86_64:
        url: https://upstream.example.com/AppTwo_x86_64.flatpak
        sha256: "abc123...64hexchars"
      aarch64:
        url: https://upstream.example.com/AppTwo_aarch64.flatpak
        sha256: "def456...64hexchars"

Each app requires exactly one of manifest or bundles — not both . For the full aetherpak.yaml schema reference, including global settings, defaults, branding, channel_mappings, and linter configuration, see the CLI README and the Maintainer Guide §5 .


Common add Flags Reference#

FlagDescription
--manifest <path>Path to a local Flatpak manifest (source type 1)
--bundle-url <url>URL of a remote .flatpak bundle (source type 2)
--git <url>Git repository URL to add as a submodule (source type 3)
--app-id <id>Override app ID (required for bundle URL, auto-detected otherwise)
--arch <arch>Target architecture(s), repeatable (defaults to host arch)
--branch <branch>Flatpak channel/branch (default: stable)
--bundle-sha256 <hex>Expected checksum (verified; computed if omitted)
--git-manifest <path>Manifest path within the git repo (auto-detected if omitted)
--submodule-path <path>Submodule destination (default: manifests/<reponame>)
-y, --confirmSkip the diff confirmation prompt
--run-linterEnable linter checks on manifests
--ccacheEnable compiler cache


5. Local Testing & Container Images#

For local end-to-end testing using Docker or Podman Compose, see the Local End-to-End Testing with Podman Compose guide. For details on the CLI container image variants (standard vs. builder), tagging, and usage in CI/CD, see CLI Container Images: Standard and Builder Variants. For the underlying utility images (ghcr.io/aetherpak/flatpak and ghcr.io/aetherpak/flatpak-builder), see Utility Images: flatpak and flatpak-builder.

6. Next Steps#

You now have everything you need to run the AetherPak CLI locally and in CI. Here are the natural next steps depending on what you want to do:

Set Up a Full GitHub Actions Pipeline#

The AetherPak Maintainer Guide covers the complete end-to-end workflow setup : creating the reusable workflow, enabling GitHub Pages, configuring GHCR permissions, and going from a first push to a publicly installable Flatpak repository in minutes.

Configure GPG Signing#

Signed repositories give users cryptographic assurance that the packages they install haven't been tampered with. The Maintainer Guide's GPG Signing section walks through generating a key, adding it as a repository secret, and configuring signing: auto (or signing: gpg) in your workflow. For local testing, pass --gpg-key /path/to/key.asc to push-oci, publish, or release, or use --no-sign to skip signing entirely.

Build for Multiple Architectures#

The build and publish commands accept --arch as a repeatable flag. The plan command accepts multiple --arch values and emits a parallelizable matrix. In aetherpak.yaml, set arches: [x86_64, aarch64] per app (or in defaults) to build for both architectures automatically . The GitHub Actions reusable workflow runs each architecture on a native runner in parallel.

Customize Your Landing Page#

The generated index.html landing page can be fully customized through the branding block in aetherpak.yaml — set a custom logo, accent color, favicon, and footer text . For complete control, supply your own Go HTML template via branding.index_template (or the --index-template flag on build-site and release). See the Custom Index Templating section of the CLI README for the full template context and available helper functions .

Explore the Full CLI Reference#

Every command, flag, and environment variable is documented in the AetherPak CLI README . The --help flag on any subcommand also prints the full flag listing inline:

aetherpak --help
aetherpak build --help
aetherpak publish --help
aetherpak release --help