name: Publish container image on: release: types: [published] permissions: contents: read env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} RELEASE_TAG: ${{ github.event.release.tag_name }} jobs: test: name: Test release runs-on: ubuntu-latest services: postgres: image: postgres:17-alpine env: POSTGRES_DB: todo_test POSTGRES_USER: todo_test POSTGRES_PASSWORD: test-password ports: - 5432:5432 options: >- --health-cmd "pg_isready -U todo_test -d todo_test" --health-interval 10s --health-timeout 5s --health-retries 5 defaults: run: working-directory: app env: TEST_DATABASE_URL: postgres://todo_test:test-password@127.0.0.1:5432/todo_test?sslmode=disable steps: - name: Validate release tag working-directory: . run: | if [[ ! "$RELEASE_TAG" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$ ]]; then printf 'Release tag must use semantic versioning with a v prefix: %s\n' "$RELEASE_TAG" >&2 exit 1 fi - name: Check out release uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: app/go.mod cache-dependency-path: app/go.sum - name: Run tests run: go test ./... - name: Run vet run: go vet ./... publish: name: Publish image needs: test runs-on: ubuntu-latest concurrency: group: publish-image-${{ github.event.release.tag_name }} cancel-in-progress: false permissions: contents: read packages: write attestations: write id-token: write steps: - name: Check out release uses: actions/checkout@v6 with: ref: ${{ env.RELEASE_TAG }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Generate image metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=semver,pattern={{raw}},value=${{ env.RELEASE_TAG }} type=semver,pattern={{version}},value=${{ env.RELEASE_TAG }} type=semver,pattern={{major}}.{{minor}},value=${{ env.RELEASE_TAG }} type=semver,pattern={{major}},value=${{ env.RELEASE_TAG }},enable=${{ !startsWith(env.RELEASE_TAG, 'v0.') }} type=raw,value=latest,enable=${{ !github.event.release.prerelease }} labels: | org.opencontainers.image.description=Server-rendered Go todo application deployed to Hetzner Cloud annotations: | org.opencontainers.image.description=Server-rendered Go todo application deployed to Hetzner Cloud env: DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index - name: Build and push image id: push uses: docker/build-push-action@v6 with: context: app file: app/Dockerfile platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta.outputs.annotations }} - name: Attest image provenance uses: actions/attest@v4 with: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: true