ADR-0014: Dual Deployment Strategy — Compose (Dev) / Quadlets (Prod)¶
Status: Accepted (Implementation planned for v1.0.0) • Date: 2026-02-17
1. Context¶
Silvasonic is designed to run autonomously for years on unattended edge devices (Raspberry Pi 5). The current development workflow uses podman-compose with compose.yml for Tier 1 service orchestration. While this works well for local development (just start/stop/build), podman-compose is a Python-based translation layer over Podman CLI commands — it is not a production-grade process manager.
For a system where "Data Capture Integrity is paramount" and autonomous operation is measured in years, the weakest link in the chain matters. podman-compose introduces:
- A single point of failure — if the compose process crashes, service restart management is lost.
- No native integration with the OS init system — service ordering, crash recovery, and boot dependencies rely on compose logic rather than the kernel's process manager.
- An unnecessary abstraction layer between the OS and the containers.
2. Decision¶
We chose: A dual deployment strategy.
| Environment | Tool | Purpose |
|---|---|---|
| Development | podman-compose + compose.yml |
Fast iteration, familiar DX (just start/stop/build) |
| Production | Podman Quadlets (.container, .volume, .network systemd units) |
Maximum resilience, native OS integration |
What are Quadlets?¶
Podman Quadlets (available since Podman 4.4, stable in 4.6+) allow defining containers as native systemd services. Instead of compose.yml, each Tier 1 service gets a .container unit file:
# /etc/containers/systemd/silvasonic-database.container
[Unit]
Description=Silvasonic TimescaleDB
After=network-online.target
[Container]
Image=localhost/silvasonic-database:latest
ContainerName=silvasonic-database
EnvironmentFile=/etc/silvasonic/.env
Volume=silvasonic-db-data:/var/lib/postgresql/data:z
Network=silvasonic.network
PublishPort=5432:5432
HealthCmd=pg_isready -U silvasonic
HealthInterval=10s
HealthRetries=5
HealthStartPeriod=15s
[Service]
Restart=always
RestartSec=5s
TimeoutStartSec=120
[Install]
WantedBy=multi-user.target default.target
Systemd then manages start ordering (After=), crash recovery (Restart=always), boot activation (WantedBy=), and clean shutdown — without any intermediary tool.
Reasoning:
- Resilience: systemd is the battle-tested Linux process manager. It handles crash recovery, boot ordering, and watchdog timers natively. A container crashing at 3 AM in the forest is restarted within seconds — no compose process required.
- Zero Dependencies: No Python runtime, no
podman-composeinstallation. The OS manages containers directly. - Boot Reliability:
WantedBy=multi-user.targetguarantees all Tier 1 services start automatically after power loss — the single most critical requirement for autonomous field stations. - Alignment with Podman-Only Strategy: Quadlets are the Podman-native production deployment method (see ADR-0004, ADR-0013). Using them aligns with the project's commitment to Podman as the sole container engine.
- Development Unchanged:
compose.ymlandpodman-composeremain the developer-facing tools. The Quadlet files are generated/maintained by the Ansible provisioning playbooks.
3. Options Considered¶
podman-composein Production: Status quo. Simple but fragile. No native crash recovery, no systemd integration. Rejected for production use.podman generate systemd: Generates legacy systemd unit files from running containers. Works but is deprecated in favor of Quadlets. Rejected.- Podman Quadlets: Native systemd integration via
.containerunit files. Production-grade, zero dependencies, battle-tested. Chosen. - Kubernetes/K3s: Vastly overengineered for a single-node edge device. Rejected.
4. Consequences¶
- Positive:
- Tier 1 services survive crashes and power losses with automatic, systemd-managed restarts.
- No runtime dependency on
podman-composein production. - Clean separation between development tooling (compose) and production deployment (Quadlets).
- Ansible can template and deploy
.containerfiles as part of fleet provisioning. - Aligns with "Resilience over Features" design principle.
- Negative:
- Two parallel "descriptions" of Tier 1 services:
compose.yml(dev) and.containerfiles (prod). Must be kept in sync. - Developers unfamiliar with systemd units have a learning curve.
- Quadlets require Podman ≥ 4.4 on the target device.
- Two parallel "descriptions" of Tier 1 services:
5. Implementation Notes¶
- Scope: Tier 1 services only. Tier 2 services are managed by the Controller via
podman-py(see ADR-0013). - Timeline: v1.0.0 (production-ready field deployment).
- Provisioning: Quadlet files will be generated/deployed by the Ansible playbooks during device provisioning.
- Files:
.container,.volume, and.networkunit files will reside indeploy/quadlets/within the repository and be installed to/etc/containers/systemd/on the target device. - Compose Retained:
compose.ymlremains the canonical development tool. No changes to the dev workflow.