name: Deploy to Production on: push: branches: [main] # Only one deploy runs at a time; newer push cancels in-progress one concurrency: group: production-deploy cancel-in-progress: true jobs: deploy: name: Build & deploy on server runs-on: self-hosted # ← runs on YOUR Linux server timeout-minutes: 30 steps: - name: Checkout latest code uses: actions/checkout@v4 # Copy .env if it exists outside the repo (first deploy won't have it) - name: Ensure .env exists run: | if [ ! -f .env ]; then echo "⚠️ No .env found — copy .env.example and fill in secrets!" cp .env.example .env fi # Build only the changed service images, skip rebuild if nothing changed - name: Build Docker images run: | docker compose build --parallel \ --build-arg BUILDKIT_INLINE_CACHE=1 \ api web website koja env: DOCKER_BUILDKIT: 1 COMPOSE_DOCKER_CLI_BUILD: 1 # Rolling restart — database and redis are untouched if already healthy - name: Start / restart services run: | docker compose up -d \ --remove-orphans \ --no-deps \ postgres redis api web website koja # Wait for API healthcheck before declaring success - name: Wait for API health run: | echo "Waiting for API to become healthy..." for i in $(seq 1 24); do STATUS=$(docker inspect --format='{{.State.Health.Status}}' meezi-api 2>/dev/null || echo "missing") echo " attempt $i/24 — $STATUS" if [ "$STATUS" = "healthy" ]; then echo "✅ API is healthy" exit 0 fi sleep 5 done echo "❌ API did not become healthy in time" docker compose logs --tail=50 api exit 1 - name: Show running containers if: always() run: docker compose ps - name: Prune old images if: success() run: docker image prune -f