# Publish and redeploy (US08-04). Adapted from the crowdsec-admin deploy workflow: # build, log in to the Gitea registry, push, trigger the Portainer webhook, prune. # The difference is the gate — this project has a required suite that must not be # skipped, so publishing happens only after `Test` (`.gitea/workflows/test.yml`) # succeeded on `main`, never on the push itself. # # The stack is managed by Portainer from git (`docker-compose.yml`), and the runtime # secrets it needs — vision key, Immich key, access secret — live in the Portainer # stack's environment. They are deliberately not repository secrets and are not in the # image: rotation stays in one place, and a repository read never discloses them. # # Repository secrets required: # REGISTRY_USER user with write:package on the registry # REGISTRY_TOKEN that user's token # PORTAINER_WEBHOOK_URL POST URL from the stack's auto-update setting name: Deploy on: workflow_run: workflows: - Test types: - completed branches: - main workflow_dispatch: inputs: dry_run: description: Build and push a scratch tag only — leave `latest` and the running stack alone type: boolean default: true concurrency: # Deliberately not keyed by commit: the point is that two deploys of *different* # commits cannot overlap. Queued, not cancelled — a half-pushed tag set is worse # than a late one. group: deploy cancel-in-progress: false env: IMAGE: git.domverse-berlin.eu/domverse/photoanalyzer # The commit that was tested, not whatever `main` points at by the time this starts. SHA: ${{ gitea.event.workflow_run.head_sha || gitea.sha }} jobs: publish: # A completed `Test` run is not a passing one. if: >- (gitea.event_name == 'workflow_run' && gitea.event.workflow_run.conclusion == 'success') || (gitea.event_name == 'workflow_dispatch' && inputs.dry_run == false) runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: ref: ${{ env.SHA }} - name: Log in to the Gitea registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.domverse-berlin.eu -u "${{ secrets.REGISTRY_USER }}" --password-stdin - name: Build and push # The commit tag is pushed first, so a `latest` that exists is always a tag # that also exists under its own commit — which is what makes a rollback a # tag change rather than a rebuild. run: | docker build -t "$IMAGE:$SHA" -t "$IMAGE:latest" . docker push "$IMAGE:$SHA" docker push "$IMAGE:latest" - name: Trigger the Portainer redeploy # --fail turns an HTTP error into a non-zero exit: a redeploy that did not # happen must not read as a green deploy. run: curl -sS --fail -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}" - name: Prune dangling images # Untagged layers only. Published tags are the rollback history; `-a` would # delete exactly the images this workflow exists to keep. run: docker image prune -f dry-run: # Manual only, and the default: prove the image still builds and the registry # still accepts it without moving `latest` or touching the running stack. if: gitea.event_name == 'workflow_dispatch' && inputs.dry_run runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Log in to the Gitea registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.domverse-berlin.eu -u "${{ secrets.REGISTRY_USER }}" --password-stdin - name: Build and push a scratch tag run: | docker build -t "$IMAGE:scratch-$SHA" . docker push "$IMAGE:scratch-$SHA" - name: Prune dangling images run: docker image prune -f