Merge pull request #206 from spoo-me/fix/ci-pyjwt-fastapi-test-compat #56
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
| name: Deploy to Production | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths-ignore: | |
| - '**/*.md' | |
| - '**/*.rst' | |
| - 'docs/**' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| outputs: | |
| image-tag: ${{ steps.meta.outputs.version }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v4 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GHCR_PAT }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@v6 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=raw,value=latest | |
| type=sha,prefix=,format=short | |
| - name: Build and push | |
| uses: docker/build-push-action@v7 | |
| with: | |
| context: . | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| platforms: linux/amd64 | |
| deploy: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| IMAGE_TAG: ${{ needs.build.outputs.image-tag }} | |
| steps: | |
| - name: Deploy via SSH | |
| uses: appleboy/ssh-action@v1.2.5 | |
| with: | |
| host: ${{ secrets.VPS_HOST }} | |
| username: root | |
| key: ${{ secrets.VPS_SSH_KEY }} | |
| port: 22 | |
| envs: IMAGE_TAG | |
| script: | | |
| set -e | |
| cd /opt/spoo | |
| echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | |
| # write IMAGE_TAG to .env so compose substitution picks it up | |
| grep -v '^IMAGE_TAG=' .env > .env.tmp 2>/dev/null || true | |
| echo "IMAGE_TAG=${IMAGE_TAG}" >> .env.tmp | |
| mv .env.tmp .env | |
| chmod 600 .env | |
| docker compose --env-file .env -f docker-compose.prod.yml pull app | |
| docker compose --env-file .env -f docker-compose.prod.yml up -d --no-deps app | |
| # Wait for app healthy before declaring success — Caddy keeps a | |
| # connection pool to app:8000 with retries, so users see a brief | |
| # latency bump (not 502) during the swap. | |
| status="" | |
| for i in 1 2 3 4 5 6 7 8; do | |
| status=$(docker inspect spoo_app --format '{{.State.Health.Status}}') | |
| [ "$status" = "healthy" ] && break | |
| echo "app $status, retry $i"; sleep 3 | |
| done | |
| if [ "$status" != "healthy" ]; then | |
| echo "::error::app failed to reach healthy after 8 retries (last status: $status)" | |
| docker compose --env-file .env -f docker-compose.prod.yml logs --tail=100 app | |
| exit 1 | |
| fi | |
| docker image prune -f | |
| docker compose --env-file .env -f docker-compose.prod.yml ps | |
| - name: Verify health | |
| uses: appleboy/ssh-action@v1.2.5 | |
| with: | |
| host: ${{ secrets.VPS_HOST }} | |
| username: root | |
| key: ${{ secrets.VPS_SSH_KEY }} | |
| port: 22 | |
| script: | | |
| sleep 10 | |
| for i in 1 2 3 4 5; do | |
| if docker exec spoo_app curl -fsS http://localhost:8000/health > /dev/null; then | |
| echo "Healthy after $i attempts" | |
| exit 0 | |
| fi | |
| echo "Attempt $i failed, retrying..." | |
| sleep 5 | |
| done | |
| echo "Health check failed after 5 attempts" | |
| docker compose -f /opt/spoo/docker-compose.prod.yml logs --tail=100 app | |
| exit 1 |