Skip to content

Deploying

tej edited this page Jun 2, 2026 · 5 revisions

Deploying podman-api

This is about installing the daemon on the host where your CMS lives. It does not need to be on the same network as the podman hosts — it only needs the SSH keys to reach them.

Opinionated layout

/etc/podman-api/
├── hosts/
│   └── prod-1.yaml      # one file per target (see Provisioning a Podman Host)
├── keys.yaml            # Argon2id bearer tokens
└── templates/           # optional override of the bundled set

Install with the bundled script

contrib/install.sh creates a dedicated podman-api system user, builds the binary with the correct tags (via make build), installs it to /usr/local/bin, drops the systemd unit, and seeds an empty config tree. It does not start the service — review config first.

sudo contrib/install.sh
# or use a pre-built binary:
sudo BINARY=/path/to/podman-api contrib/install.sh

Bearer keys

Generate an Argon2id hash with the binary itself, then add it to keys.yaml:

podman-api hash-token "my-plaintext-token"
# $argon2id$v=19$m=65536,t=3,p=4$...
keys:
  - id: cms-prod
    secret_hash: '$argon2id$v=19$m=65536,t=3,p=4$...'
    scopes: [hosts:read, instances:*, secrets:*]
    description: "Prod CMS"
  - id: prom-scraper
    secret_hash: '$argon2id$v=19$m=65536,t=3,p=4$...'
    scopes: [hosts:read]

Scopes: hosts:read, hosts:write, instances:read, instances:write, secrets:read, secrets:write (plus instances:* / secrets:* shorthands). Rotation is live — see Operating.

Run

podman-api \
  -addr=127.0.0.1:8080 \
  -metrics-addr=127.0.0.1:9090 \
  -hosts-dir=/etc/podman-api/hosts \
  -keys-file=/etc/podman-api/keys.yaml
# optional: -templates-dir=/etc/podman-api/templates  -audit-log-file=/var/log/podman-api/audit.log

Then:

systemctl enable --now podman-api
curl http://127.0.0.1:8080/healthz      # {"status":"ok"}

Security model

  • Binds 127.0.0.1 only. The binary never exposes plaintext on a public port. TLS is the reverse proxy's job — the recommended setup is Caddy in front terminating Let's Encrypt (contrib/Caddyfile.example).
  • /metrics is on a separate listener (-metrics-addr), not the main port — its labels include host/template/path and are not safe to expose publicly. Scrape it over an SSH tunnel or a VPC-internal bind.
  • Bearer tokens are stored as Argon2id PHC strings; plaintext exists only at issuance and in the client config.
  • Input validation (slug/template/secret/container names must match ^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$) happens at the API edge, before anything reaches the renderer or podman.
  • Secrets arrive in the JSON body of POST /instances, are written as Kubernetes Secret objects on the target host, and are zeroed in the in-memory request struct after use.

Deleting instances — mind the prune flags

DELETE /hosts/{host}/instances/{template}/{slug} keeps volumes and per-instance secrets by default. Pass ?prune_volumes=true&prune_secrets=true to also reap them. Delete is an idempotent reconcile: if the pod is already gone, a delete that requests pruning still removes orphans and returns 204. Forgetting prune_secrets leaves the secret on the host — see Troubleshooting.

Related

Clone this wiki locally