chore: refactor docker-compose configuration to integrate Traefik for… #375
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ============================================================================ | |
| # Production CI/CD Pipeline - Traefik Zero-Downtime Deployment | |
| # ============================================================================ | |
| # | |
| # Architecture: | |
| # Traefik (SSL/LB) → Services (auto-discovered via Docker labels) | |
| # | |
| # Deployment Strategy: | |
| # Rolling update - Traefik handles graceful traffic shifting | |
| # New containers start → health check passes → old containers removed | |
| # | |
| # ============================================================================ | |
| name: CI-Production | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| env: | |
| ENV_SOURCE: /opt/projects/prod.docs.plus/.env | |
| ENV_FILE: .env.production | |
| COMPOSE_FILE: docker-compose.prod.yml | |
| DEPLOY_TAG: ${{ github.sha }} | |
| jobs: | |
| deploy: | |
| name: 🚀 Deploy to Production | |
| runs-on: prod.docs.plus | |
| if: contains(github.event.head_commit.message, 'build') && (contains(github.event.head_commit.message, 'front') || contains(github.event.head_commit.message, 'back')) | |
| steps: | |
| - name: 📦 Checkout Code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 1 | |
| - name: 🥟 Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: 📥 Install Dependencies | |
| run: bun install --frozen-lockfile | |
| - name: 🔐 Prepare Environment | |
| run: | | |
| echo "📁 Copying environment file..." | |
| if [ ! -f "${{ env.ENV_SOURCE }}" ]; then | |
| echo "❌ .env file not found at ${{ env.ENV_SOURCE }}" | |
| exit 1 | |
| fi | |
| cp "${{ env.ENV_SOURCE }}" "${{ env.ENV_FILE }}" | |
| # Add deploy tag | |
| echo "DEPLOY_TAG=${{ env.DEPLOY_TAG }}" >> "${{ env.ENV_FILE }}" | |
| # Validate required vars | |
| for var in DATABASE_URL SUPABASE_URL SUPABASE_ANON_KEY; do | |
| if ! grep -q "^${var}=" "${{ env.ENV_FILE }}"; then | |
| echo "❌ ${var} not found in .env" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ Environment prepared" | |
| - name: 🏗️ Build Docker Images | |
| run: | | |
| echo "🔨 Building images with tag: ${{ env.DEPLOY_TAG }}" | |
| # Load env vars for build args | |
| set -a | |
| source ${{ env.ENV_FILE }} | |
| set +a | |
| docker-compose -f ${{ env.COMPOSE_FILE }} \ | |
| --env-file ${{ env.ENV_FILE }} \ | |
| build --parallel | |
| echo "✅ Images built" | |
| - name: 🚀 Deploy Services | |
| run: | | |
| echo "🚀 Deploying services..." | |
| # Deploy with rolling update | |
| # Traefik automatically routes to healthy containers | |
| docker-compose -f ${{ env.COMPOSE_FILE }} \ | |
| --env-file ${{ env.ENV_FILE }} \ | |
| up -d \ | |
| --remove-orphans \ | |
| --scale webapp=2 \ | |
| --scale rest-api=2 \ | |
| --scale hocuspocus-server=2 \ | |
| --scale hocuspocus-worker=1 | |
| echo "✅ Services deployed" | |
| - name: ⏳ Wait for Health Checks | |
| run: | | |
| echo "⏳ Waiting for services to be healthy..." | |
| sleep 30 | |
| # Check Traefik | |
| if ! docker ps --filter "name=traefik" --filter "health=healthy" | grep -q traefik; then | |
| echo "⚠️ Traefik not healthy yet, waiting..." | |
| sleep 15 | |
| fi | |
| echo "✅ Initial wait complete" | |
| - name: 🩺 Verify Deployment | |
| run: | | |
| echo "🩺 Verifying deployment..." | |
| # Check all services are running | |
| SERVICES="traefik docsplus-redis" | |
| for svc in $SERVICES; do | |
| if ! docker ps --filter "name=$svc" | grep -q "$svc"; then | |
| echo "❌ $svc is not running" | |
| docker logs $svc --tail 50 2>/dev/null || true | |
| exit 1 | |
| fi | |
| done | |
| # Check scaled services | |
| for svc in webapp rest-api hocuspocus-server hocuspocus-worker; do | |
| COUNT=$(docker ps --filter "name=${svc}" --format "{{.Names}}" | wc -l) | |
| if [ "$COUNT" -eq 0 ]; then | |
| echo "❌ No $svc containers running" | |
| exit 1 | |
| fi | |
| echo "✅ $svc: $COUNT container(s) running" | |
| done | |
| # Health check via Traefik | |
| echo "🔍 Testing endpoints via Traefik..." | |
| # Wait for SSL cert (first deploy might take a moment) | |
| for i in {1..30}; do | |
| if curl -sf -o /dev/null https://docs.plus/api/health 2>/dev/null; then | |
| echo "✅ docs.plus is healthy" | |
| break | |
| fi | |
| if [ $i -eq 30 ]; then | |
| echo "⚠️ docs.plus health check timed out (may still be provisioning SSL)" | |
| fi | |
| sleep 5 | |
| done | |
| echo "✅ Deployment verified" | |
| - name: 🧹 Cleanup | |
| run: | | |
| # Remove old images | |
| docker image prune -f --filter "until=24h" | |
| # Remove unused volumes (careful!) | |
| docker volume prune -f --filter "label!=keep" | |
| echo "✅ Cleanup complete" | |
| - name: 📊 Summary | |
| run: | | |
| echo "======================================" | |
| echo "✅ DEPLOYMENT SUCCESSFUL" | |
| echo "======================================" | |
| echo "Tag: ${{ env.DEPLOY_TAG }}" | |
| echo "" | |
| echo "Services:" | |
| docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | head -20 | |
| echo "" | |
| echo "URLs:" | |
| echo " - https://docs.plus" | |
| echo " - https://prodback.docs.plus" | |
| echo "======================================" | |
| - name: 🚨 Rollback on Failure | |
| if: failure() | |
| run: | | |
| echo "⚠️ Deployment failed - attempting rollback..." | |
| # Get previous working tag (if any) | |
| PREV_TAG=$(docker images docsy-webapp --format "{{.Tag}}" | grep -v "${{ env.DEPLOY_TAG }}" | head -1) | |
| if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "latest" ]; then | |
| echo "🔄 Rolling back to: $PREV_TAG" | |
| sed -i "s/DEPLOY_TAG=.*/DEPLOY_TAG=$PREV_TAG/" "${{ env.ENV_FILE }}" | |
| docker-compose -f ${{ env.COMPOSE_FILE }} \ | |
| --env-file ${{ env.ENV_FILE }} \ | |
| up -d --remove-orphans | |
| echo "✅ Rollback complete" | |
| else | |
| echo "⚠️ No previous version to rollback to" | |
| fi | |
| # =========================================================================== | |
| # UPTIME KUMA (Monitoring) | |
| # =========================================================================== | |
| deploy-uptime-kuma: | |
| name: 🔔 Deploy Uptime Kuma | |
| runs-on: prod.docs.plus | |
| if: contains(github.event.head_commit.message, 'build') && contains(github.event.head_commit.message, 'uptime-kuma') | |
| steps: | |
| - name: 🚀 Deploy | |
| run: | | |
| docker network create docsplus-network 2>/dev/null || true | |
| docker stop uptime-kuma 2>/dev/null || true | |
| docker rm uptime-kuma 2>/dev/null || true | |
| docker run -d \ | |
| --name uptime-kuma \ | |
| --network docsplus-network \ | |
| --restart unless-stopped \ | |
| -v uptime-kuma-data:/app/data \ | |
| --label "traefik.enable=true" \ | |
| --label "traefik.http.routers.uptime.rule=Host(\`status.docs.plus\`)" \ | |
| --label "traefik.http.routers.uptime.entrypoints=websecure" \ | |
| --label "traefik.http.routers.uptime.tls.certresolver=letsencrypt" \ | |
| --label "traefik.http.services.uptime.loadbalancer.server.port=3001" \ | |
| louislam/uptime-kuma:latest | |
| sleep 15 | |
| echo "✅ Uptime Kuma deployed at https://status.docs.plus" |