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 echo "=== Git pull ===" git config --global --add safe.directory /opt/wordly git fetch origin production-deployment git reset --hard origin/production-deployment echo "=== Building images ===" docker compose build backend frontend echo "=== Starting services ===" docker compose up -d --remove-orphans echo "=== 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 "ERROR: Postgres not ready after 60s" docker compose logs postgres --tail=30 exit 1 fi sleep 2 done echo "=== Running database migrations ===" docker compose exec -T backend alembic upgrade head 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 "ERROR: Backend not healthy after 100s" docker compose logs backend --tail=50 exit 1 fi sleep 5 done echo "=== Deploy summary ===" docker compose ps echo "Health: $(curl -sf http://localhost:8001/health 2>/dev/null || echo 'FAILED')" 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