Merge pull request #485 from databuddy-analytics/staging #135
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Docker Images | |
| on: | |
| release: | |
| types: [published] | |
| push: | |
| branches: [main] | |
| paths: | |
| - "*.Dockerfile" | |
| - ".dockerignore" | |
| - "apps/**" | |
| - "packages/**" | |
| - "bun.lock" | |
| - "package.json" | |
| - "turbo.json" | |
| - ".github/workflows/docker-publish.yml" | |
| workflow_dispatch: | |
| concurrency: | |
| group: docker-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name != 'release' }} | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_PREFIX: ghcr.io/databuddy-analytics/databuddy | |
| jobs: | |
| detect: | |
| name: Detect affected services | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| timeout-minutes: 5 | |
| outputs: | |
| services: ${{ steps.detect.outputs.services }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: "1.3.14" | |
| - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 | |
| with: | |
| path: ~/.bun/install/cache | |
| key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} | |
| restore-keys: ${{ runner.os }}-bun- | |
| - run: bun install --frozen-lockfile --ignore-scripts | |
| - id: detect | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BEFORE_SHA: ${{ github.event.before }} | |
| run: | | |
| ALL='["api","basket","dashboard","insights","links","uptime"]' | |
| if [[ "$EVENT_NAME" != "push" ]]; then | |
| echo "services=$ALL" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [[ -z "$BEFORE_SHA" || "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]]; then | |
| echo "services=$ALL" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| export TURBO_SCM_BASE="$BEFORE_SHA" | |
| export TURBO_SCM_HEAD="HEAD" | |
| affected=() | |
| for svc in api basket dashboard insights links uptime; do | |
| count=$(bunx turbo ls --affected --filter="@databuddy/$svc" --output=json | jq -r '.packages.count') | |
| if [[ "$count" != "0" ]]; then | |
| affected+=("\"$svc\"") | |
| fi | |
| done | |
| if [[ ${#affected[@]} -eq 0 ]]; then | |
| echo "services=[]" >> "$GITHUB_OUTPUT" | |
| else | |
| IFS=, | |
| echo "services=[${affected[*]}]" >> "$GITHUB_OUTPUT" | |
| fi | |
| build: | |
| name: Build ${{ matrix.service }} (${{ matrix.platform.arch }}) | |
| needs: detect | |
| if: needs.detect.outputs.services != '[]' | |
| runs-on: ${{ matrix.platform.runner }} | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| security-events: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| service: ${{ fromJson(needs.detect.outputs.services) }} | |
| platform: | |
| - arch: amd64 | |
| os: linux/amd64 | |
| runner: blacksmith-4vcpu-ubuntu-2404 | |
| - arch: arm64 | |
| os: linux/arm64 | |
| runner: blacksmith-4vcpu-ubuntu-2404-arm | |
| include: | |
| - service: api | |
| description: "Databuddy API service - analytics backend" | |
| - service: basket | |
| description: "Databuddy Basket service - event ingestion" | |
| - service: dashboard | |
| description: "Databuddy Dashboard service - web analytics UI" | |
| - service: insights | |
| description: "Databuddy Insights service - queued insight generation" | |
| - service: links | |
| description: "Databuddy Links service - URL shortening and tracking" | |
| - service: uptime | |
| description: "Databuddy Uptime service - availability monitoring" | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Mount Docker build cache | |
| uses: useblacksmith/stickydisk@41873b1513bb679f9c115504cbd13d3660432504 # v1 | |
| with: | |
| key: ${{ github.repository }}-${{ matrix.service }}-${{ matrix.platform.arch }} | |
| path: /tmp/docker-build-cache | |
| - name: Set up Docker Builder | |
| uses: useblacksmith/setup-docker-builder@ac083cc84672d01c60d5e8561d0a939b697de542 # v1 | |
| - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Determine version tag | |
| id: version | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| TAG_NAME: ${{ github.event.release.tag_name }} | |
| run: | | |
| if [[ "$EVENT_NAME" == "release" ]]; then | |
| echo "tag=$TAG_NAME" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=edge" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Capture build date | |
| id: builddate | |
| run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" | |
| - name: Extract metadata (labels) | |
| id: meta | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 | |
| with: | |
| images: ${{ env.IMAGE_PREFIX }}-${{ matrix.service }} | |
| labels: | | |
| org.opencontainers.image.title=databuddy-${{ matrix.service }} | |
| org.opencontainers.image.description=${{ matrix.description }} | |
| org.opencontainers.image.vendor=Databuddy Analytics | |
| org.opencontainers.image.licenses=AGPL-3.0 | |
| - name: Build and push | |
| id: build | |
| uses: useblacksmith/build-push-action@cbd1f60d194a98cb3be5523b15134501eaf0fbf3 # v2 | |
| with: | |
| context: . | |
| file: ${{ matrix.service }}.Dockerfile | |
| push: true | |
| platforms: ${{ matrix.platform.os }} | |
| provenance: mode=max | |
| sbom: true | |
| labels: ${{ steps.meta.outputs.labels }} | |
| tags: ${{ env.IMAGE_PREFIX }}-${{ matrix.service }}:${{ steps.version.outputs.tag }}-${{ matrix.platform.arch }} | |
| build-args: | | |
| VERSION=${{ steps.version.outputs.tag }} | |
| BUILD_DATE=${{ steps.builddate.outputs.timestamp }} | |
| GIT_SHA=${{ github.sha }} | |
| cache-from: type=registry,ref=${{ env.IMAGE_PREFIX }}-${{ matrix.service }}:cache-${{ matrix.platform.arch }} | |
| cache-to: type=registry,ref=${{ env.IMAGE_PREFIX }}-${{ matrix.service }}:cache-${{ matrix.platform.arch }},mode=max | |
| - name: Scan image for vulnerabilities | |
| if: always() && steps.build.outcome != 'cancelled' | |
| env: | |
| TRIVY_PLATFORM: ${{ matrix.platform.os }} | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0 | |
| with: | |
| image-ref: ${{ env.IMAGE_PREFIX }}-${{ matrix.service }}:${{ steps.version.outputs.tag }}-${{ matrix.platform.arch }} | |
| format: sarif | |
| output: trivy-results.sarif | |
| severity: CRITICAL,HIGH | |
| ignore-unfixed: true | |
| - name: Upload scan results to GitHub Security | |
| uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 | |
| if: always() && hashFiles('trivy-results.sarif') != '' | |
| with: | |
| sarif_file: trivy-results.sarif | |
| - name: Install cosign | |
| if: always() && steps.build.outcome != 'cancelled' | |
| uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4 | |
| - name: Sign image | |
| if: always() && steps.build.outcome != 'cancelled' | |
| env: | |
| IMAGE_PREFIX: ${{ env.IMAGE_PREFIX }} | |
| SERVICE: ${{ matrix.service }} | |
| TAG: ${{ steps.version.outputs.tag }} | |
| ARCH: ${{ matrix.platform.arch }} | |
| run: cosign sign --yes "${IMAGE_PREFIX}-${SERVICE}:${TAG}-${ARCH}" | |
| manifest: | |
| name: Publish ${{ matrix.service }} manifest | |
| needs: [detect, build] | |
| if: needs.detect.outputs.services != '[]' | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| strategy: | |
| matrix: | |
| service: ${{ fromJson(needs.detect.outputs.services) }} | |
| steps: | |
| - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Determine version tag | |
| id: version | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| TAG_NAME: ${{ github.event.release.tag_name }} | |
| run: | | |
| if [[ "$EVENT_NAME" == "release" ]]; then | |
| echo "tag=$TAG_NAME" >> "$GITHUB_OUTPUT" | |
| echo "is_release=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=edge" >> "$GITHUB_OUTPUT" | |
| echo "is_release=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Create multi-arch manifest | |
| env: | |
| IMAGE_PREFIX: ${{ env.IMAGE_PREFIX }} | |
| SERVICE: ${{ matrix.service }} | |
| TAG: ${{ steps.version.outputs.tag }} | |
| IS_RELEASE: ${{ steps.version.outputs.is_release }} | |
| COMMIT_SHA: ${{ github.sha }} | |
| run: | | |
| IMAGE="${IMAGE_PREFIX}-${SERVICE}" | |
| SHA_SHORT="${COMMIT_SHA:0:7}" | |
| docker buildx imagetools create \ | |
| --tag "$IMAGE:$TAG" \ | |
| --tag "$IMAGE:sha-$SHA_SHORT" \ | |
| "$IMAGE:$TAG-amd64" \ | |
| "$IMAGE:$TAG-arm64" | |
| if [[ "$IS_RELEASE" == "true" ]]; then | |
| docker buildx imagetools create \ | |
| --tag "$IMAGE:latest" \ | |
| "$IMAGE:$TAG-amd64" \ | |
| "$IMAGE:$TAG-arm64" | |
| fi | |
| - name: Verify manifest | |
| env: | |
| IMAGE_PREFIX: ${{ env.IMAGE_PREFIX }} | |
| SERVICE: ${{ matrix.service }} | |
| TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| IMAGE="${IMAGE_PREFIX}-${SERVICE}" | |
| echo "Inspecting $IMAGE:$TAG" | |
| docker buildx imagetools inspect "$IMAGE:$TAG" | |
| - name: Install cosign | |
| uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4 | |
| - name: Sign manifest | |
| env: | |
| IMAGE_PREFIX: ${{ env.IMAGE_PREFIX }} | |
| SERVICE: ${{ matrix.service }} | |
| TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| IMAGE="${IMAGE_PREFIX}-${SERVICE}" | |
| cosign sign --yes "$IMAGE:$TAG" | |
| result: | |
| name: Docker Publish Result | |
| if: always() | |
| needs: [detect, build, manifest] | |
| runs-on: blacksmith-2vcpu-ubuntu-2404 | |
| timeout-minutes: 1 | |
| steps: | |
| - run: | | |
| if [[ "${{ needs.detect.result }}" == "failure" ]]; then | |
| echo "Detection failed" | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.build.result }}" == "failure" ]]; then | |
| echo "Build failed" | |
| exit 1 | |
| fi | |
| if [[ "${{ needs.manifest.result }}" == "failure" ]]; then | |
| echo "Manifest publish failed" | |
| exit 1 | |
| fi | |
| echo "All jobs passed (or were skipped because no services were affected)" |