do-stacks is the Defend-O-Tron's container orchestrator — the tool you'd reach for whenever something needs to be started, stopped, restarted, or checked at the container layer. It wraps Podman + podman-compose with sensible defaults, picks the right compose files from /etc/awesome-o/do-stacks.conf, and adds the operator-niceties Podman alone doesn't ship (banner, a clean status table, cleanup of stale overlays, API-key provisioning for the CrowdSec bouncer).
Most of the device's supporting services run as Podman containers grouped into named stacks (proxy, crowdsec, adguard, monitoring, etc.). do-stacks is the unified front door for managing them.
Where to run it: Open the Cockpit admin interface and click Tools → Terminal.
do-stacksis on your$PATH. You can also SSH to the device if you've configured a key. No need to install anything separately.
do-stacks [<command>] [<stack>...]
If you don't pass a command, do-stacks runs deploy (the default), which brings up the configured stacks. If you don't pass a stack list, the defaults in /etc/awesome-o/do-stacks.conf are used.
do-stacks needs root — if you run it as a non-root user it re-runs itself under sudo automatically. You'll be prompted for your password the first time per terminal session.
| Flag | Effect |
|---|---|
--no-color |
Force plain ASCII output (no ANSI colours, no Unicode glyphs). Also honored via NO_COLOR=1 in the environment — see no-color.org. |
--quiet |
Suppress non-error output. Useful in scripts. |
--verbose |
Print everything --quiet hides plus podman-compose's raw stdout, unfiltered. |
--json |
Emit one JSON event per line for every subcommand except status (which keeps its single-snapshot JSON form, ideal for Cockpit and scripts). |
The Cockpit web interface (and therefore the built-in terminal) is served through the proxy stack — Traefik routes https://defend-o-tron.protected.lan/ to Cockpit on port 9090.
If you run do-stacks rm proxy or do-stacks stop proxy from inside the Cockpit terminal, you will lose your session immediately. Cockpit's web service keeps running on the host, but you can no longer reach it through the FQDN until the proxy stack comes back up.
To work around this:
https://<management-ip>:9090/ (the direct Cockpit port) instead of the FQDN before touching the proxy stack, or:9090 after the operation completes — do-stacks deploy proxy will bring it back.The same care applies to any stack that hosts a service you're actively using (e.g. running do-stacks stop adguard while your network's DNS depends on it).
Print the run context (timezone, deploy directory, compose provider), a one-line container + pod count summary, a per-pod summary table, and a per-container detail table — the fastest way to check "is everything running, and which container is where?"
Usage:
do-stacks status [--json]
Example:
$ do-stacks status
┌────────────────────────────────────────────┐
│ Timezone America/Denver │
│ Working Directory /opt/awesome-o/deploy │
│ Compose Provider /usr/bin/podman-compose │
└────────────────────────────────────────────┘
Stacks 7 │ Containers 17 (17 running, 0 exited, 0 created)
┌────────────┬─────────┬─────────┬────────────┐
│ NAME │ STATUS │ CREATED │ CONTAINERS │
├────────────┼─────────┼─────────┼────────────┤
│ adguard │ Running │ 2h │ 1 │
│ crowdsec │ Running │ 2h │ 3 │
│ monitoring │ Running │ 2h │ 5 │
│ portainer │ Running │ 2h │ 1 │
│ proxy │ Running │ 2h │ 4 │
│ smtp │ Running │ 2h │ 1 │
│ suricata │ Running │ 2h │ 2 │
└────────────┴─────────┴─────────┴────────────┘
┌──────────────────────┬────────────┬─────────┬─────────┐
│ CONTAINER │ POD │ STATUS │ CREATED │
├──────────────────────┼────────────┼─────────┼─────────┤
│ adguard-home │ adguard │ Running │ 2h │
│ crowdsec │ crowdsec │ Running │ 2h │
│ crowdsec-bouncer │ crowdsec │ Running │ 2h │
│ crowdsec-proxy │ crowdsec │ Running │ 2h │
│ alertmanager │ monitoring │ Running │ 2h │
│ grafana │ monitoring │ Running │ 2h │
│ loki │ monitoring │ Running │ 2h │
│ prometheus │ monitoring │ Running │ 2h │
│ promtail │ monitoring │ Running │ 2h │
│ portainer │ portainer │ Running │ 2h │
│ authelia │ proxy │ Running │ 2h │
│ redis │ proxy │ Running │ 2h │
│ traefik │ proxy │ Running │ 2h │
│ traefik-forward-auth │ proxy │ Running │ 2h │
│ rspamd │ smtp │ Running │ 2h │
│ suricata │ suricata │ Running │ 2h │
│ suricata-logrotate │ suricata │ Running │ 2h │
└──────────────────────┴────────────┴─────────┴─────────┘
A few details that don't always show up in a snapshot like this:
just now, 5m, 3h, 2d, 4w) — pods older than 30 days fall back to a YYYY-MM-DD date. A blank/missing creation timestamp shows as -.do-stacks cleanup.<pod>-infra pause containers so only real workloads show up. The per-pod CONTAINERS count is the raw Podman count and still includes the infra container, so a pod showing 2 here may have only 1 row in the detail table below.NO_COLOR=1 / --no-color is set), the box-drawing characters fall back to ASCII (+, -, |) and color is dropped — the layout stays identical.With --json, the same snapshot is emitted as a single JSON object — suitable for scripted health checks or Cockpit ingestion. The top-level shape is:
{
"timezone": "America/Denver",
"deploy_home": "/opt/awesome-o/deploy",
"compose_provider": "/usr/bin/podman-compose",
"counts": {
"pods": 7,
"containers": { "total": 17, "running": 17, "exited": 0, "created": 0, "other": 0 }
},
"pods": [ { "name": "...", "status": "...", "created": "...", "containers": 1 }, ... ],
"containers": [ { "name": "...", "pod": "...", "state": "running", "created": "..." }, ... ]
}
Bring stacks up. Pulls any missing images, brings every container in the named stacks to running, and reports what it did. This is do-stacks's default action if you don't pass a subcommand.
Usage:
do-stacks deploy [<stack>...]
do-stacks # same as deploy with default stacks
Example:
$ do-stacks deploy proxy crowdsec
[+] Pulling proxy/traefik:v3.5... done
[+] Pulling crowdsec/crowdsec:v1.7.8... done
[+] Running 5/5
✔ Pod proxy Started 1.4s
✔ Container traefik Started 0.8s
✔ Pod crowdsec Started 1.1s
✔ Container crowdsec Started 0.6s
✔ Container crowdsec-bouncer Started 0.3s
OK Success: 2 stacks deployed (proxy, crowdsec)
If a stack is already running, deploy is a no-op for that stack — it won't restart healthy containers.
Render the resolved compose configuration for one or more stacks to stdout — the same YAML podman-compose would actually use, with every variable substituted and every override merged in. Useful for "what would deploy actually do?" without doing it.
Usage:
do-stacks config [<stack>...]
Example:
$ do-stacks config proxy
services:
traefik:
image: traefik:v3.5
container_name: traefik
restart: unless-stopped
cap_drop: [ALL]
cap_add: [CAP_NET_BIND_SERVICE]
ports:
- "80:80"
- "443:443"
volumes:
- /opt/data/traefik:/var/traefik
- /opt/deploy/proxy/config:/etc/traefik:ro
networks: [edgenet]
[...]
Pull container images for the named stacks without changing their running state. Useful for pre-staging updates before a maintenance window.
Usage:
do-stacks pull [<stack>...]
Example:
$ do-stacks pull crowdsec
[+] Pulling crowdsec/crowdsec:v1.7.8... done
[+] Pulling cs-firewall-bouncer-nftables:latest... done
OK Success: 2 images pulled for stack 'crowdsec'
Start containers for stacks that exist but are stopped. Unlike deploy, start does not pull images or create new containers — it only resumes existing ones.
Usage:
do-stacks start [<stack>...]
Example:
$ do-stacks start adguard
[+] Running 1/1
✔ Container adguard-home Started 0.4s
OK Success: stack 'adguard' started
Stop running containers for the named stacks. The containers remain on disk and can be resumed with start.
Stopping the proxy stack from inside the Cockpit terminal ends your session — see Important above. Stopping adguard while your network's DNS depends on it will break name resolution until you
startit again.
Usage:
do-stacks stop [<stack>...]
Example:
$ do-stacks stop portainer
[+] Stopping 1/1
✔ Container portainer Stopped 0.6s
OK Success: stack 'portainer' stopped
Tear stacks down — stop containers and remove them. The persistent volumes under /opt/data/ are not deleted. Use rm when you want to recreate a container from scratch (after a config change, for example), then deploy to bring it back up.
Removing the proxy stack from inside the Cockpit terminal will end your session immediately — Traefik routes the FQDN to Cockpit. Use
https://<management-ip>:9090/(direct Cockpit) or an SSH session before running this.do-stacks deploy proxywill bring it back.
Usage:
do-stacks rm [<stack>...]
Example:
$ do-stacks rm crowdsec
[+] Stopping + removing 3/3
✔ Container crowdsec Stopped + Removed 1.1s
✔ Container crowdsec-bouncer Stopped + Removed 0.4s
✔ Container crowdsec-proxy Stopped + Removed 0.4s
OK Success: stack 'crowdsec' removed (data volumes preserved)
Load *.img container image tarballs from <image_dir> (default /opt/data/images). Used during firmware updates that ship pre-built images alongside the firmware, so the device doesn't need to pull from a registry to come up clean.
Usage:
do-stacks load [name]
If name is omitted, every .img file in the image directory is loaded.
Example:
$ do-stacks load
[+] Loading /opt/data/images/crowdsec-v1.7.8.img done
[+] Loading /opt/data/images/traefik-v3.5.img done
OK Success: 2 images loaded
Remove stopped containers, orphaned pods, and stale overlay storage. Reclaims disk space and stops Podman's overlay store from growing without bound after many update cycles.
Usage:
do-stacks cleanup
Example:
$ do-stacks cleanup
[+] Pruning stopped containers 8 removed
[+] Pruning unused images 3 removed
[+] Cleaning overlay storage at /opt/data/podman/overlay 428 MB reclaimed
OK Success: cleanup complete
Provision a CrowdSec firewall-bouncer API key, store it both in CrowdSec's database and as a Podman secret, and restart the bouncer with the new credentials. Used during firstboot, after cscli resets, or if the bouncer key has been compromised.
Usage:
do-stacks config-apikey
Example:
$ do-stacks config-apikey
OK Success: generated new bouncer API key
OK Success: stored in CrowdSec at /var/lib/crowdsec/data/crowdsec.db
OK Success: stored as Podman secret 'cs-bouncer-key'
[+] Restarting bouncer container
✔ Container cs-firewall-bouncer Restarted 0.8s
OK Success: bouncer reconfigured
Print the do-stacks binary version. Useful for confirming you're testing against the expected build during firmware upgrades.
Usage:
do-stacks version
Example:
$ do-stacks version
do-stacks 1.1.0
Print the full usage text (the same content shown at the top of this page, plus the global flags).
Usage:
do-stacks help
do-stacks --help
do-stacks -h
Quick sanity check after reboot:
sudo do-stacks status
Apply a config change to a single stack:
sudo do-stacks rm crowdsec
sudo do-stacks deploy crowdsec
Pre-stage updates without touching running services:
sudo do-stacks pull
Free up disk space:
sudo do-stacks cleanup
Scripted health check for monitoring:
# count of running containers (number)
sudo do-stacks --json --no-color status | jq '.counts.containers.running'
# names of any pod that isn't currently Running
sudo do-stacks --json --no-color status \
| jq -r '.pods[] | select(.status != "Running") | .name'
do-stacks reads a single configuration file: /etc/awesome-o/do-stacks.conf. It's a dpkg conffile, so your edits survive apt upgrade. The file is the single source of truth for every operator-tunable value — do-stacks carries no fallback defaults for these keys. If the file is missing, or a required section/key is absent or empty, do-stacks refuses to run with an explanatory error.
The file itself is heavily commented. The summary below is a roadmap; for the per-key detail, open the conffile.
| Section | What it controls |
|---|---|
[paths] |
Filesystem layout — deploy_home (master deployment root, default /opt/deploy), image_dir, zoneinfo_root, localtime_link, optional overlay_root. Most operators never touch this. |
[network.<name>] |
Podman network definitions. Each [network.<name>] block becomes a Podman network of that name. Edit subnet / gateway / interface here to retune; add a new network by adding a new section — no code changes required. The two networks shipped are edgenet (routable, used by proxy etc.) and secnet (internal-only, no default gateway). |
[stacks] |
Default stack lists — deploy_order for deploy / config / pull, teardown_order for rm / start / stop / cleanup. Order matters: monitoring is last in deploy_order so it scrapes services that are already up, and first in teardown_order so it stops before the things it scrapes. |
[apikey] |
Where do-stacks config-apikey reads/writes the CrowdSec firewall-bouncer credentials — bouncer_yaml, bouncer_template, api_key_path, protected_key_path, and the two Podman secret names. Only relevant if you're recovering from a CrowdSec reset or rotating the key. |
[netowrk.edgenet]. Fail loudly rather than silently ignore a section that was meant to do something.[network.<name>] section, so the internal boolean carries a real value rather than the silent zero default false.Pop a new section into the file:
[network.protectnet]
driver = bridge
interface = br-protectnet
internal = false
ipam_driver = host-local
subnet = 172.20.3.0/24
gateway = 172.20.3.1
Then either do-stacks deploy (creates the network on first need) or do-stacks config <stack> (verifies the YAML and the conf parse together). A new network on its own doesn't bring anything up — a stack has to reference it in its compose file for containers to actually attach.
A do-stacks config <stack> is the fastest "does this still parse?" check — it renders the resolved compose YAML using the current conf and exits non-zero on errors. If the change affects a running stack (e.g. you changed a network subnet), follow up with:
sudo do-stacks rm <stack>
sudo do-stacks deploy <stack>
do-stacks doesn't ship a hot-reload — anything network-shaped requires the stack to be torn down and recreated.
| Variable | Effect |
|---|---|
NO_COLOR=1 |
Same as --no-color. Standard convention from no-color.org. |
SHOWBANNER=0 |
Suppress the banner header. Used by callers like init-system.firstboot and tech-support. |
do-stacks.do-stacks manages.do-stacks rm crowdsec for parser/scenario reloads./etc/awesome-o/; the two tools share that config tree.