diff --git a/infrastructure/swarm.md b/infrastructure/swarm.md new file mode 100644 index 0000000..89693c1 --- /dev/null +++ b/infrastructure/swarm.md @@ -0,0 +1,82 @@ +# Docker Swarm + +**Last updated:** 2026-08-11 + +## Overview + +Container orchestration uses Docker Swarm managed through Portainer. All +production services are deployed as Swarm stacks, not standalone containers. + +## Topology + +| Role | Hostname | IP | +|------|----------|----| +| Swarm manager | impulse-controller | 192.168.50.120 | +| Database node | memory-archives | 192.168.50.107 | +| Hypervisor | *(Xen Orchestra)* | 192.168.50.100 | + +- **Portainer:** https://192.168.50.120:9443 +- **Hypervisor:** Xen (Xen Orchestra UI at 192.168.50.100) + +## Databases + +All databases are centralised on `memory-archives` (192.168.50.107): + +| Engine | Notes | +|--------|-------| +| PostgreSQL 16 + pgvector | Primary relational store; vector extension enabled | +| MySQL | Legacy / compatibility workloads | + +Services should connect to `memory-archives` by hostname, not IP, where +Swarm DNS resolves within the overlay network. + +## Stack YAML Conventions + +```yaml +# No top-level 'version:' field — Swarm ignores it and modern Compose drops it +services: + myservice: + image: example/myservice:latest + restart: unless-stopped # required on all services + networks: + - master-proxy-overlay + deploy: + labels: + # Traefik labels go under deploy:, not at the service root + - "traefik.enable=true" + - "traefik.http.routers.myservice.rule=Host(`myservice.pldyn.net`)" + - "traefik.http.routers.myservice.entrypoints=websecure" + - "traefik.http.routers.myservice.tls.certresolver=cloudflare" + # HTTP → HTTPS redirect + - "traefik.http.routers.myservice-http.rule=Host(`myservice.pldyn.net`)" + - "traefik.http.routers.myservice-http.entrypoints=web" + - "traefik.http.routers.myservice-http.middlewares=redirect-to-https@docker" + +networks: + master-proxy-overlay: + external: true +``` + +Key conventions: +- No `version:` field at the top of the file +- `restart: unless-stopped` on every service +- Traefik labels placed under `deploy:` (not at service root) +- HTTP→HTTPS redirect middleware included for every public-facing service + +## Service Discovery + +Swarm services are referenced by **service name**, not container name. +Container names are non-deterministic across replicas; service names are +stable within the overlay network. + +## Sharp Edges + +### Portainer env UI passes literal quotes + +When setting environment variables via the Portainer UI, the value field +passes content verbatim — including any quotes you type. Do **not** wrap +values in `"..."` or `'...'` in the UI; the quotes become part of the value +and will break the application. + +**Correct:** `MY_VAR=somevalue` +**Wrong:** `MY_VAR="somevalue"` ← the quotes are included in the string