← 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-admin is the PUBLIC quiz (-admin suffix is historical).
  • coachpilot-gateway's container is named gateway (so kubectl set image deployment/coachpilot-gateway gateway=...).

The four production images

All hosted in Docker Hub under the synergygroup/ org.

ImageWhere it runsImage tag schemeDeploys to
synergygroup/coachpilot-gatewaycoachpilot namespacep10e-mmc-YYYYMMDD-<phase>-<description>deployment/coachpilot-gateway
synergygroup/coachpilot-dashboardcoachpilot namespacep10e-mmc-YYYYMMDD-<description>deployment/coachpilot-dashboard
synergygroup/moneyquiz-adminassess namespace as dashboardsemver v1.x.ydeployment/dashboard
synergygroup/az-adaptive-assessmentaz namespacev<semver>-<phase>-<description>-YYYYMMDDdeployment/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:

NamespaceWhat runs there
coachpilotcoachpilot-gateway, coachpilot-dashboard, Postgres (postgres-0), Redis
assessdashboard (moneyquiz-admin), fleet-collector, srh
azadaptive-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.36.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 --noEmit passes (rm -rf .next first if files moved)
  • If new file: required by require_once or import chain
  • If new env var: documented + set in cluster secret/configmap

Post-deploy verification

  • kubectl rollout status returned success
  • curl https://api.coachpilot.ch/health (or equivalent for each service) returns 200
  • For changes to a specific tenant: GET /api/v1/assess/tenant/<id>/brand and 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 undo reverts 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_FIELDS lists are append-only).
  • MMC WordPress: SFTP restore the .bak-<timestamp> file from before the deploy. OPcache invalidate + cache purge.

Deployed image timeline (recent)

DateImageTagWhy
2026-05-18coachpilot-gatewayp10e-mmc-20260518-c1-aliasesFormat rename alias layer
2026-05-18coachpilot-gatewayp10e-mmc-20260518-c5-urls-fixPer-tenant URL composer
2026-05-18coachpilot-gatewayp10e-mmc-20260518-c6-mode-keysResolver canonicalize-on-read
2026-05-18az-adaptive-assessmentv1.4-c7-aliases-20260518NATS start_qa alias
2026-05-18moneyquiz-adminv1.6.1v1.6.3C-phase route + folder cleanup
2026-05-19coachpilot-gatewayp10e-mmc-20260519-d4-webhooksD4: tenant_brand webhook fields
2026-05-19moneyquiz-adminv1.6.4D4: brand editor with webhook fields
2026-05-19az-adaptive-assessmentv1.5-c2-calibration-20260519C: agent-side calibration
2026-05-19moneyquiz-adminv1.6.5B: TenantThemeProvider
2026-05-19moneyquiz-adminv1.6.6B: TenantBrandContext + dashboard preview
2026-05-19coachpilot-gatewayp10e-mmc-20260519-c5-calibrateC5: /calibrate endpoint

Migration history: see 04 Config schema §Migrations.


Next

11 Operations — runbooks, incident recovery, smoke tests.