Repository navigation
ci: build and publish the dashboard image to GHCR #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
28ac494
ci: build and publish the dashboard image to GHCR
claude a4e638b
docs: document the container image and cd workflow
claude 34e852e
fix(ci): validate release tags and document the image's policy and is…
claude a96a02d
fix(i18n): replace the expired pkg.pr.new lunaria build with @lunaria…
claude 1561a62
test(i18n): cover the missing-key path format of the status report
claude 2918d13
Merge branch 'claude/fix-lunaria-core-dependency' into claude/dockerf…
claude fb397bc
Merge branch 'main' into claude/dockerfile-github-cd-workflow-s49dsg
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Build context for the dashboard image (see Dockerfile). Keep it close to .gitignore so the image | ||
| # is built from what a clean checkout contains, and never from local secrets or build output. | ||
| .git | ||
| .github | ||
| .husky/_ | ||
| .vscode | ||
| **/node_modules | ||
| **/.turbo | ||
| **/dist | ||
| **/.output | ||
| **/.nuxt | ||
| **/.vercel | ||
| **/.vitehub | ||
| **/.maizzle | ||
| **/.data | ||
| **/.code-zero | ||
| **/coverage | ||
| **/playwright-report | ||
| **/test-results | ||
| **/*.tsbuildinfo | ||
| **/*.log | ||
| .skilld | ||
| .env | ||
| .env.* | ||
| **/.env | ||
| **/.env.* | ||
| !**/.env.example | ||
| code-zero.deployment.yml | ||
| Dockerfile | ||
| .dockerignore |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| name: cd | ||
|
|
||
| # Builds the dashboard container image from the root Dockerfile and publishes it to the GitHub | ||
| # Container Registry, modelled on wolfstar-project/.github's reusable-publish-image workflow: | ||
| # each platform builds natively on its own runner and pushes by digest, then one job merges the | ||
| # digests into a single multi-arch manifest list. The resulting image runs on Docker, Railway, or | ||
| # any other container platform (see the Dockerfile for the runtime contract). | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| tags: | ||
| - 'v[0-9]+.[0-9]+.[0-9]+*' | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: {} | ||
|
|
||
| env: | ||
| REGISTRY: ghcr.io | ||
| IMAGE_NAME: ${{ github.repository }} | ||
|
|
||
| jobs: | ||
| build: | ||
| name: 🐳 Build ${{ matrix.platform }} | ||
| runs-on: ${{ matrix.runner }} | ||
| timeout-minutes: 30 | ||
| permissions: | ||
| contents: read # checkout repository | ||
| packages: write # push image layers to ghcr.io | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - platform: linux/amd64 | ||
| runner: ubuntu-24.04 | ||
| - platform: linux/arm64 | ||
| runner: ubuntu-24.04-arm | ||
| steps: | ||
| - name: Prepare | ||
| env: | ||
| PLATFORM: ${{ matrix.platform }} | ||
| run: echo "PLATFORM_PAIR=${PLATFORM//\//-}" >> "$GITHUB_ENV" | ||
|
|
||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| # A tag builds the `release` channel only when it is exactly `v<version>` of the dashboard | ||
| # package, so a stray or stale tag cannot publish an image that reports a different release. | ||
| - name: Resolve build environment | ||
| env: | ||
| REF_TYPE: ${{ github.ref_type }} | ||
| REF_NAME: ${{ github.ref_name }} | ||
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | ||
| run: | | ||
| if [[ "$REF_TYPE" == "tag" ]]; then | ||
| version="$(node --print "require('./apps/dashboard/package.json').version")" | ||
| if [[ "$REF_NAME" != "v$version" ]]; then | ||
| echo "::error::Tag $REF_NAME does not match apps/dashboard version v$version" >&2 | ||
| exit 1 | ||
| fi | ||
| build_env=release | ||
| elif [[ "$REF_NAME" == "$DEFAULT_BRANCH" ]]; then | ||
| build_env=canary | ||
| else | ||
| build_env=preview | ||
| fi | ||
| echo "BUILD_ENV=$build_env" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Extract metadata (labels) for Docker | ||
| id: meta | ||
| uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 | ||
| with: | ||
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | ||
|
|
||
| - name: Login to GitHub Container Registry | ||
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Build and push by digest | ||
| id: build | ||
| uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 | ||
| with: | ||
| context: . | ||
| file: Dockerfile | ||
| platforms: ${{ matrix.platform }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| build-args: | | ||
| CODE_ZERO_BUILD_COMMIT=${{ github.sha }} | ||
| CODE_ZERO_BUILD_BRANCH=${{ github.ref_name }} | ||
| CODE_ZERO_BUILD_ENV=${{ env.BUILD_ENV }} | ||
| outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true | ||
|
|
||
| - name: Export digest | ||
| env: | ||
| DIGEST: ${{ steps.build.outputs.digest }} | ||
| run: | | ||
| mkdir -p "$RUNNER_TEMP/digests" | ||
| touch "$RUNNER_TEMP/digests/${DIGEST#sha256:}" | ||
|
|
||
| - name: Upload digest | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: digests-${{ env.PLATFORM_PAIR }} | ||
| path: ${{ runner.temp }}/digests/* | ||
| if-no-files-found: error | ||
| retention-days: 1 | ||
|
|
||
| merge: | ||
| name: 📦 Create and push manifest list | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 10 | ||
| needs: build | ||
| permissions: | ||
| packages: write # push the manifest list to ghcr.io | ||
| steps: | ||
| - name: Download digests | ||
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||
| with: | ||
| path: ${{ runner.temp }}/digests | ||
| pattern: digests-* | ||
| merge-multiple: true | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 | ||
|
|
||
| # `latest` follows the default branch, so a Railway or Docker service pointed at | ||
| # `ghcr.io/<owner>/<repo>:latest` tracks `main`; release tags add semver tags alongside. | ||
| - name: Docker meta | ||
| id: meta | ||
| uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 | ||
| with: | ||
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
| tags: | | ||
| type=raw,value=latest,enable={{is_default_branch}} | ||
| type=ref,event=branch | ||
| type=semver,pattern={{version}} | ||
| type=semver,pattern={{major}}.{{minor}} | ||
| type=sha,format=long,prefix= | ||
|
|
||
| - name: Login to GitHub Container Registry | ||
| uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Create manifest list and push | ||
| working-directory: ${{ runner.temp }}/digests | ||
| env: | ||
| IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
| run: | | ||
| # shellcheck disable=SC2046 | ||
| docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ | ||
| $(printf "${IMAGE}@sha256:%s " *) | ||
|
|
||
| - name: Inspect image | ||
| env: | ||
| IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
| VERSION: ${{ steps.meta.outputs.version }} | ||
| run: docker buildx imagetools inspect "${IMAGE}:${VERSION}" | ||
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # syntax=docker/dockerfile:1 | ||
|
|
||
| # Container image for the single deployable app, `apps/dashboard`. | ||
| # | ||
| # The build stage installs the workspace with the pinned aube version and builds the dashboard | ||
| # (and the workspace packages it depends on) through Turborepo, with the self-hosted `node` | ||
| # ViteHub preset. That emits Nitro's self-contained `.output/` bundle, which is the only thing | ||
| # the runtime stage copies: no workspace sources, no dev dependencies, no package manager. | ||
| # | ||
| # The image listens on `$PORT` (default 3000), so it runs unchanged on Docker, Railway, and any | ||
| # other container platform that injects the port it routes to. | ||
|
|
||
| ARG NODE_VERSION=24.19.0 | ||
|
|
||
| FROM node:${NODE_VERSION}-bookworm-slim AS build | ||
|
|
||
| ARG AUBE_VERSION=1.41.0 | ||
|
|
||
| # Build metadata published under `runtimeConfig.public.buildInfo` (see packages/build-env). The | ||
| # checkout's `.git` is not part of the build context, so CI passes these in explicitly. | ||
| ARG CODE_ZERO_BUILD_COMMIT="" | ||
| ARG CODE_ZERO_BUILD_BRANCH="" | ||
| ARG CODE_ZERO_BUILD_URL="" | ||
| ARG CODE_ZERO_BUILD_PRODUCTION_URL="" | ||
| ARG CODE_ZERO_BUILD_ENV="" | ||
|
|
||
| # `CI=true` skips the Husky install in the `prepare` script and keeps tools non-interactive. | ||
| ENV CI=true \ | ||
| HUSKY=0 \ | ||
| NITRO_PRESET=node-server \ | ||
| CODE_ZERO_BUILD_COMMIT=${CODE_ZERO_BUILD_COMMIT} \ | ||
| CODE_ZERO_BUILD_BRANCH=${CODE_ZERO_BUILD_BRANCH} \ | ||
| CODE_ZERO_BUILD_URL=${CODE_ZERO_BUILD_URL} \ | ||
| CODE_ZERO_BUILD_PRODUCTION_URL=${CODE_ZERO_BUILD_PRODUCTION_URL} \ | ||
| CODE_ZERO_BUILD_ENV=${CODE_ZERO_BUILD_ENV} | ||
|
|
||
| RUN npm install --global --ignore-scripts=false "@endevco/aube@${AUBE_VERSION}" | ||
|
|
||
| WORKDIR /workspace | ||
|
|
||
| COPY . . | ||
|
|
||
| RUN aube ci \ | ||
| && aube exec turbo run build --filter=@code-zero/dashboard | ||
|
|
||
| FROM node:${NODE_VERSION}-bookworm-slim AS runtime | ||
|
|
||
| # The runner boundary clones and inspects target repositories, so the image ships git and the CA | ||
| # bundle it needs for HTTPS remotes. | ||
| RUN apt-get update \ | ||
| && apt-get install --yes --no-install-recommends ca-certificates git tini \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
RedStar071 marked this conversation as resolved.
|
||
|
|
||
| ENV NODE_ENV=production \ | ||
| HOST=0.0.0.0 \ | ||
| PORT=3000 | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| COPY --from=build --chown=node:node /workspace/apps/dashboard/.output ./.output | ||
|
|
||
| # `fs-lite` KV keeps task history under `.data/kv` relative to the working directory. Mount a | ||
| # volume at /app/.data to keep it across restarts. No `VOLUME` instruction on purpose: Railway | ||
| # rejects images that declare one and attaches its own volumes instead. | ||
| RUN mkdir -p /app/.data && chown node:node /app/.data | ||
|
|
||
| USER node | ||
|
|
||
| EXPOSE 3000 | ||
|
|
||
| ENTRYPOINT ["/usr/bin/tini", "--"] | ||
| CMD ["node", ".output/server/index.mjs"] | ||
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.