Ward is a disposable Arch Linux environment for pi and its shared tmux server. It runs as a rootless Podman container under the user systemd instance. It exists to give a coding agent an exact, always-current toolchain that can be rebuilt and discarded at any time, without touching host state.
The image builds from archlinux:latest and installs the full package set
in one transaction at build time; a daily rebuild keeps it current. The
container runs with dropped capabilities and private namespaces; its
writable root is discarded on stop, and only declared bind
mounts — projects, selected configuration, and agent state — persist. The
tmux server runs in the container and exposes its socket, so sessions are
driven by host clients. Networking is shared with the host; Ward isolates
state, not the network.
sudo pacman --needed -S github-cli openssh podman tmux
systemctl --user enable --now ssh-agent.socket podman.socketThe ward unit syncs the ward-github-token podman secret from gh auth token at every start and exposes it in the container as GH_TOKEN. The
start fails when gh is not authenticated on the host. Create the Brave secret
before starting Ward; it is exposed in the container as BRAVE_API_KEY:
printf %s "$BRAVE_API_KEY" | \
podman secret create --replace ward-brave-api-key -From the repository root:
install -d -m 0700 "$HOME/.config/containers/systemd" "$HOME/.config/systemd/user"
ln -sT "$PWD" "$HOME/.config/containers/systemd/ward"
ln -sT "$PWD/ward-build.timer" "$HOME/.config/systemd/user/ward-build.timer"
systemctl --user daemon-reload
systemctl --user start ward-build.service
systemctl --user start ward.service
systemctl --user enable --now ward-build.timerward-build.timer builds daily in the background (Persistent=true catches
up after downtime). Every run pulls archlinux:latest; when the digest is
unchanged, every layer is a cache hit and the build installs nothing. Real
rebuilds happen only when Arch publishes a new base image or the
Containerfile changes — the single pacman -Syu transaction then runs
against current mirrors. Package state advances when the base image moves,
not at each build.
A build never restarts the running container: it keeps its current root, and
the new image is used the next time ward.service starts — after a reboot,
or when applied manually below. Updates land when you choose, not
mid-session. Superseded builds remain in storage as dangling images;
podman image prune -f removes all dangling images, spares the live build
cache (images with children), and never removes an image in use by a
container.
ward-build.timer is a plain systemd unit, not a quadlet file, so it
installs under ~/.config/systemd/user/.
systemctl --user daemon-reload
systemctl --user restart ward-build.service &&
systemctl --user restart ward.serviceThe container is restarted only after a successful build; sessions in it end at that moment.
tmux -S "$XDG_RUNTIME_DIR/ward/tmux.sock"systemctl --user disable --now ward-build.timer
systemctl --user stop ward.service ward-build.service
rm -- "$HOME/.config/containers/systemd/ward" \
"$HOME/.config/systemd/user/ward-build.timer"
systemctl --user daemon-reload
podman image rm localhost/ward:latest
podman image prune -f
podman secret rm ward-github-token ward-brave-api-key