All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 3s
Three fixes to ensure alembic migrations always run in production: 1. entrypoint.sh: add `exec "$@"` passthrough so `docker compose run --rm backend alembic upgrade head` actually runs alembic instead of ignoring args and starting uvicorn. 2. entrypoint.sh: remove `|| echo` fallback on alembic — if migration fails the container must stop, not silently continue. 3. deploy.yml: start only postgres+redis first, run migration, THEN start backend. Previously the backend started before migration, ran with stale schema, and the separate migration step was broken by the entrypoint bug. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
149 lines
7.3 KiB
YAML
149 lines
7.3 KiB
YAML
name: Deploy to Production
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- production-deployment
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
deploy:
|
|
name: Build and Deploy
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- name: Setup SSH
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
|
chmod 600 ~/.ssh/id_rsa
|
|
ssh-keyscan -H 192.168.1.151 >> ~/.ssh/known_hosts
|
|
|
|
- name: Deploy via SSH
|
|
run: |
|
|
ssh root@192.168.1.151 << 'ENDSSH'
|
|
set -e
|
|
cd /opt/wordly
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 1. GIT PULL
|
|
# ──────────────────────────────────────────────
|
|
echo "=== [1/8] Git pull ==="
|
|
git config --global --add safe.directory /opt/wordly
|
|
git fetch origin production-deployment
|
|
git reset --hard origin/production-deployment
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 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 (--no-cache for fresh deps)
|
|
# ──────────────────────────────────────────────
|
|
echo "=== [3/8] Building images ==="
|
|
docker compose build --no-cache backend frontend
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 4. START INFRASTRUCTURE ONLY (postgres + redis)
|
|
# ──────────────────────────────────────────────
|
|
echo "=== [4/8] Starting postgres and redis ==="
|
|
docker compose up -d postgres redis
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 5. WAIT FOR POSTGRES
|
|
# ──────────────────────────────────────────────
|
|
echo "=== [5/8] Waiting for postgres ==="
|
|
for i in $(seq 1 30); do
|
|
if docker compose exec -T postgres pg_isready -U translate >/dev/null 2>&1; then
|
|
echo " Postgres ready after $((i * 2))s"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 30 ]; then
|
|
echo " FATAL: Postgres not ready after 60s"
|
|
docker compose logs postgres --tail=30
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 6. RUN MIGRATIONS (BEFORE starting backend)
|
|
# entrypoint passes through args via exec "$@"
|
|
# ──────────────────────────────────────────────
|
|
echo "=== [6/8] Running database migrations ==="
|
|
if ! docker compose run --rm backend alembic upgrade head; then
|
|
echo " FATAL: 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."
|
|
echo " Deploy ABORTED."
|
|
exit 1
|
|
fi
|
|
echo " Migrations applied successfully"
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 7. START ALL SERVICES (backend + frontend)
|
|
# backend entrypoint will see alembic is already at head → no-op
|
|
# ──────────────────────────────────────────────
|
|
echo "=== [7/8] Starting all services ==="
|
|
docker compose up -d --remove-orphans
|
|
|
|
# Wait for backend healthy
|
|
echo " Waiting for backend healthy..."
|
|
for i in $(seq 1 20); do
|
|
if curl -sf http://localhost:8001/health >/dev/null 2>&1; then
|
|
echo " Backend healthy after $((i * 5))s"
|
|
break
|
|
fi
|
|
if [ "$i" -eq 20 ]; then
|
|
echo " FATAL: 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
|
|
exit 1
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
# ──────────────────────────────────────────────
|
|
# 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 "Backup: ${BACKUP_FILE} (${BACKUP_SIZE})"
|
|
ENDSSH
|
|
|
|
- name: Wait for frontend
|
|
run: |
|
|
echo "Waiting for frontend..."
|
|
for i in $(seq 1 20); do
|
|
CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 http://192.168.1.151:3000/ || echo "000")
|
|
if [ "$CODE" != "000" ] && [ "$CODE" -lt 500 ]; then
|
|
echo "Frontend OK (HTTP $CODE) after $((i * 5))s"
|
|
exit 0
|
|
fi
|
|
echo " [$((i * 5))s] HTTP $CODE"
|
|
sleep 5
|
|
done
|
|
echo "Timeout!"
|
|
exit 1
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
run: ssh root@192.168.1.151 "docker image prune -f" || true
|