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 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 libsecret openssh podman tmux
systemctl --user enable --now ssh-agent.socket podman.socketThe ward unit syncs the ward-github-token, ward-brave-api-key,
ward-bsky-app-password, and ward-mastodon-access-token podman secrets from
the host keyring (secret-tool) at every start and exposes them in the
container as GH_TOKEN, BRAVE_API_KEY, BSKY_APP_PASSWORD, and
MASTODON_ACCESS_TOKEN. The start fails when any keyring entry is missing.
Store them before starting Ward:
printf %s "$GH_TOKEN" | secret-tool store --label='Ward' service gh:github.com
printf %s "$BRAVE_API_KEY" | secret-tool store --label='Ward' service brave
printf %s "$BSKY_APP_PASSWORD" | secret-tool store --label='Ward' service bluesky
printf %s "$MASTODON_ACCESS_TOKEN" | secret-tool store --label='Ward' service mastodonFrom the repository root:
install -d -m 0755 \
"$HOME/.cargo" \
"$HOME/.cache/uv" \
"$HOME/.local/share/pnpm" \
"$HOME/.local/share/uv"
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