| name | funnelcake-deployment-workflow |
| description | Deploy funnelcake (api + relay) to ANY environment (production, staging, poc) on GKE via ArgoCD.
Use when: (1) Deploying new funnelcake commits, (2) Building Docker images for GKE (amd64),
(3) Running ClickHouse migrations, (4) Troubleshooting ImagePullBackOff errors,
(5) Syncing staging/poc after production deployment. Covers complete workflow with
PRE-FLIGHT CHECKLIST to prevent common deployment failures.
|
| author | Claude Code |
| version | 2.0.0 |
| date | "2026-01-31T00:00:00.000Z" |
Funnelcake Deployment Workflow
Problem
Deploying funnelcake requires multiple coordinated steps across DIFFERENT environments,
each with its own container registry and ArgoCD instance. Common failures:
- Updating kustomization with image tags that don't exist in target registry
- Assuming push to main auto-deploys to staging/poc (it doesn't)
- Not verifying pods actually started (vs stuck in ImagePullBackOff)
Context / Trigger Conditions
- User asks to deploy funnelcake to any environment
- New commits need to be deployed
- Migrations need to be run
- Pods stuck in ImagePullBackOff or ErrImagePull
- Staging/poc is behind production
CRITICAL: Multi-Environment Architecture
Each environment is ISOLATED - nothing is shared!
| Environment | Container Registry | ArgoCD | kubectl Context |
|---|
| Production | us-central1-docker.pkg.dev/dv-platform-prod/containers-production/ | In production cluster | connectgateway_dv-platform-prod_us-central1_gke-production-membership |
| Staging | us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/ | In staging cluster | connectgateway_dv-platform-staging_us-central1_gke-staging-membership |
| POC | us-central1-docker.pkg.dev/rich-compiler-479518-d2/containers-poc/ | In POC cluster | connectgateway_rich-compiler-479518-d2_us-central1_gke-poc-membership |
Key Facts:
- Images in production registry are NOT available to staging/poc
- Pushing to
main branch does NOT auto-sync staging/poc ArgoCD
- Each environment's ArgoCD must be synced separately
PRE-FLIGHT CHECKLIST (DO THIS FIRST!)
Before deploying to ANY environment, verify:
1. Does the image exist in the TARGET registry?
gcloud artifacts docker images list \
us-central1-docker.pkg.dev/<PROJECT>/<REPO>/funnelcake-relay \
--include-tags --limit=5
If image doesn't exist → BUILD AND PUSH IT FIRST!
2. Am I updating the correct overlay?
k8s/applications/funnelcake-relay/overlays/staging/kustomization.yaml
k8s/applications/funnelcake-relay/overlays/poc/kustomization.yaml
k8s/applications/funnelcake-relay/overlays/production/kustomization.yaml
3. What's currently deployed vs what's in git?
kubectl --context <CONTEXT> get deployment funnelcake-relay -n funnelcake \
-o jsonpath='{.spec.template.spec.containers[0].image}'
cat k8s/applications/funnelcake-relay/overlays/<ENV>/kustomization.yaml | grep newTag
Complete Deployment Workflow
Step 1: Get Latest Code
cd /Users/rabble/code/divine/divine-funnelcake
git pull
git log --oneline -3
Step 2: Build Images for AMD64 (CRITICAL)
IMPORTANT: Mac builds arm64 by default. GKE runs amd64. You MUST specify --platform linux/amd64.
docker buildx build --platform linux/amd64 --target api \
-t us-central1-docker.pkg.dev/dv-platform-prod/containers-production/funnelcake-api:COMMIT_HASH \
--push .
docker buildx build --platform linux/amd64 --target relay \
-t us-central1-docker.pkg.dev/dv-platform-prod/containers-production/funnelcake-relay:COMMIT_HASH \
--push .
If you get auth errors, run:
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
Step 3: Update Kustomize and Push
cd /Users/rabble/code/divine/divine-iac-coreconfig
sed -i '' 's/newTag: "OLD_HASH"/newTag: "NEW_HASH"/' \
k8s/applications/funnelcake-api/overlays/production/kustomization.yaml
sed -i '' 's/newTag: "OLD_HASH"/newTag: "NEW_HASH"/' \
k8s/applications/funnelcake-relay/overlays/production/kustomization.yaml
git add k8s/applications/funnelcake-*/overlays/production/kustomization.yaml
git commit -m "deploy(production): funnelcake NEW_HASH - description"
git pull --rebase origin main && git push origin main
Step 4: Trigger ArgoCD Sync
kubectl patch application funnelcake-api -n argocd --type=merge \
-p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}'
kubectl patch application funnelcake-relay -n argocd --type=merge \
-p '{"metadata":{"annotations":{"argocd.argoproj.io/refresh":"hard"}}}'
sleep 3
kubectl patch application funnelcake-api -n argocd --type=merge \
-p '{"operation":{"sync":{"revision":"HEAD"}}}'
kubectl patch application funnelcake-relay -n argocd --type=merge \
-p '{"operation":{"sync":{"revision":"HEAD"}}}'
Step 5: Wait for Rollout
kubectl rollout status deploy/funnelcake-api deploy/funnelcake-relay \
-n funnelcake --timeout=120s
Step 6: Verify
kubectl get deploy funnelcake-api -n funnelcake \
-o jsonpath='{.spec.template.spec.containers[0].image}'
Running Migrations
CRITICAL: Migration Entrypoint Has Two Modes
The database/entrypoint.sh detects ClickHouse connection mode from env vars:
- Mode 1 (CLICKHOUSE_URL): Assumes ClickHouse Cloud — forces port 9440 with TLS.
Use for production (ClickHouse Cloud at
*.clickhouse.cloud).
- Mode 2 (CLICKHOUSE_HOST + CLICKHOUSE_PORT): Direct host/port, no TLS assumption.
Use for staging/poc (self-hosted ClickHouse on port 9000).
If you pass CLICKHOUSE_URL for self-hosted ClickHouse, migrations will fail with
i/o timeout on port 9440 because the self-hosted instance listens on port 9000.
Build and Push Migration Image
cd /Users/rabble/code/divine/divine-funnelcake/database
docker buildx build --platform linux/amd64 \
-t us-central1-docker.pkg.dev/dv-platform-prod/containers-production/funnelcake-migrate:COMMIT_HASH \
--push .
Run Migration Job — PRODUCTION (ClickHouse Cloud)
kubectl delete job funnelcake-db-migrate -n funnelcake --ignore-not-found
cat <<'EOF' | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: funnelcake-db-migrate
namespace: funnelcake
spec:
ttlSecondsAfterFinished: 600
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: us-central1-docker.pkg.dev/dv-platform-prod/containers-production/funnelcake-migrate:COMMIT_HASH
args: ["up"]
env:
- name: CLICKHOUSE_URL
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_URL
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_PASSWORD
- name: CLICKHOUSE_DATABASE
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_DATABASE
EOF
Run Migration Job — STAGING/POC (Self-hosted ClickHouse)
kubectl --context <STAGING_CONTEXT> delete job funnelcake-db-migrate -n funnelcake --ignore-not-found
cat <<'EOF' | kubectl --context <STAGING_CONTEXT> apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: funnelcake-db-migrate
namespace: funnelcake
spec:
ttlSecondsAfterFinished: 600
backoffLimit: 3
template:
spec:
restartPolicy: OnFailure
containers:
- name: migrate
image: us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-migrate:COMMIT_HASH
args: ["up"]
env:
- name: CLICKHOUSE_HOST
value: "funnelcake-funnelcake-clickhouse.funnelcake.svc.cluster.local"
- name: CLICKHOUSE_PORT
value: "9000"
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_PASSWORD
- name: CLICKHOUSE_DATABASE
valueFrom:
secretKeyRef:
name: funnelcake-clickhouse-credentials
key: CLICKHOUSE_DATABASE
EOF
Check Migration Logs
sleep 10 && kubectl logs job/funnelcake-db-migrate -n funnelcake
Handle "Dirty Database" Error
If you see error: Dirty database version X, a previous migration failed partway:
kubectl delete job funnelcake-db-migrate -n funnelcake --ignore-not-found
Common Errors and Fixes
Error: "no match for platform in manifest: not found"
Cause: Image built for wrong architecture (arm64 on Mac, but GKE needs amd64)
Fix: Rebuild with --platform linux/amd64
Error: "Unauthenticated request" when pushing
Cause: Docker not authenticated to Artifact Registry
Fix: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
Error: "failed to push... fetch first"
Cause: Remote has new commits
Fix: git pull --rebase origin main && git push origin main
Error: "Dirty database version X"
Cause: Previous migration failed midway
Fix: Run force X to clear dirty state, then up again
Pods stuck in ImagePullBackOff
Causes:
- Wrong architecture (check with
kubectl describe pod)
- Image doesn't exist (typo in tag)
- Auth issues (check imagePullSecrets)
Key Paths
- Funnelcake repo:
/Users/rabble/code/divine/divine-funnelcake
- IaC repo:
/Users/rabble/code/divine/divine-iac-coreconfig
- API kustomization:
k8s/applications/funnelcake-api/overlays/production/kustomization.yaml
- Relay kustomization:
k8s/applications/funnelcake-relay/overlays/production/kustomization.yaml
- Migrations:
/Users/rabble/code/divine/divine-funnelcake/database/migrations/
Deploying to Staging or POC
Full Workflow for Non-Production Environments
cat k8s/applications/funnelcake-relay/overlays/production/kustomization.yaml | grep newTag
gcloud artifacts docker images list \
us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-relay \
--include-tags --limit=10 | grep e7e79eb
cd /Users/rabble/code/divine/divine-funnelcake
git checkout e7e79eb
docker build --platform linux/amd64 --target relay \
-t us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-relay:e7e79eb .
docker push us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-relay:e7e79eb
docker build --platform linux/amd64 --target api \
-t us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-api:e7e79eb .
docker push us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-api:e7e79eb
docker tag ...staging...:e7e79eb ...poc...:e7e79eb
docker push ...poc...:e7e79eb
git checkout main
cd /Users/rabble/code/divine/divine-iac-coreconfig
git add -A && git commit -m "deploy(staging, poc): funnelcake @ e7e79eb" && git push
kubectl --context connectgateway_dv-platform-staging_us-central1_gke-staging-membership \
patch application funnelcake-relay -n argocd --type merge \
-p '{"operation":{"initiatedBy":{"username":"claude"},"sync":{"syncStrategy":{"apply":{"force":false}}}}}'
kubectl --context connectgateway_dv-platform-staging_us-central1_gke-staging-membership \
get pods -n funnelcake
POST-DEPLOYMENT VERIFICATION CHECKLIST
Always run these after any deployment:
kubectl --context <CONTEXT> get pods -n funnelcake
kubectl --context <CONTEXT> get deployment funnelcake-relay -n funnelcake \
-o jsonpath='{.spec.template.spec.containers[0].image}'
curl -s https://relay.staging.dvines.org/ | grep -o "version.*" | head -1
Image Registry
- Production:
us-central1-docker.pkg.dev/dv-platform-prod/containers-production/
- Staging:
us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/
- POC:
us-central1-docker.pkg.dev/rich-compiler-479518-d2/containers-poc/
Notes
- Always use short commit hash (7 chars) for image tags
- Dockerfile has multi-stage build:
--target api or --target relay
- Migration image is in
database/Dockerfile
- ClickHouse credentials are in
funnelcake-clickhouse-credentials secret
- ArgoCD needs BOTH refresh AND sync operations