From b67b7a24a83fbce5501add0188b6baff05932a12 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 1 Aug 2026 11:07:22 -0400 Subject: [PATCH] Add nightly docker builds (#13223) * Add nightly docker builds * Separate environment to ungate nightly builds from manual approval --- .github/actionlint.yaml | 5 + .github/workflows/ci.yml | 14 +- .github/workflows/docker-build.yml | 160 +++++++++++++++++++++ .github/workflows/docker-nightly.yml | 21 +++ .github/workflows/docker.yml | 205 ++------------------------- .github/workflows/stale.yml | 3 +- 6 files changed, 208 insertions(+), 200 deletions(-) create mode 100644 .github/actionlint.yaml create mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/docker-nightly.yml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000000..ecd02db7f2 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,5 @@ +self-hosted-runner: + # Blacksmith-hosted runners are not known to actionlint's built-in label list. + labels: + - blacksmith-8vcpu-ubuntu-2404 + - blacksmith-8vcpu-ubuntu-2404-arm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72e0deaf3b..3aa47f58d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,16 @@ jobs: - uses: ./.github/actions/install - run: yarn lint --quiet + actionlint: + needs: changes + if: ${{ needs.changes.outputs.config == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: docker://rhysd/actionlint:1.7.12 + with: + args: -color + types: needs: changes if: ${{ needs.changes.outputs.code == 'true' }} @@ -135,7 +145,7 @@ jobs: - uses: actions/checkout@v5 - uses: ./.github/actions/install - name: Set environment to production - run: echo "NODE_ENV=production" >> $GITHUB_ENV + run: echo "NODE_ENV=production" >> "$GITHUB_ENV" - run: yarn vite:build - name: Send bundle stats to RelativeCI if: ${{ env.RELATIVE_CI_KEY != '' }} @@ -150,7 +160,7 @@ jobs: # Always runs, so it can be used as the single required status check. Jobs # above are skipped when irrelevant to the changes, which is a pass here. ci: - needs: [changes, lint, types, audit, test, test-server, bundle-size] + needs: [changes, lint, actionlint, types, audit, test, test-server, bundle-size] if: always() runs-on: ubuntu-latest steps: diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000000..7b3e124096 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,160 @@ +name: Docker build + +# Reusable multi-arch build and publish pipeline. Callers supply the ref to +# build and the docker/metadata-action tag rules to publish under. +on: + workflow_call: + inputs: + ref: + description: Git ref to build. Defaults to the ref that triggered the caller. + type: string + required: false + default: "" + tags: + description: docker/metadata-action tag rules. + type: string + required: true + flavor: + description: docker/metadata-action flavor rules. + type: string + required: false + default: "" + environment: + description: Environment used to gate publishing of the manifest. + type: string + required: false + default: dockerhub + +env: + IMAGE_NAME: outlinewiki/outline + BASE_IMAGE_NAME: outlinewiki/outline-base + +jobs: + build: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: blacksmith-8vcpu-ubuntu-2404 + - platform: linux/arm64 + runner: blacksmith-8vcpu-ubuntu-2404-arm + runs-on: ${{ matrix.runner }} + steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" + + - name: Checkout + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref }} + + - name: Setup Blacksmith Builder + uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 + + - name: Docker base meta + id: base_meta + uses: docker/metadata-action@v6 + with: + images: | + ${{ env.BASE_IMAGE_NAME }} + flavor: ${{ inputs.flavor }} + tags: ${{ inputs.tags }} + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push base image + id: base_build + uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 + with: + context: . + file: Dockerfile.base + labels: ${{ steps.base_meta.outputs.labels }} + tags: ${{ env.BASE_IMAGE_NAME }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + platforms: ${{ matrix.platform }} + pull: false + + - name: Docker meta + id: meta + uses: docker/metadata-action@v6 + with: + images: | + ${{ env.IMAGE_NAME }} + flavor: ${{ inputs.flavor }} + tags: ${{ inputs.tags }} + + - name: Build and push + id: build + uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 + with: + context: . + file: Dockerfile + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ env.IMAGE_NAME }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + platforms: ${{ matrix.platform }} + pull: false + build-args: | + BASE_IMAGE=${{ env.BASE_IMAGE_NAME }}@${{ steps.base_build.outputs.digest }} + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v5 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: blacksmith-8vcpu-ubuntu-2404 + needs: + - build + environment: ${{ inputs.environment }} + steps: + - name: Download digests + uses: actions/download-artifact@v5 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Setup Blacksmith Builder + uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.IMAGE_NAME }} + flavor: ${{ inputs.flavor }} + tags: ${{ inputs.tags }} + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + # shellcheck disable=SC2046 # word splitting is intentional here + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/docker-nightly.yml b/.github/workflows/docker-nightly.yml new file mode 100644 index 0000000000..fa54b4b378 --- /dev/null +++ b/.github/workflows/docker-nightly.yml @@ -0,0 +1,21 @@ +name: Publish nightly build + +on: + schedule: + # 03:00 UTC daily + - cron: "0 3 * * *" + workflow_dispatch: + +jobs: + publish: + uses: ./.github/workflows/docker-build.yml + secrets: inherit + with: + ref: main + environment: dockerhub-nightly + flavor: | + latest=false + tags: | + type=raw,value=nightly + type=raw,value=nightly-{{date 'YYYYMMDD'}} + type=sha,prefix=nightly-,format=short diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8e91de3e53..3c35fdfa14 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,201 +6,12 @@ on: - "v*" workflow_dispatch: -env: - IMAGE_NAME: outlinewiki/outline - BASE_IMAGE_NAME: outlinewiki/outline-base - jobs: - build-arm: - runs-on: blacksmith-8vcpu-ubuntu-2404-arm - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Blacksmith Builder - uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 - - - name: Docker base meta - id: base_meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.BASE_IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Login to Docker Hub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push base image - id: base_build - uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 - with: - context: . - file: Dockerfile.base - labels: ${{ steps.base_meta.outputs.labels }} - tags: ${{ env.BASE_IMAGE_NAME }} - outputs: type=image,push-by-digest=true,name-canonical=true,push=true - platforms: linux/arm64 - pull: false - - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push - id: build - uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 - with: - context: . - file: Dockerfile - labels: ${{ steps.meta.outputs.labels }} - tags: ${{ env.IMAGE_NAME }} - outputs: type=image,push-by-digest=true,name-canonical=true,push=true - platforms: linux/arm64 - pull: false - build-args: | - BASE_IMAGE=${{ env.BASE_IMAGE_NAME }}@${{ steps.base_build.outputs.digest }} - - - name: Export digest - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v5 - with: - name: digests-linux-arm64 - path: ${{ runner.temp }}/digests/* - if-no-files-found: error - retention-days: 1 - - build-amd: - runs-on: blacksmith-8vcpu-ubuntu-2404 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Blacksmith Builder - uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 - - - name: Docker base meta - id: base_meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.BASE_IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Login to Docker Hub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push base image - id: base_build - uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 - with: - context: . - file: Dockerfile.base - labels: ${{ steps.base_meta.outputs.labels }} - tags: ${{ env.BASE_IMAGE_NAME }} - outputs: type=image,push-by-digest=true,name-canonical=true,push=true - platforms: linux/amd64 - pull: false - - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Build and push - id: build - uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2 - with: - context: . - file: Dockerfile - labels: ${{ steps.meta.outputs.labels }} - tags: ${{ env.IMAGE_NAME }} - outputs: type=image,push-by-digest=true,name-canonical=true,push=true - platforms: linux/amd64 - pull: false - build-args: | - BASE_IMAGE=${{ env.BASE_IMAGE_NAME }}@${{ steps.base_build.outputs.digest }} - - - name: Export digest - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v5 - with: - name: digests-linux-amd64 - path: ${{ runner.temp }}/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - runs-on: blacksmith-8vcpu-ubuntu-2404 - needs: - - build-amd - - build-arm - environment: dockerhub - steps: - - name: Download digests - uses: actions/download-artifact@v5 - with: - path: ${{ runner.temp }}/digests - pattern: digests-* - merge-multiple: true - - - name: Login to Docker Hub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Setup Blacksmith Builder - uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v6 - with: - images: ${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - - - name: Create manifest list and push - working-directory: ${{ runner.temp }}/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + publish: + uses: ./.github/workflows/docker-build.yml + secrets: inherit + with: + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f62a6df37b..19633dfe14 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,6 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/stale@v10 + id: stale with: stale-pr-message: "This PR is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days" stale-issue-message: "This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 5 days" @@ -26,4 +27,4 @@ jobs: stale-pr-label: stale exempt-issue-labels: "security,pinned,A1" - name: Print outputs - run: echo ${{ join(steps.stale.outputs.*, ',') }} + run: echo "${{ steps.stale.outputs['staled-issues-prs'] }},${{ steps.stale.outputs['closed-issues-prs'] }}"