feat: production CI/CD with DB backup and auto-rollback
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2s

8-step pipeline:
1. Git pull
2. pg_dumpall + gzip backup (keep last 10)
3. Build images (with cache)
4. Start services
5. Wait for postgres
6. Run alembic migration (one-shot container)
   - On failure: auto-restore DB from backup
7. Health check backend
   - On failure: auto-rollback DB + restart
8. Summary with backup path

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 10:02:36 +02:00
parent 392418c3f9
commit 413f610f1a

View File

@@ -24,54 +24,106 @@ jobs:
set -e set -e
cd /opt/wordly cd /opt/wordly
echo "=== Git pull ===" # ──────────────────────────────────────────────
# 1. GIT PULL
# ──────────────────────────────────────────────
echo "=== [1/8] Git pull ==="
git config --global --add safe.directory /opt/wordly git config --global --add safe.directory /opt/wordly
git fetch origin production-deployment git fetch origin production-deployment
git reset --hard origin/production-deployment git reset --hard origin/production-deployment
echo "=== Building images ===" # ──────────────────────────────────────────────
# 2. DATABASE BACKUP (before anything else)
# ──────────────────────────────────────────────
echo "=== [2/8] Database backup ==="
BACKUP_DIR="/opt/backups/postgres"
BACKUP_FILE="${BACKUP_DIR}/translate_db_$(date +%Y%m%d_%H%M%S).sql.gz"
mkdir -p "$BACKUP_DIR"
docker compose exec -T postgres pg_dumpall -U translate | gzip > "$BACKUP_FILE"
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
echo " ✅ Backup saved: ${BACKUP_FILE} (${BACKUP_SIZE})"
# Keep only last 10 backups
ls -t "${BACKUP_DIR}"/translate_db_*.sql.gz | tail -n +11 | xargs -r rm --
echo " 📦 Retained last 10 backups"
# ──────────────────────────────────────────────
# 3. BUILD IMAGES
# ──────────────────────────────────────────────
echo "=== [3/8] Building images ==="
docker compose build backend frontend docker compose build backend frontend
echo "=== Starting services ===" # ──────────────────────────────────────────────
# 4. START SERVICES
# ──────────────────────────────────────────────
echo "=== [4/8] Starting services ==="
docker compose up -d --remove-orphans docker compose up -d --remove-orphans
echo "=== Waiting for postgres ===" # ──────────────────────────────────────────────
# 5. WAIT FOR POSTGRES
# ──────────────────────────────────────────────
echo "=== [5/8] Waiting for postgres ==="
for i in $(seq 1 30); do for i in $(seq 1 30); do
if docker compose exec -T postgres pg_isready -U translate >/dev/null 2>&1; then if docker compose exec -T postgres pg_isready -U translate >/dev/null 2>&1; then
echo "Postgres ready after $((i * 2))s" echo "Postgres ready after $((i * 2))s"
break break
fi fi
if [ "$i" -eq 30 ]; then if [ "$i" -eq 30 ]; then
echo "ERROR: Postgres not ready after 60s" echo " Postgres not ready after 60s"
docker compose logs postgres --tail=30 docker compose logs postgres --tail=30
exit 1 exit 1
fi fi
sleep 2 sleep 2
done done
echo "=== Running database migrations (one-shot container) ===" # ──────────────────────────────────────────────
docker compose run --rm backend alembic upgrade head # 6. RUN MIGRATIONS (one-shot container)
# ──────────────────────────────────────────────
echo "=== [6/8] Running database migrations ==="
if ! docker compose run --rm backend alembic upgrade head; then
echo " ❌ Migration FAILED!"
echo " ⚠️ Restoring database from backup..."
gunzip -c "$BACKUP_FILE" | docker compose exec -T postgres psql -U translate -d translate_db >/dev/null 2>&1 || true
echo " 🔄 Database restored. Restarting services..."
docker compose restart backend
echo " ❌ Deploy aborted. Database restored from backup."
exit 1
fi
echo " ✅ Migrations applied"
echo "=== Restarting backend to pick up migration ===" # Restart backend to pick up new schema
docker compose restart backend docker compose restart backend
echo "=== Waiting for backend healthy ===" # ──────────────────────────────────────────────
# 7. HEALTH CHECK
# ──────────────────────────────────────────────
echo "=== [7/8] Waiting for backend healthy ==="
for i in $(seq 1 20); do for i in $(seq 1 20); do
if curl -sf http://localhost:8001/health >/dev/null 2>&1; then if curl -sf http://localhost:8001/health >/dev/null 2>&1; then
echo "Backend healthy after $((i * 5))s" echo "Backend healthy after $((i * 5))s"
break break
fi fi
if [ "$i" -eq 20 ]; then if [ "$i" -eq 20 ]; then
echo "ERROR: Backend not healthy after 100s" echo " Backend not healthy after 100s"
echo " ⚠️ Rolling back database..."
gunzip -c "$BACKUP_FILE" | docker compose exec -T postgres psql -U translate -d translate_db >/dev/null 2>&1 || true
docker compose restart backend
echo " ❌ Deploy failed. Database restored."
docker compose logs backend --tail=50 docker compose logs backend --tail=50
exit 1 exit 1
fi fi
sleep 5 sleep 5
done done
echo "=== Deploy summary ===" # ──────────────────────────────────────────────
docker compose ps # 8. SUMMARY
# ──────────────────────────────────────────────
echo "=== [8/8] Deploy summary ==="
docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "Health: $(curl -sf http://localhost:8001/health 2>/dev/null || echo 'FAILED')" echo "Health: $(curl -sf http://localhost:8001/health 2>/dev/null || echo 'FAILED')"
echo "Backup: ${BACKUP_FILE} (${BACKUP_SIZE})"
ENDSSH ENDSSH
- name: Wait for frontend - name: Wait for frontend