name: Build and Push Docker Images on: release: types: [published] workflow_dispatch: inputs: image_tag: description: "Tag to publish for a manual test run. Defaults to test-." required: false type: string push_latest: description: "Also update :latest. Leave false for test runs." required: false default: false type: boolean permissions: contents: read packages: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: REGISTRY_GHCR: ghcr.io jobs: prepare: runs-on: ubuntu-latest outputs: short_sha: ${{ steps.tags.outputs.short_sha }} version: ${{ steps.tags.outputs.version }} push_latest: ${{ steps.tags.outputs.push_latest }} steps: - name: Compute tags id: tags run: | SHORT_SHA="${GITHUB_SHA::8}" if [ "${{ github.event_name }}" = "release" ]; then VERSION="${{ github.event.release.tag_name }}" VERSION="${VERSION#dograh-}" VERSION="${VERSION#v}" PUSH_LATEST="true" else VERSION="${{ inputs.image_tag }}" if [ -z "$VERSION" ]; then VERSION="test-${SHORT_SHA}" fi PUSH_LATEST="${{ inputs.push_latest }}" fi echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "push_latest=${PUSH_LATEST}" >> "$GITHUB_OUTPUT" build: needs: prepare strategy: fail-fast: false matrix: service: - name: dograh-api dockerfile: api/Dockerfile context: . - name: dograh-ui dockerfile: ui/Dockerfile context: . platform: - name: linux/amd64 runner: ubuntu-24.04 short: amd64 - name: linux/arm64 runner: ubuntu-24.04-arm short: arm64 runs-on: ${{ matrix.platform.runner }} steps: - name: Free Disk Space uses: jlumbroso/free-disk-space@main with: tool-cache: false android: false dotnet: false haskell: true large-packages: true docker-images: true swap-storage: true - name: Checkout repository uses: actions/checkout@v4 with: submodules: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Log in to DockerHub uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to GHCR uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY_GHCR }} username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Build and push by digest id: build uses: docker/build-push-action@v7 with: context: ${{ matrix.service.context }} file: ${{ matrix.service.dockerfile }} platforms: ${{ matrix.platform.name }} outputs: 'type=image,"name=${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.service.name }},${{ env.REGISTRY_GHCR }}/${{ secrets.GHCR_USERNAME }}/${{ matrix.service.name }}",push-by-digest=true,name-canonical=true,push=true' cache-from: type=gha,scope=${{ matrix.service.name }}-${{ matrix.platform.short }} cache-to: type=gha,mode=max,scope=${{ matrix.service.name }}-${{ matrix.platform.short }} - name: Export digest run: | mkdir -p "/tmp/digests/${{ matrix.service.name }}" echo "${{ steps.build.outputs.digest }}" | sed 's/^sha256://' > "/tmp/digests/${{ matrix.service.name }}/${{ matrix.platform.short }}" - name: Upload digest uses: actions/upload-artifact@v4 with: name: digest-${{ matrix.service.name }}-${{ matrix.platform.short }} path: /tmp/digests/${{ matrix.service.name }}/${{ matrix.platform.short }} retention-days: 1 merge: needs: - prepare - build runs-on: ubuntu-latest steps: - name: Download API digests uses: actions/download-artifact@v4 with: pattern: digest-dograh-api-* merge-multiple: true path: /tmp/digests/dograh-api - name: Download UI digests uses: actions/download-artifact@v4 with: pattern: digest-dograh-ui-* merge-multiple: true path: /tmp/digests/dograh-ui - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Log in to DockerHub uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Log in to GHCR uses: docker/login-action@v4 with: registry: ${{ env.REGISTRY_GHCR }} username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Create manifest lists env: DH_NAMESPACE: ${{ secrets.DOCKERHUB_USERNAME }} GH_NAMESPACE: ${{ env.REGISTRY_GHCR }}/${{ secrets.GHCR_USERNAME }} VERSION: ${{ needs.prepare.outputs.version }} SHORT_SHA: ${{ needs.prepare.outputs.short_sha }} PUSH_LATEST: ${{ needs.prepare.outputs.push_latest }} run: | inspect_digests() { service="$1" digest_dir="/tmp/digests/$service" dh_image="$DH_NAMESPACE/$service" gh_image="$GH_NAMESPACE/$service" for digest_file in "$digest_dir"/*; do digest="$(cat "$digest_file")" docker buildx imagetools inspect "$dh_image@sha256:$digest" >/dev/null docker buildx imagetools inspect "$gh_image@sha256:$digest" >/dev/null done } create_manifests() { service="$1" digest_dir="/tmp/digests/$service" dh_image="$DH_NAMESPACE/$service" gh_image="$GH_NAMESPACE/$service" dh_refs=$(printf "${dh_image}@sha256:%s " $(cat "$digest_dir"/*)) gh_refs=$(printf "${gh_image}@sha256:%s " $(cat "$digest_dir"/*)) dh_tags=(-t "$dh_image:$VERSION" -t "$dh_image:$SHORT_SHA") gh_tags=(-t "$gh_image:$VERSION" -t "$gh_image:$SHORT_SHA") if [ "$PUSH_LATEST" = "true" ]; then dh_tags+=(-t "$dh_image:latest") gh_tags+=(-t "$gh_image:latest") fi docker buildx imagetools create "${dh_tags[@]}" $dh_refs docker buildx imagetools create "${gh_tags[@]}" $gh_refs } inspect_digests dograh-api inspect_digests dograh-ui create_manifests dograh-api create_manifests dograh-ui notify: needs: - prepare - merge if: always() runs-on: ubuntu-latest steps: - name: Slack success if: needs.merge.result == 'success' uses: slackapi/slack-github-action@v1.26.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} with: payload: | { "text": "✅ Docker images built for ${{ needs.prepare.outputs.version }} on ${{ github.ref_name }} by ${{ github.actor }}" } - name: Slack failure if: needs.merge.result != 'success' uses: slackapi/slack-github-action@v1.26.0 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} with: payload: | { "text": "❌ Docker build failed for ${{ needs.prepare.outputs.version }} on ${{ github.ref_name }} by ${{ github.actor }} - <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Logs>" }