name: Deploy on: push: branches: [main] workflow_dispatch: concurrency: group: ${{ github.workflow }} # Queue every run FIFO instead of coalescing pending ones; BUILD_TAG stays monotonic. queue: max cancel-in-progress: false permissions: contents: read env: IMAGE_PREFIX: ghcr.io/immich-app/yucca # Monotonic monorepo build tag (every app ships together). The staging # ResourceSetInputProvider picks the highest 0.0. from GHCR and deploys it # in-cluster: pull-based, so CI never needs cluster/Tailscale access. BUILD_TAG: 0.0.${{ github.run_number }} jobs: # A migration that sorts before one already executed makes every yucca-api / # yucca-admin-api pod crashloop at boot (Kysely refuses the whole set), and # the stale-branch case escapes PR CI: checks don't re-run when main moves # (incident: #492 merged after #510's re-date). Gate the build on HEAD^ so a # bad merge stops here instead of rolling out. migration-order: name: Migration ordering gate runs-on: ubuntu-latest timeout-minutes: 10 steps: # Delivery is main-only: a workflow_dispatch on any other ref would # build and tag images from an unmerged tree. Fail instead of skip so # a branch dispatch cannot conclude green as a silent no-op; every # later job needs this one, so the refusal cascades. - name: Refuse non-main refs if: github.ref != 'refs/heads/main' run: | echo "::error::Deploy delivers main only, not ${GITHUB_REF}" exit 1 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false fetch-depth: 2 - run: packages/yucca-api/.mise/tasks/check-migration-order HEAD^ # == Build + push every app image (the ONLY delivery action CI performs) == # Release commits build like any other. v image tags are published # by the release-images workflow on the release event, decoupled from this # workflow's queue. build: name: Build ${{ matrix.app.name }} needs: migration-order runs-on: ubuntu-latest # A hung build would hold the group's single running slot and stall every # queued run behind it; fail fast instead of the default 360m. timeout-minutes: 45 permissions: contents: read packages: write strategy: fail-fast: false matrix: app: - { name: yucca-api, dockerfile: packages/yucca-api/Dockerfile } - { name: yucca-admin-api, dockerfile: packages/yucca-admin-api/Dockerfile } - { name: yucca-metrics-worker, dockerfile: packages/yucca-metrics-worker/Dockerfile } - { name: futo-backups-bot, dockerfile: packages/futo-backups-bot/Dockerfile } - { name: web, dockerfile: packages/web/Dockerfile } - { name: michael, dockerfile: packages/michael/Dockerfile } - { name: columbo, dockerfile: packages/columbo/Dockerfile } - { name: monk, dockerfile: packages/monk/Dockerfile } steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Set up Buildx uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Log in to GHCR uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0 with: context: . file: ${{ matrix.app.dockerfile }} push: true tags: | ${{ env.IMAGE_PREFIX }}/${{ matrix.app.name }}:${{ env.BUILD_TAG }} ${{ env.IMAGE_PREFIX }}/${{ matrix.app.name }}:sha-${{ github.sha }} ${{ env.IMAGE_PREFIX }}/${{ matrix.app.name }}:main cache-from: type=gha,scope=${{ matrix.app.name }} cache-to: type=gha,mode=max,scope=${{ matrix.app.name }} # Staging deploys automatically: the flux-operator ResourceSetInputProvider in # kubernetes/apps/staging/flux-system/ detects the new ${BUILD_TAG} in GHCR and # rolls it out in-cluster. Outcome is visible as a GitHub commit status # (notification-controller Provider), not as a job here. No GHA step needed. # Production promotion is the release-please PR itself: it stamps the new # tag into both prod pins (kubernetes/clusters/prod/htz-fsn1/ # {flux-release,image-versions}.yaml, extra-files), so merging it commits # the promotion through normal review: no promote workflow, no bot push to # main. Prod Flux pulls the pins from main and the app tree + charts from # the tag; images are the v tags the release-images workflow # publishes on the release event, decoupled from this workflow's queue. # Rollback = revert the two stamped lines in a normal PR.