← Back to index | ← 09 MMC bridge | 11 Operations →
Image taxonomy, registry conventions, kubectl recipes, and rollout patterns. Everything you need to ship a change.
Image taxonomy + cluster topology
flowchart LR
subgraph DH["Docker Hub: synergygroup/"]
GW_IMG["coachpilot-gateway<br/>:p10e-mmc-YYYYMMDD-..."]
DASH_IMG["coachpilot-dashboard<br/>:p10e-mmc-YYYYMMDD-..."]
MQ_IMG["moneyquiz-admin<br/>:vX.Y.Z"]
AG_IMG["az-adaptive-assessment<br/>:vX.Y-phase-YYYYMMDD"]
end
subgraph SKS["Exoscale SKS (Zurich · ch-dk-2)"]
subgraph NS_CP["ns: coachpilot"]
DEP_GW["deployment/coachpilot-gateway<br/>container: gateway"]
DEP_DASH["deployment/coachpilot-dashboard"]
PG[("postgres-0<br/>statefulset")]
end
subgraph NS_ASSESS["ns: assess"]
DEP_MQ["deployment/dashboard<br/>container: dashboard<br/>(the PUBLIC quiz, not admin)"]
end
subgraph NS_AZ["ns: az"]
DEP_AG["deployment/adaptive-assessment"]
end
end
NLB((NLB<br/>159.100.252.218))
MMC_WP[mindfulmoneycoaching.online<br/>WordPress · Hostinger SFTP]
GW_IMG --> DEP_GW
DASH_IMG --> DEP_DASH
MQ_IMG --> DEP_MQ
AG_IMG --> DEP_AG
DEP_GW & DEP_DASH & DEP_MQ --> NLB
NLB --> Internet((Internet))
MMC_WP -. SFTP deploy<br/>(separate) .-> MMC_WP
classDef img fill:#fff3e0,stroke:#f57c00
classDef pod fill:#e3f2fd,stroke:#1976d2
classDef net fill:#e8f5e9,stroke:#2e7d32
classDef ext fill:#f3e5f5,stroke:#7b1fa2
class GW_IMG,DASH_IMG,MQ_IMG,AG_IMG img
class DEP_GW,DEP_DASH,DEP_MQ,DEP_AG,PG pod
class NLB,Internet net
class MMC_WP ext
Two name asymmetries to remember:
moneyquiz-adminis the PUBLIC quiz (-adminsuffix is historical).coachpilot-gateway's container is namedgateway(sokubectl set image deployment/coachpilot-gateway gateway=...).
The four production images
All hosted in Docker Hub under the synergygroup/ org.
| Image | Where it runs | Image tag scheme | Deploys to |
|---|---|---|---|
synergygroup/coachpilot-gateway | coachpilot namespace | p10e-mmc-YYYYMMDD-<phase>-<description> | deployment/coachpilot-gateway |
synergygroup/coachpilot-dashboard | coachpilot namespace | p10e-mmc-YYYYMMDD-<description> | deployment/coachpilot-dashboard |
synergygroup/moneyquiz-admin | assess namespace as dashboard | semver v1.x.y | deployment/dashboard |
synergygroup/az-adaptive-assessment | az namespace | v<semver>-<phase>-<description>-YYYYMMDD | deployment/adaptive-assessment |
Note: moneyquiz-admin is the PUBLIC QUIZ Next.js app, despite the -admin suffix in its name (historical). The CoachPilot admin dashboard is a separate image (coachpilot-dashboard).
Note: coachpilot-gateway's deployment uses container name gateway (NOT coachpilot-gateway). When using kubectl set image, target it as gateway=<image>. This caught us during D4 deploy.
Cluster topology
Exoscale SKS, zone ch-dk-2 (Zurich):
cluster: a2e3aa09-0be5-4ee6-993d-2d0aa506d125
nlb: 159.100.252.218
Namespaces:
| Namespace | What runs there |
|---|---|
coachpilot | coachpilot-gateway, coachpilot-dashboard, Postgres (postgres-0), Redis |
assess | dashboard (moneyquiz-admin), fleet-collector, srh |
az | adaptive-assessment agent + other Agent Zero agents |
kubeconfig — 24h TTL
Refresh with:
PYTHONUTF8=1 python3 -X utf8 \
"/c/Users/andre/StarGate/10 Projects/Agent Zero/repos/exoscale-deploy-kit/exoscale-action.py" \
kubeconfig-refresh \
--cluster-id=a2e3aa09-0be5-4ee6-993d-2d0aa506d125 \
--output="$HOME/.kube/coachpilot-prod.yaml" \
--zone=ch-dk-2 \
--env-file=.env
PYTHONUTF8=1 python3 -X utf8 is mandatory on Windows — the Exoscale SDK crashes on api_spec.json with cp1252 decode otherwise.
Verify:
kubectl --kubeconfig="$HOME/.kube/coachpilot-prod.yaml" -n coachpilot get pods
Build + push (Docker)
Each image has a Dockerfile in its repo. All use --platform linux/amd64 because the Exoscale nodes are amd64 and we build on potentially-arm Macs.
Gateway
cd "10 Projects/Agentic Starter/api-gateway"
docker build --platform linux/amd64 \
-t synergygroup/coachpilot-gateway:p10e-mmc-20260519-d4-webhooks .
docker push synergygroup/coachpilot-gateway:p10e-mmc-20260519-d4-webhooks
Dashboard (CoachPilot admin)
cd "10 Projects/Agentic Starter/dashboard"
docker build --platform linux/amd64 \
-t synergygroup/coachpilot-dashboard:p10e-mmc-20260519-d4 .
docker push synergygroup/coachpilot-dashboard:p10e-mmc-20260519-d4
Moneyquiz-admin (public quiz)
cd "10 Projects/MoneyQuiz/moneyquiz-app"
docker build --platform linux/amd64 -t synergygroup/moneyquiz-admin:v1.6.6 .
docker push synergygroup/moneyquiz-admin:v1.6.6
Adaptive-assessment agent
Build context is the agents/ directory, not the agent subdir (Dockerfile expects adaptive-assessment/ as a subpath):
cd "10 Projects/Agent Zero/repos/agent-zero-agents/agents"
docker build --platform linux/amd64 \
-f adaptive-assessment/Dockerfile \
-t synergygroup/az-adaptive-assessment:v1.5-c2-calibration-20260519 .
docker push synergygroup/az-adaptive-assessment:v1.5-c2-calibration-20260519
Rollout
Gateway
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n coachpilot \
set image deployment/coachpilot-gateway \
gateway=synergygroup/coachpilot-gateway:p10e-mmc-20260519-d4-webhooks
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n coachpilot \
rollout status deployment/coachpilot-gateway --timeout=120s
Container name is gateway (see top of section).
Dashboard (CoachPilot admin)
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n coachpilot \
set image deployment/coachpilot-dashboard \
coachpilot-dashboard=synergygroup/coachpilot-dashboard:p10e-mmc-20260519-d4
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n coachpilot \
rollout status deployment/coachpilot-dashboard --timeout=120s
Moneyquiz-admin (public quiz)
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n assess \
set image deployment/dashboard \
dashboard=synergygroup/moneyquiz-admin:v1.6.6
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n assess \
rollout status deployment/dashboard --timeout=120s
Deployment name is dashboard (in assess namespace), NOT moneyquiz-admin. Container name is also dashboard.
Adaptive-assessment agent
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n az \
set image deployment/adaptive-assessment \
adaptive-assessment=synergygroup/az-adaptive-assessment:v1.5-c2-calibration-20260519
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n az \
rollout status deployment/adaptive-assessment --timeout=120s
Postgres migrations
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n coachpilot \
exec -i postgres-0 -- psql -U coachpilot -d coachpilot \
< "10 Projects/Agentic Starter/api-gateway/migrations/0009_tenant_brand_webhooks.sql"
Migrations are idempotent (CREATE TABLE IF NOT EXISTS, ADD COLUMN IF NOT EXISTS). Safe to re-apply.
For one-off direct SQL (e.g. dropping a JSONB sub-key):
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n coachpilot \
exec -i postgres-0 -- psql -U coachpilot -d coachpilot \
-c "UPDATE framework_configs SET mode_overrides = mode_overrides - 'quiz' WHERE tenant_id='tsg';"
MMC deploy (WordPress, separate)
MMC is on Hostinger, NOT on Exoscale. Deploy via SFTP. Theme version bump + cache purge mandatory.
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("coachpilot.ch", port=65002, username="u146818668",
password="<from vault: secret/passwords/hostinger_ssh>", timeout=15)
sftp = ssh.open_sftp()
# Backup
sftp.rename(remote, remote + ".bak-<timestamp>")
# Upload
sftp.put(local, remote)
# OPcache invalidate + cache purge
ssh.exec_command(f'touch "{remote}" && cd <wp-root> && wp eval \'opcache_invalidate("{remote}", true);\' && wp cache flush')
After every theme change: bump version in style.css (e.g. 6.4.3 → 6.4.4) AND nuclear purge:
wp cache flush && rm -rf wp-content/litespeed/* wp-content/cache/* wp-content/uploads/litespeed-css/* && wp transient delete --all
Otherwise LiteSpeed serves stale assets.
See 11 Operations §MMC deploy for the full Python recipe.
Pre-deploy checklist
Before every deploy:
- Build succeeds locally
- No new hardcoded secrets in source
- If schema change: migration written + idempotent
- If TS change: dashboard or moneyquiz-app
tsc --noEmitpasses (rm -rf .nextfirst if files moved) - If new file: required by
require_onceorimportchain - If new env var: documented + set in cluster secret/configmap
Post-deploy verification
kubectl rollout statusreturned successcurl https://api.coachpilot.ch/health(or equivalent for each service) returns 200- For changes to a specific tenant: GET
/api/v1/assess/tenant/<id>/brandand verify new fields present - Smoke the user-facing flow (real or synthetic)
If verification fails: rollback BEFORE debugging.
kubectl --kubeconfig=$HOME/.kube/coachpilot-prod.yaml -n <ns> \
rollout undo deployment/<name>
Rollback policy
- Gateway / dashboard / moneyquiz-admin / agent:
kubectl rollout undoreverts to the previous image. Safe — these are stateless. - Postgres migrations: NEVER rollback a migration. They're additive by design. If a migration broke something, fix forward with a new migration. The image rollback restores the OLD code, which ignores new columns gracefully (because
_TENANT_BRAND_FIELDSlists are append-only). - MMC WordPress: SFTP restore the
.bak-<timestamp>file from before the deploy. OPcache invalidate + cache purge.
Deployed image timeline (recent)
| Date | Image | Tag | Why |
|---|---|---|---|
| 2026-05-18 | coachpilot-gateway | p10e-mmc-20260518-c1-aliases | Format rename alias layer |
| 2026-05-18 | coachpilot-gateway | p10e-mmc-20260518-c5-urls-fix | Per-tenant URL composer |
| 2026-05-18 | coachpilot-gateway | p10e-mmc-20260518-c6-mode-keys | Resolver canonicalize-on-read |
| 2026-05-18 | az-adaptive-assessment | v1.4-c7-aliases-20260518 | NATS start_qa alias |
| 2026-05-18 | moneyquiz-admin | v1.6.1 → v1.6.3 | C-phase route + folder cleanup |
| 2026-05-19 | coachpilot-gateway | p10e-mmc-20260519-d4-webhooks | D4: tenant_brand webhook fields |
| 2026-05-19 | moneyquiz-admin | v1.6.4 | D4: brand editor with webhook fields |
| 2026-05-19 | az-adaptive-assessment | v1.5-c2-calibration-20260519 | C: agent-side calibration |
| 2026-05-19 | moneyquiz-admin | v1.6.5 | B: TenantThemeProvider |
| 2026-05-19 | moneyquiz-admin | v1.6.6 | B: TenantBrandContext + dashboard preview |
| 2026-05-19 | coachpilot-gateway | p10e-mmc-20260519-c5-calibrate | C5: /calibrate endpoint |
Migration history: see 04 Config schema §Migrations.
Next
→ 11 Operations — runbooks, incident recovery, smoke tests.