name: Web Build and Push Docker Images # Rolling `dev` / sha images for the FTStore Admin server (web/). Versioned # release images are built by web-release.yml, so there is deliberately no tag # trigger here: GitHub does not document whether a `paths` filter is evaluated # for tag pushes, and a release build must not hinge on undocumented behaviour. on: push: branches: [main] paths: - "web/**" # The app image compiles zsign from here, so a change to it has to # rebuild the image even though nothing under web/ moved. - "manager/Zsign/**" - ".dockerignore" - ".github/workflows/web-docker-build.yml" pull_request: branches: [main] paths: - "web/**" # The app image compiles zsign from here, so a change to it has to # rebuild the image even though nothing under web/ moved. - "manager/Zsign/**" - ".dockerignore" - ".github/workflows/web-docker-build.yml" env: REGISTRY: ghcr.io IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}/ftstore-admin jobs: build-app: name: Build App Image runs-on: [self-hosted, linux, x64] permissions: contents: read packages: write steps: - uses: actions/checkout@v5 with: # manager/Zsign is a submodule, and the zsign stage compiles from it. # Without this the directory checks out empty and the build dies on # `make -C Zsign/build/linux`. The worker job needs no submodules. submodules: true - uses: docker/setup-buildx-action@v3 # A plain `docker login` with retries rather than `docker/login-action`, # which has none. The two image jobs run on the same self-hosted runner and # reach for ghcr.io in the same second, and one of the pair came back with # a TLS handshake timeout: a whole build lost on a step that costs nothing # to try again. Five attempts, backing off, then the failure is real. - name: Log in to GHCR if: github.event_name != 'pull_request' env: GHCR_USER: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | for attempt in 1 2 3 4 5; do if printf '%s' "$GHCR_TOKEN" \ | docker login ghcr.io --username "$GHCR_USER" --password-stdin then exit 0 fi echo "ghcr.io login attempt $attempt failed, waiting $((attempt * 5))s" sleep $((attempt * 5)) done echo "ghcr.io refused five logins in a row" >&2 exit 1 - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_PREFIX }}-app tags: | type=sha type=ref,event=branch type=raw,value=dev,enable={{is_default_branch}} - name: Build and push app image uses: docker/build-push-action@v6 with: # Repo root, not ./web: the image compiles zsign from manager/Zsign. # The root .dockerignore is what keeps that context small. context: . file: ./web/Dockerfile push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} no-cache: true # The runner is self-hosted, so without this the credentials stay in # ~/.docker/config.json after the job is over. `docker/login-action` did # this in a post step; logging in by hand means logging out by hand. - name: Log out of GHCR if: always() run: docker logout ghcr.io build-worker: name: Build Worker Image runs-on: [self-hosted, linux, x64] permissions: contents: read packages: write steps: - uses: actions/checkout@v5 - uses: docker/setup-buildx-action@v3 # A plain `docker login` with retries rather than `docker/login-action`, # which has none. The two image jobs run on the same self-hosted runner and # reach for ghcr.io in the same second, and one of the pair came back with # a TLS handshake timeout: a whole build lost on a step that costs nothing # to try again. Five attempts, backing off, then the failure is real. - name: Log in to GHCR if: github.event_name != 'pull_request' env: GHCR_USER: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | for attempt in 1 2 3 4 5; do if printf '%s' "$GHCR_TOKEN" \ | docker login ghcr.io --username "$GHCR_USER" --password-stdin then exit 0 fi echo "ghcr.io login attempt $attempt failed, waiting $((attempt * 5))s" sleep $((attempt * 5)) done echo "ghcr.io refused five logins in a row" >&2 exit 1 - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.IMAGE_PREFIX }}-worker tags: | type=sha type=ref,event=branch type=raw,value=dev,enable={{is_default_branch}} - name: Build and push worker image uses: docker/build-push-action@v6 with: context: ./web file: ./web/Dockerfile.worker push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} no-cache: true # The runner is self-hosted, so without this the credentials stay in # ~/.docker/config.json after the job is over. `docker/login-action` did # this in a post step; logging in by hand means logging out by hand. - name: Log out of GHCR if: always() run: docker logout ghcr.io