Environment setup¶
Everything runs as an ordinary user: rootless podman for Artifact Keeper and all image
builds, and an unprivileged QEMU process for the edge node (a QEMU VM in this PoC).
No sudo for the pipeline itself; two host settings may need an administrator once
(kvm group membership, vm.max_map_count). It was developed on Fedora 44; any recent
Linux with the tools below should work.
Requirements¶
On Fedora:
sudo dnf install podman skopeo cosign qemu-system-x86 edk2-ovmf curl python3 jq gnupg2 docker-compose uv
docker-compose is only there as the provider behind podman compose (podman-compose
works too); uv is only needed to build this documentation site.
| Tool | Used for | Tested with |
|---|---|---|
| podman 5.x (rootless) | Artifact Keeper stack, base, RPM and image builds | 5.8.7 |
podman compose |
starting the Artifact Keeper compose file | delegating to docker-compose v5.5.1 |
| skopeo | inspecting, copying and promoting tags in the registry | 1.22.3 |
| cosign | signing and verifying images | 3.1.3 (see the note below) |
| gpg | RPM signing key, repodata signature checks | GnuPG 2.4.9 |
| jq, curl, python3 | scripts, API calls, serving the kickstart | |
| qemu-system-x86_64, edk2-ovmf | the edge node VM and its UEFI firmware | QEMU 10.2.2 |
| uv | building this documentation site | 0.12 |
Other requirements:
- SSH key pair. The deploy scripts use
SSH_KEYfor the private key (default~/.ssh/id_ed25519) andSSH_PUBKEYfor the public key (default$SSH_KEY.pub). The public key is injected into the node with the kickstartsshkeycommand; root's password is locked, so this key is the only way in. - RAM: about 8 GB free for the VM (6 vCPU, 8 GiB by default;
VM_SMP,VM_MEM), plus what the Artifact Keeper stack uses (Postgres, OpenSearch, backend, web, Caddy). - Disk: about 7 GB for images and install media (including the cached 750 MB installer
stage2), plus the VM's 40 GB sparse qcow2 (
VM_DISK_SIZE) and Artifact Keeper's volumes.
cosign version
cosign 3 signs in the Sigstore bundle format via the OCI referrers API by default.
podman, skopeo, bootc and the installer cannot see that format, so signing/lib.sh
passes the deprecated --new-bundle-format=false --use-signing-config=false --tlog-upload=false
flags. Pin cosign (3.1.3 works) until containers-image reads bundles. Details in the
signing log.
Preflight¶
make preflight (deploy/preflight.sh) checks the host before anything is started and
changes nothing:
- the tools above, a
podman composeprovider and the OVMF firmware; - that podman runs rootless for your user;
- whether
/dev/kvmexists and is read-write for you (a warning, not a failure: see below); vm.max_map_countis at least 262144 (OpenSearch);- the SSH key pair at
SSH_KEY/SSH_PUBKEY; - ports 30080 (free, or already answering as Artifact Keeper), 8000 and 2222 (free, or held by the running VM).
== KVM
OK /dev/kvm usable: VMs run with -accel kvm
== vm.max_map_count (OpenSearch)
OK vm.max_map_count = 2147483642
...
preflight: all required checks passed (0 WARN)
It exits non-zero if any check reports FAIL. SSH_KEY, SSH_PUBKEY, KS_PORT and
SSH_PORT are honoured as in the rest of the harness.
Rootless podman¶
- Compose.
registry/up.shusespodman compose. The stock Artifact Keeper compose file (fixed172.30.0.0/24network,depends_on.required: false,service_completed_successfully,!resetin the override) worked without changes. If your compose provider talks to the podman socket, start it withsystemctl --user enable --now podman.socket. - Networking. Rootless podman 5 uses pasta. From inside a container or a
podman build, the host ishost.containers.internal; every build in this repository reaches Artifact Keeper ashost.containers.internal:30080. - Short names. Rootless podman's
short-name-mode = "enforcing"matched a shortpostgres:18-alpineagainst an unrelated locally cached image, so the compose override fully qualifies every image (docker.io/library/...). - OpenSearch needs
vm.max_map_countof at least 262144. Ifmake preflightflags it, an administrator raises it once (sysctl -w vm.max_map_count=262144, plus a file in/etc/sysctl.d/to keep it). - Builds that run the RESF recipe need
--security-opt=label=disable --cap-add=all --device /dev/fuse, which rootless podman allows as is.
KVM or not¶
No /dev/kvm? Everything still works under software emulation (QEMU TCG), about 5x slower
(install about 10 minutes, power-on to a working cluster about 17 minutes). deploy/lib.sh
picks -accel kvm -cpu host when /dev/kvm is read-write for you and
-accel tcg,thread=multi -cpu max otherwise; the harness timeouts are sized for TCG.
To enable KVM, check the kernel log:
kvm_amd: SVM disabled (by BIOS) in MSR_VM_CR (or, on Intel, kvm_intel reporting VMX
disabled by BIOS) means the CPU supports virtualization but the firmware has it switched off:
enable SVM Mode / AMD-V or Intel Virtualization Technology (VT-x) in the BIOS or
UEFI setup and reboot. If /dev/kvm then exists but is group-only, your user must be in the
kvm group (an administrator change).
Stage-by-stage numbers for both modes are on the Timings page.
Files the scripts write under ~/.config/containers¶
The build host verifies the base image's signature before building on it.
image/setup-host.sh (run by the image build) writes only under
${XDG_CONFIG_HOME:-~/.config}/containers/:
| File | Why |
|---|---|
registries.conf.d/50-artifact-keeper-local.conf |
marks localhost:30080 insecure (plain HTTP), so FROM, pull and push work without --tls-verify=false |
policy.json |
a copy of the system policy plus a rule requiring our cosign signature for localhost:30080/oci-bootc |
registries.d/ak-oci-bootc.yaml |
tells containers-image to look for cosign's sha256-<digest>.sig tags |
registries.d/*.yaml (symlinks) |
links to each /etc/containers/registries.d/*.yaml; see the warning below |
Content of the three files
registries.conf.d/50-artifact-keeper-local.conf:
policy.json (added scope; the rest is a copy of /etc/containers/policy.json, or an
insecureAcceptAnything default if there is none):
{ "transports": { "docker": {
"localhost:30080/oci-bootc": [{
"type": "sigstoreSigned",
"keyPath": "<repo>/signing/keys/pub/edge-cosign.pub",
"signedIdentity": { "type": "matchRepository" }
}]
}}}
registries.d/ak-oci-bootc.yaml:
User-level files replace the system ones
A user-level ~/.config/containers/policy.json replaces /etc/containers/policy.json
for your user, and a user-level registries.d/ replaces /etc/containers/registries.d/.
That is why the script starts from a copy of the system policy and symlinks the system
registries.d files next to its own. If your system policy changes later, your copy
does not follow it.
The matchRepository identity is deliberate: cosign records a tag-less identity
(localhost:30080/oci-bootc/rocky-edge), and the default matchRepoDigestOrExact rejects
it with Signature for identity ... is not accepted.
Undoing it¶
C="${XDG_CONFIG_HOME:-$HOME/.config}/containers"
rm -f "$C/registries.conf.d/50-artifact-keeper-local.conf"
rm -f "$C/registries.d/ak-oci-bootc.yaml"
# the symlinks to /etc/containers/registries.d/*.yaml (only the links, not the targets)
find "$C/registries.d" -maxdepth 1 -type l -lname '/etc/containers/registries.d/*' -delete
rmdir "$C/registries.d" 2>/dev/null || true
# policy.json: delete it if setup-host.sh created it (the system policy applies again) ...
rm -f "$C/policy.json"
# ... or, if you had your own before, just drop the added scope:
# jq 'del(.transports.docker["localhost:30080/oci-bootc"])' "$C/policy.json" > p && mv p "$C/policy.json"
Other generated state (all gitignored)¶
| Path | What |
|---|---|
registry/.env |
admin password (user admin), JWT secret, webhook key, generated by up.sh (mode 600) |
registry/.ak-token |
CI API token (read:artifacts, write:artifacts, 30 days), reused while valid |
registry/out/ |
generated edge.repo, edge.repo.in, README-urls.md |
signing/keys/ |
cosign key pair, RPM GPG home, cosign's registry auth, public keys (mode 700; PoC keys have no passphrase) |
deploy/cache/ |
Rocky 10.2 pxeboot vmlinuz, initrd.img, stage2 install.img, sha256-checked |
deploy/state/ |
VM disk, OVMF vars, serial logs, timings.log, rendered kickstart (contains your public key) |
./registry/down.sh -v deletes Artifact Keeper's volumes; make vm-clean deletes
deploy/state/ (CLEAN_CACHE=1 also drops the media cache); make clean removes the
local build scratch directories.
Ports¶
| Port | Bound by | Use |
|---|---|---|
30080 |
Caddy in the Artifact Keeper stack | everything: web UI, /api/v1, /rpm/<key>, OCI /v2. The VM reaches it as 10.0.2.2:30080 |
30443 |
Caddy | HTTPS with Caddy's internal CA for localhost; not used here |
8000 |
deploy/serve-ks.sh (python3 -m http.server on 0.0.0.0) |
kickstart and the cached installer stage2, during vm-install only |
2222 |
QEMU user-mode hostfwd |
ssh to the node: ssh -p 2222 root@localhost (make vm-ssh) |
30090 |
inside the VM | the nginx-demo NodePort (not forwarded to the host by default) |
Change them with HTTP_PORT / HTTPS_PORT in registry/.env, and KS_PORT / SSH_PORT
for the harness. Postgres, OpenSearch, the backend (8080) and the web UI (3000) are only
reachable on the compose network; the stock compose file publishes Postgres and
OpenSearch on the host, and the override in registry/compose/compose.override.yml removes that.