6 Configuration Management
Note
pyinfra for agentless deploys. Static files, not templates. One repo per host.
6.1 Why pyinfra
6.2 The instance repo
- One Git repo per physical host (e.g.,
carminefor one machine) - Contains: Containerfile (tier 3 image), quadlet files, pyinfra deploy scripts, rootful service configs, Makefile
- The repo is the single source of truth for what runs on that host
6.3 Repo layout
quadlets/{service}/—.container,.pod,Caddyfileper servicerootful/— Envoy and Alloy configs (the two rootful components)pyinfra/— deploy orchestration, service definitions, bootstrap tasksContainerfile— instance image definition (FROM base image + users + policy)Makefile—make deploy-all,make deploy-{service},make dry-{service}
6.4 Static files over templates
- Quadlet files and Caddyfiles are committed as-is, not generated from templates
- What you read in the repo is exactly what lands on the host
- Secrets are the exception: injected at runtime via
podman secret, never in the repo
6.5 The deploy pattern
pyinfra/services.pydefines every service: user, UID, FQDN, port, quadlet directory, secrets, ZFS volumespyinfra/tasks/deploy_service.pysyncs quadlet files to~/.config/containers/systemd/for the service user, thendaemon-reload- Rootful services (Envoy, Alloy) deploy to system-level systemd paths
pyinfra/tasks/bootstrap.pyhandles one-time setup: user creation, subuid/subgid, ZFS verification, SSH hardening, firewall, SELinux booleans
6.6 Targeting a single service
make deploy-forgedeploys only the forge service- pyinfra supports
--data target=forgeto filter the service list - Useful for iterating on one service without touching the rest
6.7 Drift detection
- CI runs
ruffandpy_compileon pyinfra scripts - Quadlet validation via
systemd-analyze verify(planned) - The image build pipeline detects base image changes and triggers instance rebuilds