Three recent releases together cover the container build workflow in Woodpecker CI: a Podman-based build plugin for any Woodpecker agent, a private agent on Hetzner Cloud as a home for pipelines with secrets or large builds, and a filter proxy that decides which plugin images may run privileged on your own agent.
woodpecker-ci-plugin-podman0.1.0 - a directpodman build/podman pushwrapper that produces multi-arch manifest lists in one stepchilly-willy-agentv0.3.0 - a private agent on Fedora CoreOS plus Hetzner Cloudwally-walrus-agent-guardv0.2.0 - a filtering proxy between the agent and the Podman socket, now with an active privilege policy
Woodpecker CI Plugin Podman 0.1.0#
A direct wrapper around podman build, podman push, and podman manifest push --all. The plugin keeps the same interface that developers already know from running podman locally.
Codeberg’s public CI: the plugin runs out of the box. It needs nothing beyond CLONE_NEWUSER, which the default seccomp profile permits. Storage falls back to vfs (no /dev/fuse on the shared agents), and multi-arch builds work as far as the runner hosts ship the necessary binfmt_misc configuration.
Self-hosted agents (Chilly Willy Agent v0.3.0 as the reference setup): overlay plus fuse-overlayfs (much faster for layer-heavy builds), real cross-arch RUN steps for arbitrary manifest list entries, and privilege force-escalation through Agent Guard.
steps:
- name: publish
image: head1328/woodpecker-ci-plugin-podman
settings:
repo: head1328/myapp
tags: latest
platforms: linux/amd64,linux/arm64
username:
from_secret: docker_hub_user
password:
from_secret: docker_hub_tokenA deliberate design choice: the plugin never registers binfmt_misc inside its own container. Since Linux 6.7 binfmt_misc is scoped per user namespace, and in nested rootless Podman qemu handlers cannot be set reliably inside the build sub-runtime. The plugin defers to the host instead: if the host registers qemu handlers with the F (fix-binary) flag, cross-arch RUN steps just work; if not, the build fails cleanly.
Chilly Willy Agent v0.3.0#
The agent provisions a private Woodpecker agent on Hetzner Cloud and registers it with a Woodpecker instance (e.g. Codeberg’s). A full introduction is in the initial-release post ; 0.3.0 brings three larger changes:
Native multi-arch in the host kernel: on first boot a systemd oneshot unit layers
qemu-user-staticviarpm-ostree installand reboots. On second bootsystemd-binfmtregisters the qemu interpreters from/usr/lib/binfmt.d/qemu-*-static.confwith theFflag in the host kernel. That flag opens the interpreter file descriptor at registration time and exposes it to every nested rootless user namespace - including Podman’s build sub-runtime.[Unit] Description=Layer qemu-user-static via rpm-ostree for cross-arch builds After=hetzner-ipv6.service network-online.target nss-lookup.target Before=zincati.service boot-complete.target ConditionPathExists=!/var/lib/cwa-qemu-layered.stamp [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/rpm-ostree install --allow-inactive qemu-user-static ExecStart=/usr/bin/touch /var/lib/cwa-qemu-layered.stamp ExecStart=/usr/bin/systemctl rebootTwo presets:
secure(high-security baseline, vfs+chroot, no privileged plugins) andprivileged(overlay+FUSE, privilege escalation via allowlist). Both inherit the qemu binfmt handlers from the host kernel and can therefore build multi-arch manifests.Wally Walrus Agent Guard v0.2.0 integration plus a deterministic update window: the agent talks to Podman through the guard socket, and Zincati reboots are bounded to a daily 03:00 to 05:00 UTC window instead of the uncontrolled
immediatestrategy.
Wally Walrus Agent Guard v0.2.0#
Agent Guard is a filtering reverse proxy that sits between the Woodpecker agent and the Podman socket. Every POST /containers/create is inspected and matched against the agent operator’s privileged_images allowlist.
With 0.2.0 the passive filter turns into an active policy. Three cases:
- Image on the allowlist, server sends
Privileged=true: passed through. - Image on the allowlist, server sends
Privileged=false: Agent Guard force-escalates toPrivileged=true(new in 0.2.0). The agent operator independently decides which plugin images run privileged on this agent - regardless of what the upstream server allowed. - Image not on the allowlist, server sends
Privileged=true: HTTP 403, the container-create is rejected.
This matters in the Codeberg world: Codeberg maintains WOODPECKER_PLUGINS_PRIVILEGED as a central list. Which plugins Codeberg lets run privileged is the Codeberg admins’ call, not the repository owner’s. If you want a different plugin (or a different version) to run privileged on your own agent without colliding with the Codeberg list, Agent Guard can force-escalate it on the agent side, scoped strictly to your own allowlist.
How the three play together in the privileged preset#
The demo pipeline .woodpecker/privileged.yml in the agent repo exercises the setup end-to-end with four podman-plugin steps: each against the rolling :next tag and against the digest-pinned release tag :0.1.0@sha256:c3f066..., once single-arch (linux/amd64) and once multi-arch (linux/amd64,linux/arm64).
The multi-arch run actually does this:
- Woodpecker sends the container-create to the agent API.
- Codeberg does not have
head1328/woodpecker-ci-plugin-podmanon its server allowlist, so the request arrives withPrivileged=false. - Agent Guard finds the image in
WALLY_GUARD_PRIVILEGED_IMAGES, rewrites the request toPrivileged=true, and forwards it to Podman. - The plugin runs inside the privileged pipeline container and invokes
podman build --platform=linux/amd64,linux/arm64 --manifest=.... - For the linux/arm64 build,
podman buildopens a fresh rootless userns. The first exec of an arm64 binary (/bin/shfrom the Alpine image) triggers thebinfmt_misclookup in the kernel; the kernel findsqemu-aarch64with theFflag and reuses the already-open interpreter fd directly. RUN apk add jq ca-certificatesruns inside the arm64 container throughqemu-aarch64-static.uname -mreportsaarch64.
Licenses#
All three projects are Apache 2.0.
Acknowledgments#
- Woodpecker CI - container-native CI agent
- Codeberg - Forgejo-based Git forge with Woodpecker CI integration
- Fedora CoreOS - minimal, transactionally updated container host OS
- Podman - daemonless container runtime
- QEMU
- machine emulator (
qemu-user-staticfor userspace emulation) - Hetzner Cloud - cloud provider with IPv6-only servers
Found this helpful?
Consider supporting via:
