Architecture
One process, one binary
Section titled “One process, one binary” ┌──────────────────────────────────┐ │ Pier (single binary) │ │ │ Browser ───────▶ │ Axum ──▶ API routes (100+) │ │ │ │ │ ├──▶ MiniJinja ──▶ HTML (HTMX) │ │ ├──▶ Bollard ──▶ Docker Engine │ │ ├──▶ rusqlite ──▶ SQLite │ │ └──▶ reqwest ──▶ Remote Agents │ └──────────────────────────────────┘ │ ┌───────────────┴────────────────┐ │ Traefik (reverse proxy) │ │ Let's Encrypt · Auto-routing │ └────────────────────────────────┘Components
Section titled “Components”| Layer | Crate | Role |
|---|---|---|
| HTTP + WebSocket | axum | API endpoints and SSE/WebSocket streams for logs & metrics |
| Docker | bollard | All container, volume, and network operations |
| Database | rusqlite | Embedded SQLite, WAL mode for concurrent reads |
| Templates | minijinja | Server-rendered HTML for the dashboard |
| Auth | axum-login + totp-rs | Session-based login; TOTP on the roadmap |
| SSH | russh | Remote server provisioning |
| Git | gix | Clone and pull for Git-to-deploy |
| Metrics | sysinfo | System and container metrics |
| Cache | moka | In-memory cache for Docker stats |
| Runtime | tokio | Async I/O |
Dashboard
Section titled “Dashboard”The UI is server-rendered with MiniJinja. HTMX handles partial updates; Alpine.js covers client-side state for small interactions (modals, toggles). Total client-side JS is around 30 KB. Everything ships embedded in the binary — no CDN, no external fonts, no bundler at runtime.
Storage layout
Section titled “Storage layout”/opt/pier/├── bin/pier # the binary├── bin/pier.old # previous version (for rollback)├── .env # PIER_SECRET — AES-256 key for env vars└── data/ ├── pier.db # SQLite main database ├── pier.db-wal # WAL file ├── backups/ # automated DB + .env backups └── traefik/ # dynamic config files per domainSecurity at rest
Section titled “Security at rest”Every environment variable stored in the database is encrypted with AES-256-GCM. The key lives in /opt/pier/.env as PIER_SECRET (32 random bytes, base64-encoded). The database alone is useless without the key — the split is intentional so that a leaked SQL dump does not leak secrets.
Encryption is backward-compatible: rows written before the feature shipped are readable as plain text; new writes carry an ENC: prefix and are always encrypted.