step 9: single image CI/CD, drop FE/BE split
Some checks failed
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Failing after 5s
CI/CD / test-sqlite (push) Successful in 2m37s
CI/CD / test-go-pg (push) Successful in 1m53s
CI/CD / build-and-deploy (push) Has been skipped

CI pipeline: replace separate FE + BE image builds with single unified
Dockerfile. Remove FE_IMAGE, FE_REPLICAS, FE_* resource vars. Simplify
build/push/deploy to single IMAGE. Remove frontend.yaml k8s manifest.
Remove provider integration test env vars (PROVIDER, PROVIDER_KEY,
VENICE_API_KEY). Remove EXTRACTION_CONCURRENCY.

Delete server/Dockerfile (backend-only image, superseded by root
Dockerfile). Delete k8s/frontend.yaml (no separate FE deployment).

Update ROADMAP: steps 5-7 and 9 marked complete. Step 8 (ICD) partial.
Step 10 (smoke test + tag) is all that remains for v0.1.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:10:47 +00:00
parent ec750f4981
commit 71d448406c
4 changed files with 46 additions and 240 deletions

View File

@@ -2,8 +2,8 @@
# ============================================
# Switchboard Core - CI/CD Pipeline (v0.17.3)
# ============================================
# Cluster deployments use SEPARATE FE + BE images.
# Unified image is for Docker Hub only (docker-compose use).
# Single unified image (Go backend + nginx frontend).
# v0.1.0: Dropped FE/BE image split per ROADMAP design decision.
#
# Pipeline:
# 0. Detect changes (path-based gating for all downstream jobs)
@@ -88,8 +88,7 @@ env:
CERT_ISSUER: ${{ vars.CERT_ISSUER_PROD || 'letsencrypt-prod' }}
POSTGRES_HOST: ${{ vars.POSTGRES_HOST || 'postgres-primary.postgres.svc.cluster.local' }}
POSTGRES_PORT: ${{ vars.POSTGRES_PORT || '5432' }}
FE_IMAGE: ${{ vars.REGISTRY }}/xcaliber/switchboard-fe
BE_IMAGE: ${{ vars.REGISTRY }}/xcaliber/switchboard-be
IMAGE: ${{ vars.REGISTRY }}/switchboard/core
DOCKERHUB_IMAGE: ${{ vars.DOCKERHUB_IMAGE || 'gobha/switchboard-core' }}
jobs:
@@ -297,12 +296,6 @@ jobs:
env:
GOPRIVATE: git.gobha.me/*
GONOSUMCHECK: git.gobha.me/*
# Generic provider config for live integration tests
PROVIDER: ${{ vars.PROVIDER }}
PROVIDER_KEY: ${{ secrets.PROVIDER_KEY }}
PROVIDER_URL: ${{ vars.PROVIDER_URL }}
# Legacy compat: VENICE_API_KEY still works if PROVIDER_KEY is not set
VENICE_API_KEY: ${{ secrets.VENICE_API_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -415,16 +408,11 @@ jobs:
echo "DEPLOY_SUFFIX=-dev" >> "$GITHUB_OUTPUT"
echo "DB_NAME=switchboard_core_dev" >> "$GITHUB_OUTPUT"
echo "DB_WIPE=true" >> "$GITHUB_OUTPUT"
echo "FE_REPLICAS=1" >> "$GITHUB_OUTPUT"
echo "BE_REPLICAS=1" >> "$GITHUB_OUTPUT"
echo "FE_MEMORY_REQUEST=64Mi" >> "$GITHUB_OUTPUT"
echo "FE_MEMORY_LIMIT=128Mi" >> "$GITHUB_OUTPUT"
echo "FE_CPU_REQUEST=25m" >> "$GITHUB_OUTPUT"
echo "FE_CPU_LIMIT=100m" >> "$GITHUB_OUTPUT"
echo "BE_MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT"
echo "BE_MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT"
echo "BE_CPU_REQUEST=50m" >> "$GITHUB_OUTPUT"
echo "BE_CPU_LIMIT=250m" >> "$GITHUB_OUTPUT"
echo "REPLICAS=1" >> "$GITHUB_OUTPUT"
echo "MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT"
echo "MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT"
echo "CPU_REQUEST=50m" >> "$GITHUB_OUTPUT"
echo "CPU_LIMIT=250m" >> "$GITHUB_OUTPUT"
echo "env_label=dev (PR #${{ gitea.event.pull_request.number }})" >> "$GITHUB_OUTPUT"
elif [[ "${{ gitea.ref }}" == refs/tags/v* ]]; then
VERSION="${{ gitea.ref_name }}"
@@ -436,16 +424,11 @@ jobs:
echo "DEPLOY_SUFFIX=" >> "$GITHUB_OUTPUT"
echo "DB_NAME=switchboard_core" >> "$GITHUB_OUTPUT"
echo "DB_WIPE=false" >> "$GITHUB_OUTPUT"
echo "FE_REPLICAS=2" >> "$GITHUB_OUTPUT"
echo "BE_REPLICAS=2" >> "$GITHUB_OUTPUT"
echo "FE_MEMORY_REQUEST=64Mi" >> "$GITHUB_OUTPUT"
echo "FE_MEMORY_LIMIT=128Mi" >> "$GITHUB_OUTPUT"
echo "FE_CPU_REQUEST=50m" >> "$GITHUB_OUTPUT"
echo "FE_CPU_LIMIT=200m" >> "$GITHUB_OUTPUT"
echo "BE_MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT"
echo "BE_MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT"
echo "BE_CPU_REQUEST=100m" >> "$GITHUB_OUTPUT"
echo "BE_CPU_LIMIT=500m" >> "$GITHUB_OUTPUT"
echo "REPLICAS=2" >> "$GITHUB_OUTPUT"
echo "MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT"
echo "MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT"
echo "CPU_REQUEST=100m" >> "$GITHUB_OUTPUT"
echo "CPU_LIMIT=500m" >> "$GITHUB_OUTPUT"
echo "is_release=true" >> "$GITHUB_OUTPUT"
echo "env_label=production (${VERSION})" >> "$GITHUB_OUTPUT"
else
@@ -456,16 +439,11 @@ jobs:
echo "DEPLOY_SUFFIX=-test" >> "$GITHUB_OUTPUT"
echo "DB_NAME=switchboard_core_test" >> "$GITHUB_OUTPUT"
echo "DB_WIPE=false" >> "$GITHUB_OUTPUT"
echo "FE_REPLICAS=1" >> "$GITHUB_OUTPUT"
echo "BE_REPLICAS=1" >> "$GITHUB_OUTPUT"
echo "FE_MEMORY_REQUEST=64Mi" >> "$GITHUB_OUTPUT"
echo "FE_MEMORY_LIMIT=128Mi" >> "$GITHUB_OUTPUT"
echo "FE_CPU_REQUEST=25m" >> "$GITHUB_OUTPUT"
echo "FE_CPU_LIMIT=100m" >> "$GITHUB_OUTPUT"
echo "BE_MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT"
echo "BE_MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT"
echo "BE_CPU_REQUEST=50m" >> "$GITHUB_OUTPUT"
echo "BE_CPU_LIMIT=250m" >> "$GITHUB_OUTPUT"
echo "REPLICAS=1" >> "$GITHUB_OUTPUT"
echo "MEMORY_REQUEST=256Mi" >> "$GITHUB_OUTPUT"
echo "MEMORY_LIMIT=512Mi" >> "$GITHUB_OUTPUT"
echo "CPU_REQUEST=50m" >> "$GITHUB_OUTPUT"
echo "CPU_LIMIT=250m" >> "$GITHUB_OUTPUT"
echo "env_label=test (main)" >> "$GITHUB_OUTPUT"
fi
@@ -577,45 +555,33 @@ jobs:
SQL
echo "✓ Dev wipe complete — backend will rebuild schema on startup"
# ── Build Backend Image ──────────────────────
- name: Build backend image
# ── Build Unified Image ──────────────────────
- name: Build image
run: |
docker build -f server/Dockerfile \
-t ${BE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} \
.
# ── Build Frontend Image ─────────────────────
- name: Build frontend image
run: |
docker build -f Dockerfile.frontend \
-t ${FE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} \
docker build -f Dockerfile \
-t ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} \
.
# ── Tag release versions ─────────────────────
- name: Tag release versions
if: steps.setup.outputs.EXTRA_TAG != ''
run: |
docker tag ${BE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${BE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
docker tag ${FE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${FE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
docker tag ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
# ── Push to Internal Registry ────────────────
- name: Push images
- name: Push image
run: |
docker push ${BE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }}
docker push ${FE_IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }}
docker push ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }}
if [[ -n "${{ steps.setup.outputs.EXTRA_TAG }}" ]]; then
docker push ${BE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
docker push ${FE_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
docker push ${IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
fi
# ── Build + Push Unified to Docker Hub (release only) ──
- name: Build and push unified image
# ── Push to Docker Hub (release only) ────────
- name: Push to Docker Hub
if: steps.setup.outputs.is_release == 'true'
run: |
docker build -f Dockerfile \
-t ${DOCKERHUB_IMAGE}:latest \
-t ${DOCKERHUB_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }} \
.
docker tag ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${DOCKERHUB_IMAGE}:latest
docker tag ${IMAGE}:${{ steps.setup.outputs.IMAGE_TAG }} ${DOCKERHUB_IMAGE}:${{ steps.setup.outputs.EXTRA_TAG }}
if [[ -n "${{ secrets.DOCKERHUB_TOKEN }}" ]]; then
echo "${{ secrets.DOCKERHUB_TOKEN }}" | \
docker login -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin
@@ -692,21 +658,15 @@ jobs:
DEPLOY_HOST: ${{ steps.setup.outputs.DEPLOY_HOST }}
BASE_PATH: ${{ steps.setup.outputs.BASE_PATH }}
DB_NAME: ${{ steps.setup.outputs.DB_NAME }}
FE_REPLICAS: ${{ steps.setup.outputs.FE_REPLICAS }}
BE_REPLICAS: ${{ steps.setup.outputs.BE_REPLICAS }}
FE_MEMORY_REQUEST: ${{ steps.setup.outputs.FE_MEMORY_REQUEST }}
FE_MEMORY_LIMIT: ${{ steps.setup.outputs.FE_MEMORY_LIMIT }}
FE_CPU_REQUEST: ${{ steps.setup.outputs.FE_CPU_REQUEST }}
FE_CPU_LIMIT: ${{ steps.setup.outputs.FE_CPU_LIMIT }}
BE_MEMORY_REQUEST: ${{ steps.setup.outputs.BE_MEMORY_REQUEST }}
BE_MEMORY_LIMIT: ${{ steps.setup.outputs.BE_MEMORY_LIMIT }}
BE_CPU_REQUEST: ${{ steps.setup.outputs.BE_CPU_REQUEST }}
BE_CPU_LIMIT: ${{ steps.setup.outputs.BE_CPU_LIMIT }}
REPLICAS: ${{ steps.setup.outputs.REPLICAS }}
MEMORY_REQUEST: ${{ steps.setup.outputs.MEMORY_REQUEST }}
MEMORY_LIMIT: ${{ steps.setup.outputs.MEMORY_LIMIT }}
CPU_REQUEST: ${{ steps.setup.outputs.CPU_REQUEST }}
CPU_LIMIT: ${{ steps.setup.outputs.CPU_LIMIT }}
# Storage (v0.12.0+)
STORAGE_CLASS: ${{ vars.STORAGE_CLASS }}
STORAGE_SIZE: ${{ vars.STORAGE_SIZE || '10Gi' }}
STORAGE_BACKEND: ${{ vars.STORAGE_BACKEND || 'pvc' }}
EXTRACTION_CONCURRENCY: ${{ vars.EXTRACTION_CONCURRENCY || '3' }}
run: |
# Render PVC first (must exist before backend references it)
if [[ -n "${STORAGE_CLASS}" ]]; then
@@ -714,7 +674,6 @@ jobs:
fi
envsubst < k8s/backend.yaml > /tmp/backend.yaml
envsubst < k8s/frontend.yaml > /tmp/frontend.yaml
envsubst < k8s/middleware-retry.yaml > /tmp/middleware-retry.yaml
envsubst < k8s/ingress.yaml > /tmp/ingress.yaml
@@ -729,7 +688,6 @@ jobs:
fi
kubectl apply -f /tmp/backend.yaml
kubectl apply -f /tmp/frontend.yaml
# Traefik retry middleware (v0.28.8) — requires RBAC for traefik.io CRDs.
# First-time setup: kubectl apply -f k8s/rbac-traefik.yaml (cluster admin)
@@ -760,23 +718,19 @@ jobs:
SUFFIX: ${{ steps.setup.outputs.DEPLOY_SUFFIX }}
ENV: ${{ steps.setup.outputs.ENVIRONMENT }}
run: |
kubectl rollout restart deployment/switchboard-be${SUFFIX} -n ${NAMESPACE}
kubectl rollout restart deployment/switchboard-fe${SUFFIX} -n ${NAMESPACE}
kubectl rollout status deployment/switchboard-be${SUFFIX} -n ${NAMESPACE} --timeout=180s
kubectl rollout status deployment/switchboard-fe${SUFFIX} -n ${NAMESPACE} --timeout=180s
kubectl rollout restart deployment/switchboard${SUFFIX} -n ${NAMESPACE}
kubectl rollout status deployment/switchboard${SUFFIX} -n ${NAMESPACE} --timeout=180s
echo ""
echo "━━━ Pod Status ━━━"
kubectl get pods -n ${NAMESPACE} -l app=switchboard,env=${ENV}
BE_READY=$(kubectl get deployment switchboard-be${SUFFIX} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}')
FE_READY=$(kubectl get deployment switchboard-fe${SUFFIX} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}')
READY=$(kubectl get deployment switchboard${SUFFIX} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}')
if [[ "${BE_READY:-0}" -gt 0 ]] && [[ "${FE_READY:-0}" -gt 0 ]]; then
echo "✅ Healthy: BE=${BE_READY} FE=${FE_READY} pods ready"
if [[ "${READY:-0}" -gt 0 ]]; then
echo "✅ Healthy: ${READY} pods ready"
else
echo "❌ Unhealthy: BE=${BE_READY:-0} FE=${FE_READY:-0}"
echo "❌ Unhealthy: ${READY:-0} pods ready"
kubectl logs -n ${NAMESPACE} -l app=switchboard,env=${ENV} --tail=30
exit 1
fi