# cloud The single central hub. One deployment, internet-facing. ## What runs here nginx-proxy, wireguard-server, keycloak, portainer, influxdb, grafana, node-red, mqtt, postfix, gitea, jenkins, sql. See [`../docs/architecture.md`](../docs/architecture.md) for the full network topology and ingress table. ## Run ```bash cp .env.example .env # fill in real secrets first ./deploy.sh # one-shot bring-up: containers + cert + smoke test ``` `deploy.sh` is idempotent — rerun any time. It will: 1. **Preflight** — check `.env` has all required vars 2. **Validate** `docker compose config` 3. **Bring up** containers, wait for `sql` healthcheck, wait for nginx :80 4. **Inspect cert** — figure out whether the current cert is self-signed, staging, or prod 5. **Issue / renew** the SAN cert via certbot only when needed (initial issuance, or when `ACME_CA_URI` no longer matches the current issuer); reload nginx 6. **Status** — show `docker compose ps` 7. **Smoke test** every `*.wbd-rd.nl` vhost over loopback The script reissues the cert **only** when the CA in `.env` changes (e.g. staging → prod) or when only the bootstrap dummy is present — it does not waste Let's Encrypt rate limits on repeated runs. ### Staging → prod flip 1. Verify everything works with the staging cert (browser will warn — that's normal) 2. Edit `.env`: change `ACME_CA_URI` to `https://acme-v02.api.letsencrypt.org/directory` 3. `./deploy.sh` — script detects the CA change and force-renews against prod ## Ingress (host port bindings) | Port | Container | |---|---| | tcp/80, 443 | nginx-proxy | | tcp/8883 | nginx-proxy (MQTT-TLS via stream block) | | udp/51820 | wireguard-server | Everything else stays on the internal `app` / `data` / `mgmt` networks. ## Adding a new stack 1. Create `stacks//` with `compose.yml`, `.env.example`, `README.md`. 2. Uncomment (or add) the `include:` entry in `compose.yml`. 3. Add the stack's env vars to `.env.example`. 4. `docker compose pull && docker compose up -d`.