cpak installs applications from OCI images while keeping package metadata in a Git repository. It provides native desktop integration, shared content-addressed layers, atomic updates and a rootless Linux sandbox from one Go binary.
Download cpak-linux-amd64 or cpak-linux-arm64 and SHA256SUMS from the
latest release, verify
the download, then install it in a directory on PATH:
sha256sum -c --ignore-missing SHA256SUMS
install -Dm755 cpak-linux-amd64 "$HOME/.local/bin/cpak"
cpak doctorcpak doctor checks user namespaces, rootless OverlayFS, seccomp, Landlock,
cgroup delegation, display, audio and the host action broker. It
prints a JSON report with cpak doctor --json.
To build from source:
make allThe build produces one cpak binary and does not embed a second container
runtime. It embeds the Adwaita, GTK, KDE and Qt dialog adapters by default. Each
adapter is a small host process linked to the matching system toolkit; cpak
extracts only the selected adapter and falls back to its built-in interface if
the toolkit or adapter is unavailable.
Distributions can limit the embedded adapters without changing runtime code:
make all UI_ADAPTERS=adwaita DIALOG_BACKEND=adwaita
make all UI_ADAPTERS=kde,qt
make all UI_ADAPTERS=builtinThe matching Go build tags are cpak_ui_adwaita, cpak_ui_gtk, cpak_ui_kde,
cpak_ui_qt and cpak_ui_builtin. The Makefile compiles and embeds only the
selected adapter binaries before invoking go build with those tags. A package
that invokes Go directly can use the same tags and install its matching
cpak-ui-* executables below /usr/libexec/cpak/ui instead of embedding them.
At runtime, CPAK_UI_ADAPTER selects auto, builtin, adwaita, gtk, kde
or qt. A persistent system or user choice uses the same value in cpak.json:
{
"desktop": {
"dialog_backend": "adwaita"
}
}The environment overrides configuration, configuration overrides the build default, and an unavailable selection always falls back to the built-in dialog.
Store application pages also provide a signed graphical installer. Each
download contains the matching cpak binary and the selected package identity.
The signed metadata also pins the SHA-256 of the complete installer. The
installer verifies both before writing cpak to ~/.local/bin and installing the
application. A browser may save the file without its executable
bit; enable execution in the file manager or run chmod +x once before opening
it.
Install and run an application by its package repository:
cpak install github.com/bottlesdevs/bottles
cpak run github.com/bottlesdevs/bottles bottlesOther common operations:
cpak list
cpak update
cpak self-update --check
cpak stop github.com/bottlesdevs/bottles
cpak audit
cpak audit --repairAliases name installed applications without changing their stored origin:
cpak alias set bottles github.com/bottlesdevs/bottles
cpak run bottles bottles
cpak update bottles
cpak alias list --json
cpak alias remove bottlesAlias names are case-insensitive and stored as lowercase letters, digits and hyphens. They are local to the cpak store and resolve only for installed applications.
Applications can be pinned to a branch, release or commit. If none is selected,
cpak follows the main branch.
An application declares which optional packages it supports. Enabled addons are mounted above the application layers without expanding its permissions. This is useful for SDKs in an editor:
cpak addon list github.com/containerpak/vscode
cpak addon enable github.com/containerpak/vscode github.com/containerpak/sdk-go
cpak addon enable github.com/containerpak/vscode github.com/containerpak/sdk-node-ltsThe selection belongs to that application. Disabling an addon rebuilds its runtime view, and an addon cannot be removed while another installed package is using it.
Each package repository contains a strict cpak.json manifest. Unknown fields
and declared features that cpak cannot apply are rejected.
{
"$schema": "https://raw.githubusercontent.com/Containerpak/cpak/v2/schema/manifest-v2.json",
"manifest_version": "2.0",
"name": "Example",
"description": "Example application.",
"version": "1.0.0",
"image": "ghcr.io/example/example:main",
"image_ref": "source",
"binaries": ["/usr/bin/example"],
"desktop_entries": ["/usr/share/applications/example.desktop"],
"dependencies": [],
"addons": [],
"idle_time": 0,
"override": {
"socketWayland": true,
"deviceDri": true,
"filesystem": [{"path": "home", "access": "read-write"}],
"network": true
}
}Create, validate and migrate manifests with the CLI:
cpak init --help
cpak validate cpak.json
cpak gen-schema --output schema/manifest-v2.json
cpak migrate-manifest cpak.jsonDependencies are installed with the application. A dependency uses nested
mode by default and runs as its own cpak through the parent service. Set its
mode to layer when the parent needs the dependency files in the same rootfs:
"dependencies": [
{"origin": "github.com/example/runtime", "mode": "layer"}
]Layer dependencies do not export their binaries or permissions. Their OCI layers are mounted below the parent image, so the parent owns the command, configuration and complete permission policy. Addons remain optional and are mounted above the parent image. The structured update result records every effective permission change.
Set image_ref to source when CI publishes OCI tags for each Git branch,
release and commit. cpak then selects the matching tag for the requested Git
reference.
cpak pulls OCI manifests, indexes, configs and layers directly. It does not use Docker, Podman, crane or container credential helpers. Bind credentials for a private repository to its package origin:
cpak auth login github.com/example/private-app --username account
cpak auth status github.com/example/private-app
cpak auth logout github.com/example/private-appDesktop sessions store secrets through Secret Service. With --secret-file,
cpak keeps the secret in the user-owned mode 0600 file and stores only its
absolute path in the binding. A binding is restricted to one package origin,
registry host and OCI repository path.
Resolve the manifest and its dependency graph to immutable OCI digests:
cpak lock cpak.jsonThe resulting cpak.lock.json includes the validated dependency manifests, so
later checks do not follow moved image tags. Both development commands infer the
Git repository origin and use the lock file beside the manifest when present:
cpak test cpak.json
cpak test cpak.json --binary example -- --version
cpak dev cpak.jsoncpak test installs the package and checks every declared binary and desktop
entry. cpak dev also launches the selected binary. Both use a temporary cpak
store and do not export files to the desktop or change installed applications.
cpak creates user, mount, PID, IPC, UTS, cgroup and optional network namespaces directly through the Linux kernel. A per-container PID 1 owns the lifecycle and accepts bounded local execution requests over a private Unix socket. OverlayFS combines immutable OCI layers with disposable runtime state.
The runtime applies no_new_privs, seccomp and Landlock where the host kernel
supports it. Filesystem paths, devices, sockets, networking, process sharing and
host actions are controlled by the manifest and user overrides. Nested user
namespaces remain blocked unless an application declares userNamespaces,
which lets browser sandboxes create their inner boundary.
Desktop notifications and external URIs use the system broker instead. It is
enabled with the notification and openURI permissions and exposes only the
matching shim. The application never receives the host D-Bus socket or command.
Applications keep a private persistent home unless the manifest explicitly mounts the host home. Packages can let users select host files without granting a directory in advance:
"filePicker": {
"openFile": true,
"openFolder": true,
"saveFile": true,
"persistent": true,
"containingFolder": true
}The desktop adapter uses the native file chooser. A selected file is mounted
read-only below /run/cpak/grants and the application receives that path. The
user can explicitly grant its containing folder when the package permits this
choice. Folder selections are read-only. Save destinations mount the selected
parent directory read-write. If the desktop chooser cannot show the scope and
lifetime choices, cpak uses the configured desktop dialog backend or its
built-in dialog.
Paths already exposed by filesystem keep their normal in-container location
and do not trigger another confirmation. The built-in confirmation follows the
host light or dark preference and accent color through
org.freedesktop.appearance.
Use home/path when an application only needs one portable path below the
user's home instead of the complete home scope:
"filesystem": [
{"path": "home/.local/share/example", "access": "read-write"}
]Session grants disappear with the running environment. Persistent grants are restored on later launches and can be inspected or revoked:
cpak grant list github.com/example/app
cpak grant manage github.com/example/app
cpak grant revoke github.com/example/app GRANT_IDGTK applications use the same mechanism through their normal file chooser.
Other applications can call the policy-gated cpak-file-picker shim. The core
grant protocol uses a private Unix socket and does not depend on a desktop bus.
The broker authenticates the package token, validates the requested capability
and passes an opened file descriptor to the existing container namespace. The
runtime verifies that the selected object did not change before attaching a
restricted mount. It does not trust a host path supplied by the application.
File selection fails closed when no desktop session is available. Headless
packages must use paths declared in filesystem or receive data through a
separate typed integration instead of starting an interactive picker.
The same broker can expose a typed container provider without granting a host shell:
"hostActions": [
{
"provider": "containers",
"capabilities": ["read", "manage-owned", "exec-owned"]
}
]podman and docker shims parse supported CLI operations inside the cpak and
send a closed request to the broker. Each shim selects its matching host engine.
Standard output, standard error, exit codes and cancellation are returned to the
caller. Read access can inspect host containers. Mutation and execution are
restricted to containers created by the requesting package and marked with its
ownership label. A nested container can mount only paths already granted to the
parent cpak, and a read-only grant cannot be promoted. Unsupported flags, host
namespaces, devices and privileged mode are rejected before the container
backend is started. There is no generic host command action.
Resource limits use delegated cgroup v2 controllers when available. Hosts without a compatible cgroup manager can run applications without limits; a requested limit fails with a direct diagnostic instead of being ignored.
A package can offer a complete Wayland desktop or a focused kiosk as a login session while remaining usable as a normal application package:
"sessions": [
{
"id": "dev.sinty.singularity",
"name": "Singularity Desktop",
"description": "Singularity Desktop session",
"kind": "desktop",
"entrypoint": "/usr/bin/singularity-session",
"override": {
"socketWayland": true,
"deviceDri": true,
"hostApplications": true,
"filesystem": [
{"path": "xdg-documents", "access": "read-write"},
{"path": "xdg-download", "access": "read-write"}
]
}
}
]Install the small system authority once, then register a session from an installed package:
cpak system setup
cpak session list github.com/singularityos-lab/singularity-desktop
cpak session enable github.com/singularityos-lab/singularity-desktop dev.sinty.singularityThe system authority accepts only session registration and removal. Every
change passes through Polkit, every field is validated, and the display manager
entry calls a fixed cpak launcher with a registered identifier. Package paths or
commands never enter the privileged request. The desktop uses the same cpak
profile and user data as a windowed launch. Removing a package also removes the
sessions which no remaining installed version provides. cpak system remove
removes registered cpak sessions before uninstalling the authority.
cpak deduplicates package data at two levels. OCI layers are addressed by digest, so an unchanged base or dependency layer is downloaded and stored once. FVS then shares equal content-defined blocks across files and layers. Persistent native checkouts reuse complete files through reflinks or hard links where the filesystem supports them. DaBaDee remains available as a compatible storage driver and as the explicit path deduplication tool.
Application launch reads an atomic index of prepared layer directories and passes them directly to rootless OverlayFS. Storage preparation and publication complete before the runtime index becomes active. Updating cpak prepares existing stores before installing or updating an application. If that preparation was interrupted, the next desktop launch shows progress, resumes completed layers, and starts the application after publication.
Inspect, prepare, and verify the selected storage driver with:
cpak storage status
cpak storage migrate
cpak storage verify
cpak storage verify --repairThe storage driver protocol is versioned independently from cpak and uses a private Unix socket. The built-in FVS and DaBaDee providers implement the same contract. External drivers can use any language, but cpak accepts their paths only below the configured driver root and refuses to start an external driver when the host cannot confine it.
Installs and updates stage data before changing the active application record. Interrupted updates are recovered on the next start, while garbage collection retains every layer referenced by an installed package.
The package origin remains a normal Git repository, so the manifest can follow a branch, release or immutable commit while the OCI digest records the exact image that was installed.
The full user and package author documentation is available at cpak.it.
Contributions are accepted under the Contributor License Agreement. See CONTRIBUTING.md before opening a pull request.
cpak is free software licensed under the GNU Lesser General Public License v2.1. Accepted contributions remain available under LGPL-2.1-only.