Deploy #260
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 | |
| on: | |
| workflow_run: | |
| workflows: [CI] | |
| branches: [main] | |
| types: [completed] | |
| # Only ever one deploy in flight; a newer push supersedes an older one still queued. | |
| concurrency: | |
| group: deploy-production | |
| cancel-in-progress: false | |
| # No checkout and no GitHub API call here — everything happens over SSH on | |
| # the VPS with its own secrets, so GITHUB_TOKEN needs no scope at all. | |
| permissions: {} | |
| jobs: | |
| deploy: | |
| if: github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Deploy over SSH | |
| env: | |
| DEPLOY_SHA: ${{ github.event.workflow_run.head_sha }} | |
| uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1 | |
| with: | |
| host: ${{ secrets.VPS_HOST }} | |
| username: ${{ secrets.VPS_USER }} | |
| key: ${{ secrets.VPS_SSH_KEY }} | |
| envs: DEPLOY_SHA | |
| script: | | |
| set -e | |
| cd ${{ secrets.VPS_DEPLOY_PATH }} | |
| git fetch origin main | |
| # Deploy the exact commit CI validated, not whatever HEAD of main | |
| # happens to be by the time this job runs — main could have moved | |
| # again in between (e.g. a second push landing before this job starts). | |
| git reset --hard "$DEPLOY_SHA" | |
| # Which override files to combine (prod, observability, portainer, | |
| # glitchtip, ...) is NOT hardcoded here — it comes from COMPOSE_FILE | |
| # in the VPS's own .env (Compose reads that automatically). Adding a | |
| # new optional add-on later means editing .env on the VPS, not this | |
| # workflow. See .env.example / README "Self-hosting" for the format. | |
| # | |
| # IMAGE_TAG pins docker-compose.yml's api/web `image:` to the | |
| # exact images the CI docker-push job built and pushed to GHCR | |
| # for this SHA (see .github/workflows/ci.yml), instead of the | |
| # floating `latest` tag — same "exact commit CI validated" | |
| # guarantee as the git reset above. Also feeds the Homepage | |
| # "Version" widget, which reads GIT_SHA baked into the api image. | |
| export IMAGE_TAG=$(git rev-parse --short HEAD) | |
| docker compose pull | |
| # --remove-orphans cleans up containers for services dropped from | |
| # COMPOSE_FILE (e.g. an add-on removed from the VPS .env). It does | |
| # NOT address "port is already allocated" on a service that's | |
| # still part of the config — that's usually a stale container | |
| # from an interrupted previous deploy not yet releasing its port, | |
| # a transient docker daemon race, so retry a few times before | |
| # giving up. | |
| for attempt in 1 2 3; do | |
| if docker compose up -d --remove-orphans; then | |
| break | |
| fi | |
| if [ "$attempt" = 3 ]; then | |
| echo "docker compose up -d failed after 3 attempts" >&2 | |
| exit 1 | |
| fi | |
| echo "docker compose up -d failed (attempt $attempt/3), retrying in 5s..." | |
| sleep 5 | |
| done |