Skip to content

Merge pull request #206 from spoo-me/fix/ci-pyjwt-fastapi-test-compat #56

Merge pull request #206 from spoo-me/fix/ci-pyjwt-fastapi-test-compat

Merge pull request #206 from spoo-me/fix/ci-pyjwt-fastapi-test-compat #56

Workflow file for this run

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