Funnelcake Deployment Workflow

SkillDatabases & data

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.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Funnelcake Deployment Workflow skill

What this skill tells your AI

The instructions your AI receives, as published by divinevideo/divine-mobile in .agents/skills/funnelcake-deployment-workflow/SKILL.md and read by ahel’s review.

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!

EnvironmentContainer RegistryArgoCDkubectl Context
Productionus-central1-docker.pkg.dev/dv-platform-prod/containers-production/In production clusterconnectgateway_dv-platform-prod_us-central1_gke-production-membership
Stagingus-central1-docker.pkg.dev/dv-platform-staging/containers-staging/In staging clusterconnectgateway_dv-platform-staging_us-central1_gke-staging-membership
POCus-central1-docker.pkg.dev/rich-compiler-479518-d2/containers-poc/In POC clusterconnectgateway_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?

# Check what images exist
gcloud artifacts docker images list \
  us-central1-docker.pkg.dev/<PROJECT>/<REPO>/funnelcake-relay \
  --include-tags --limit=5

# Projects/repos by environment:
# - Production: dv-platform-prod/containers-production
# - Staging: dv-platform-staging/containers-staging
# - POC: rich-compiler-479518-d2/containers-poc

If image doesn't exist → BUILD AND PUSH IT FIRST!

2. Am I updating the correct overlay?

# Staging overlay:
k8s/applications/funnelcake-relay/overlays/staging/kustomization.yaml

# POC overlay:
k8s/applications/funnelcake-relay/overlays/poc/kustomization.yaml

# Production overlay:
k8s/applications/funnelcake-relay/overlays/production/kustomization.yaml

3. What's currently deployed vs what's in git?

# Check deployed image
kubectl --context <CONTEXT> get deployment funnelcake-relay -n funnelcake \
  -o jsonpath='{.spec.template.spec.containers[0].image}'

# Check git kustomization
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  # Note the commit hash (e.g., e02d3b1)

Step 2: Build Images for AMD64 (CRITICAL)

IMPORTANT: Mac builds arm64 by default. GKE runs amd64. You MUST specify --platform linux/amd64.

# Build API image
docker buildx build --platform linux/amd64 --target api \
  -t us-central1-docker.pkg.dev/dv-platform-prod/containers-production/funnelcake-api:COMMIT_HASH \
  --push .

# Build Relay image
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

# Update image tags
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

# Commit and push (handle potential conflicts)
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

# Refresh to pick up git changes
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"}}}'

# Wait, then trigger sync
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)

# Use CLICKHOUSE_HOST + CLICKHOUSE_PORT instead of CLICKHOUSE_URL!
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:

# Force the version to clear dirty state
kubectl delete job funnelcake-db-migrate -n funnelcake --ignore-not-found
# Create job with args: ["force", "X"] instead of ["up"]
# Then run "up" again

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:

  1. Wrong architecture (check with kubectl describe pod)
  2. Image doesn't exist (typo in tag)
  3. 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

# 1. Check what's in production that we want to deploy
cat k8s/applications/funnelcake-relay/overlays/production/kustomization.yaml | grep newTag
# e.g., newTag: "e7e79eb"

# 2. CHECK if that image exists in staging registry
gcloud artifacts docker images list \
  us-central1-docker.pkg.dev/dv-platform-staging/containers-staging/funnelcake-relay \
  --include-tags --limit=10 | grep e7e79eb

# 3. If NOT found → BUILD AND PUSH
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

# For POC, tag and push to POC registry too
docker tag ...staging...:e7e79eb ...poc...:e7e79eb
docker push ...poc...:e7e79eb

git checkout main  # Return to main

# 4. Update kustomization (only AFTER images exist!)
cd /Users/rabble/code/divine/divine-iac-coreconfig
# Edit staging/poc overlays with new tags

# 5. Commit and push
git add -A && git commit -m "deploy(staging, poc): funnelcake @ e7e79eb" && git push

# 6. Sync ArgoCD (each environment has its own!)
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}}}}}'

# 7. VERIFY pods are actually running (not ImagePullBackOff!)
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:

# 1. Check pod status (should be Running, not ImagePullBackOff)
kubectl --context <CONTEXT> get pods -n funnelcake

# 2. Check actual deployed image matches expected
kubectl --context <CONTEXT> get deployment funnelcake-relay -n funnelcake \
  -o jsonpath='{.spec.template.spec.containers[0].image}'

# 3. Check the website actually works
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

Signals

GitHub stars
265
Forks
55
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
funnelcake-deployment-workflow
Source
github.com/divinevideo/divine-mobile