refactor: build dogfood image as base + mise oci layers (#25448)

Splits the dogfood image into two artifacts:

- `ghcr.io/coder/oss-dogfood-base:<distro>-<base-sha>`: Ubuntu base with
apt packages, chrome, rustup, brew, gh, and the mise binary. The
base-sha is a cache key over `Dockerfile.base` and `files/`, so commits
that don't touch those inputs reuse the previous build.
- `codercom/oss-dogfood:<final-sha>-<distro>` and rolling tags
(`:22.04`, `:26.04`, `:latest`, `:<branch>`): produced by `mise oci
build` on top of the base, with one content-addressed OCI layer per mise
tool. The rolling tag scheme is unchanged, so the workspace template
doesn't need updating.

Single-tool version bumps now invalidate only that tool's OCI layer, so
workspaces re-pull just what changed instead of the entire 5-6 GB image
on every recreate.

Also:

- Drops the build-time `pnpm dlx playwright@1.47.0 install --with-deps
chromium` step (~400 MB) and the equivalent `playwright-driver.browsers`
install from `flake.nix`. `@playwright/mcp` (used by the claude-code and
codex MCP servers in `dogfood/coder/main.tf`) does NOT auto-install
browsers, so the existing `install-deps` `coder_script` now runs two
installs on workspace start: `pnpm exec playwright install chromium` for
the site's pinned `@playwright/test`, and `npx
--package=@playwright/mcp@latest playwright-core install --no-shell
chromium` so the MCP servers find their matching browser revision.
Browser revisions coexist under
`~/.cache/ms-playwright/chromium-<rev>/`, which lives on the home volume
so both downloads happen once per workspace recreate and persist across
restarts. Net effect: same MCP behavior as before, +~1-2 min on first
workspace start. Nix devshell users running site e2e tests locally now
need `pnpm exec playwright install` once (instead of getting browsers
via nixpkgs).
- Bumps the pinned mise binary to v2026.5.12 (matching main after
#25521) and adds top-level `min_version = "2026.5.12"` to `mise.toml` so
every consumer (devs, CI, the embedded mise inside the dogfood image,
mise oci builds) fails fast on an older mise.
- Adds bison, flex, libicu-dev, libreadline-dev, uuid-dev, and
zlib1g-dev to both Ubuntu base images for source-build use cases (e.g.,
building Postgres from source).
- Replaces skopeo with crane as the registry client `mise oci push`
shells out to: crane is added to `mise.toml`, the workflow drops its
`apt-get install skopeo` and forces `--tool crane`, and the local
wrapper image stops bundling skopeo. One source of truth for tool
versions, no apt drift, smaller wrapper image, and workspace users get a
registry client on PATH for free via mise oci's tool layers.
- Removes `nix.hash`/`mise.hash` and their Makefile rules. The registry
digest already captures every effective change since CI rebuilds when
any baked-in input moves; the per-file `filesha1()` entries in
`pull_triggers` are redundant.

Supersedes #25400 (the `mise.hash` pull trigger landed there in
`2b612abe7b`; this PR removes it as part of the broader simplification).

> [!NOTE]
> `mise oci build` is experimental and requires `MISE_EXPERIMENTAL=1`
(set at job level in the workflow). The local-only
`scripts/dogfood/mise-oci-wrapper.sh` builds a tiny
`coderdev/mise-oci-wrapper:<version>` Debian image with curl-installed
mise on first invocation (cached by version tag thereafter); we don't
reuse `jdxcode/mise:latest` because that tag lags upstream GitHub
releases by days and would defeat the `min_version` enforcement above.

> [!NOTE]
> `compute-base-sha.sh` and `compute-final-sha.sh` are cache keys, not
strict content addresses: the base Dockerfile still pulls dynamic
resources at build time (gh/buildx `releases/latest`, chrome
`stable_current_amd64.deb`, apt mirror state). Two runs with identical
checked-in files can produce slightly different bytes, which is
acceptable here because the cache-hit savings on irrelevant commits
outweigh that drift.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thomas Kosiewski
2026-05-26 14:52:21 +02:00
committed by GitHub
parent d8dc782da5
commit 51836e681e
18 changed files with 606 additions and 258 deletions
+7 -7
View File
@@ -1,9 +1,9 @@
# This file controls what docker/BuildKit may send to the daemon when # This file controls what docker/BuildKit may send to the daemon when
# the build context is the repository root. Today only the dogfood # the build context is the repository root. Today only the dogfood
# images at dogfood/coder/ubuntu-{22,26}.04/Dockerfile use the repo # base images at dogfood/coder/ubuntu-{22,26}.04/Dockerfile.base use the
# root as context; other docker builds in this repo (scripts/Dockerfile, # repo root as context; other docker builds in this repo
# scripts/Dockerfile.base, scripts/ironbank/Dockerfile) cd into a # (scripts/Dockerfile, scripts/Dockerfile.base, scripts/ironbank/Dockerfile)
# temporary directory and have their own contexts. # cd into a temporary directory and have their own contexts.
# #
# We use an allowlist so the context stays small and predictable, and # We use an allowlist so the context stays small and predictable, and
# new top-level files added to the repo do not silently inflate every # new top-level files added to the repo do not silently inflate every
@@ -14,15 +14,15 @@
# file under a directory requires re-including the directory itself. # file under a directory requires re-including the directory itself.
** **
# Re-allow paths the dogfood Dockerfiles consume. # Re-allow paths the dogfood Dockerfile.base files consume.
!mise.toml
!mise.lock
!dogfood !dogfood
!dogfood/coder !dogfood/coder
!dogfood/coder/ubuntu-22.04 !dogfood/coder/ubuntu-22.04
!dogfood/coder/ubuntu-22.04/Dockerfile.base
!dogfood/coder/ubuntu-22.04/configure-chrome-flags.sh !dogfood/coder/ubuntu-22.04/configure-chrome-flags.sh
!dogfood/coder/ubuntu-22.04/files !dogfood/coder/ubuntu-22.04/files
!dogfood/coder/ubuntu-22.04/files/** !dogfood/coder/ubuntu-22.04/files/**
!dogfood/coder/ubuntu-26.04 !dogfood/coder/ubuntu-26.04
!dogfood/coder/ubuntu-26.04/Dockerfile.base
!dogfood/coder/ubuntu-26.04/files !dogfood/coder/ubuntu-26.04/files
!dogfood/coder/ubuntu-26.04/files/** !dogfood/coder/ubuntu-26.04/files/**
+188 -97
View File
@@ -8,21 +8,24 @@ on:
# #
# Effects vary by event: # Effects vary by event:
# #
# PRs: `build_image` builds the image variants but never pushes # PRs: `build_image` builds the base and runs `mise oci build`,
# (each `depot/build-push-action` step's `push:` and the # loads the result into the local Docker daemon, and runs
# `Push Nix image` step are gated on `github.ref == # `make gen`, `fmt`, `lint`, and a Linux build inside the image
# 'refs/heads/main'`). `test_image` rebuilds the Ubuntu images # to validate the baked-in tooling. Only the base image is pushed
# from Depot cache with `load: true` and runs `make gen`, `fmt`, # (to ghcr.io so the mise oci step can pull --from a real
# `lint`, and a Linux build inside each image to validate that # registry); the Docker Hub push is gated on
# the baked-in tooling works. `deploy_template` runs # `github.ref == 'refs/heads/main'`. Fork PRs skip the entire
# `terraform init` + `validate` only; the apply step and # base+mise-oci pipeline since GITHUB_TOKEN is read-only for
# SHA/title gathering are gated on main. # packages; the nix matrix entry still runs.
# `deploy_template` runs `terraform init` + `validate` only; the
# apply step and SHA/title gathering are gated on main.
# #
# Pushes to main: `build_image` retags rolling tags on # Pushes to main: `build_image` retags rolling tags on
# `codercom/oss-dogfood` (`:latest`, `:22.04`, `:26.04`), # `codercom/oss-dogfood` (`:latest`, `:22.04`, `:26.04`),
# `codercom/oss-dogfood-vscode-coder` (`:latest`), and # `codercom/oss-dogfood-vscode-coder` (`:latest`), and
# `codercom/oss-dogfood-nix` (`:latest`), plus a per-branch tag on # `codercom/oss-dogfood-nix` (`:latest`), plus a per-branch tag on
# each. `test_image` validates tooling as above. # each. The image-tooling validation runs as above before any
# push, so a broken image never reaches Docker Hub.
# `deploy_template` runs `terraform apply` and creates new # `deploy_template` runs `terraform apply` and creates new
# `coderd_template` versions on dev.coder.com whose `name` is the # `coderd_template` versions on dev.coder.com whose `name` is the
# commit short SHA. Content is unchanged when neither `dogfood/**` # commit short SHA. Content is unchanged when neither `dogfood/**`
@@ -37,6 +40,8 @@ on:
- "flake.nix" - "flake.nix"
- "mise.toml" - "mise.toml"
- "mise.lock" - "mise.lock"
- "scripts/dogfood/**"
- "scripts/dogfood_test_image.sh"
pull_request: pull_request:
paths: paths:
- "dogfood/**" - "dogfood/**"
@@ -45,6 +50,8 @@ on:
- "flake.nix" - "flake.nix"
- "mise.toml" - "mise.toml"
- "mise.lock" - "mise.lock"
- "scripts/dogfood/**"
- "scripts/dogfood_test_image.sh"
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@@ -58,7 +65,16 @@ jobs:
image-version: ["22.04", "26.04", "nix"] image-version: ["22.04", "26.04", "nix"]
if: github.actor != 'dependabot[bot]' # Skip Dependabot PRs if: github.actor != 'dependabot[bot]' # Skip Dependabot PRs
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-4' || 'ubuntu-latest' }} runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
permissions:
contents: read
packages: write # push the dogfood base image to ghcr.io/coder/oss-dogfood-base
env:
# MISE_EXPERIMENTAL opts into the experimental `oci` subcommand.
# Trust is set via a config file (see the Install mise step
# below) rather than MISE_TRUSTED_CONFIG_PATHS so the workspace
# template can keep parity with the same file-based approach.
MISE_EXPERIMENTAL: "1"
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
@@ -119,6 +135,58 @@ jobs:
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
if: matrix.image-version != 'nix' if: matrix.image-version != 'nix'
- name: Install mise
if: matrix.image-version != 'nix'
# MISE_VERSION + MISE_SHA256 match dogfood/coder/ubuntu-*/Dockerfile.base
# so the mise binary baking the image is the same one a workspace
# ships with. `min_version` in mise.toml catches downgrades.
# Write trust config to ~/.config/mise/conf.d/ instead of using
# MISE_TRUSTED_CONFIG_PATHS so the same file-based approach
# works in workspaces (where the user owns the file).
env:
MISE_VERSION: v2026.5.12
MISE_SHA256: a238972a3162d710b85b28c324372e96ca4e4b486c81fe78695000d9fbc77c48
WORKSPACE: ${{ github.workspace }}
run: |
set -euo pipefail
curl --silent --show-error --location --fail \
"https://github.com/jdx/mise/releases/download/${MISE_VERSION}/mise-${MISE_VERSION}-linux-x64" \
--output /tmp/mise
echo "${MISE_SHA256} /tmp/mise" | sha256sum -c
sudo install -m 0755 /tmp/mise /usr/local/bin/mise
rm /tmp/mise
mise --version
mkdir -p "$HOME/.config/mise/conf.d"
cat > "$HOME/.config/mise/conf.d/00-ci-trust.toml" <<EOF
[settings]
trusted_config_paths = ["$WORKSPACE"]
EOF
- name: Compute image SHAs
# Match the fork guard on the downstream consumers of these
# outputs: nothing reads `steps.shas.outputs.*` outside the
# base-push + mise-oci pipeline, which is gated below.
if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
id: shas
env:
IMAGE_VERSION: ${{ matrix.image-version }}
run: |
base_sha="$(./scripts/dogfood/compute-base-sha.sh "$IMAGE_VERSION")"
final_sha="$(./scripts/dogfood/compute-final-sha.sh "$IMAGE_VERSION")"
echo "base_sha=${base_sha}" >> "$GITHUB_OUTPUT"
echo "final_sha=${final_sha}" >> "$GITHUB_OUTPUT"
- name: Login to GHCR
# Fork PRs get a read-only GITHUB_TOKEN that cannot push to
# ghcr.io. Skip the entire GHCR-dependent pipeline (base push +
# mise oci build) for fork PRs; the nix matrix entry still runs.
if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub - name: Login to DockerHub
if: github.ref == 'refs/heads/main' if: github.ref == 'refs/heads/main'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
@@ -126,48 +194,122 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build and push Ubuntu 22.04 image - name: Build base image
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
with: with:
project: b4q6ltmpzh project: b4q6ltmpzh
token: ${{ secrets.DEPOT_TOKEN }} token: ${{ secrets.DEPOT_TOKEN }}
buildx-fallback: true buildx-fallback: true
# Context is the repo root so the Dockerfile can COPY the # Context is the repo root so Dockerfile.base can COPY the
# project mise.toml that the image installs from. The # distro-specific files/ tree and configure-chrome-flags.sh.
# github_token secret raises aqua's GitHub API quota during
# `mise install`.
context: "{{defaultContext}}" context: "{{defaultContext}}"
file: dogfood/coder/ubuntu-22.04/Dockerfile file: dogfood/coder/ubuntu-${{ matrix.image-version }}/Dockerfile.base
secrets: |
github_token=${{ secrets.GITHUB_TOKEN }}
pull: true pull: true
save: true # Push to ghcr.io on every non-fork CI run so the downstream
push: ${{ github.ref == 'refs/heads/main' }} # mise oci build can --from a real registry. The base-sha tag
# TODO: move the `latest` tag to 26.04 soon. we don't want to transition # is a cache key (see scripts/dogfood/compute-base-sha.sh) so
# it immediately because that would make workspaces switch to it # commits that don't change base inputs reuse the previous
# automatically without any grace period. # build.
tags: "codercom/oss-dogfood:${{ steps.docker-tag-name.outputs.tag }},codercom/oss-dogfood:22.04,codercom/oss-dogfood:latest" push: true
if: matrix.image-version == '22.04' tags: |
ghcr.io/coder/oss-dogfood-base:${{ matrix.image-version }}-${{ steps.shas.outputs.base_sha }}
ghcr.io/coder/oss-dogfood-base:${{ matrix.image-version }}-${{ steps.docker-tag-name.outputs.tag }}
- name: Build and push Ubuntu 26.04 image - name: Install mise tools
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
with: # `mise oci build` packages already-installed tools into OCI
project: b4q6ltmpzh # layers; it does not install them. Run `mise install` first so
token: ${{ secrets.DEPOT_TOKEN }} # the tools land in MISE_DATA_DIR on the runner.
buildx-fallback: true # github_token raises aqua's API quota during tool installs.
# Context is the repo root so the Dockerfile can COPY the env:
# project mise.toml that the image installs from. The GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# github_token secret raises aqua's GitHub API quota during run: |
# `mise install`. # --locked refuses to resolve URLs at install time and forces
context: "{{defaultContext}}" # the runner to consume what mise.lock already committed,
file: dogfood/coder/ubuntu-26.04/Dockerfile # so a forgotten lockfile entry fails CI instead of silently
secrets: | # being added on next run.
github_token=${{ secrets.GITHUB_TOKEN }} mise install --yes --locked
pull: true # Put mise's shims dir on PATH for subsequent steps so
save: true # `mise oci push --tool crane` can find crane (and any other
push: ${{ github.ref == 'refs/heads/main' }} # mise-managed binary it shells out to).
tags: "codercom/oss-dogfood:${{ steps.docker-tag-name.outputs.tag }},codercom/oss-dogfood:26.04" echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH"
if: matrix.image-version == '26.04'
- name: Build mise oci layer
if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
env:
IMAGE_VERSION: ${{ matrix.image-version }}
BASE_SHA: ${{ steps.shas.outputs.base_sha }}
FINAL_SHA: ${{ steps.shas.outputs.final_sha }}
# --output makes the OCI layout location explicit so the later
# `mise oci push --image-dir` steps point at the right path even
# if mise oci's default ever changes (it's experimental).
run: |
mise oci build \
--from "ghcr.io/coder/oss-dogfood-base:${IMAGE_VERSION}-${BASE_SHA}" \
--tag "codercom/oss-dogfood:${FINAL_SHA}-${IMAGE_VERSION}" \
--output ./mise-oci
# Load the OCI layout into the local Docker daemon so the next
# step can `docker run` it. crane lacks a direct OCI-layout-to-
# daemon command, but its built-in registry server gives us a
# simple two-hop path with no extra dependencies.
- name: Load mise oci image into Docker daemon
if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
env:
IMAGE_VERSION: ${{ matrix.image-version }}
run: |
set -euo pipefail
crane registry serve --address localhost:5000 &
reg_pid=$!
trap 'kill $reg_pid 2>/dev/null || true' EXIT
for _ in 1 2 3 4 5; do
curl -sf http://localhost:5000/v2/ >/dev/null && break
sleep 1
done
crane push ./mise-oci "localhost:5000/dogfood-test:${IMAGE_VERSION}"
docker pull "localhost:5000/dogfood-test:${IMAGE_VERSION}"
docker tag "localhost:5000/dogfood-test:${IMAGE_VERSION}" "dogfood-test:${IMAGE_VERSION}"
# Validate the dogfood image's tooling by running make gen, fmt,
# lint, and a fat build inside it. Failures here block the
# Docker Hub push below so broken images never reach workspaces.
- name: Test image tooling
if: matrix.image-version != 'nix' && !github.event.pull_request.head.repo.fork
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./scripts/dogfood_test_image.sh "dogfood-test:${{ matrix.image-version }}"
- name: Push final Ubuntu 22.04 image
if: matrix.image-version == '22.04' && github.ref == 'refs/heads/main'
env:
FINAL_SHA: ${{ steps.shas.outputs.final_sha }}
DOCKER_TAG: ${{ steps.docker-tag-name.outputs.tag }}
# --image-dir points at the OCI layout written by the previous
# `mise oci build` step. Without it, `mise oci push` rebuilds
# from mise.toml and forgets the --from base. --tool crane
# forces the registry client mise oci shells out to, so we
# don't drift between the apt-shipped skopeo on whatever runner
# image we land on.
# TODO: move the `latest` tag to 26.04 soon. we don't want to
# transition it immediately because that would make workspaces
# switch to it automatically without any grace period.
run: |
set -euo pipefail
for tag in "${FINAL_SHA}-22.04" "$DOCKER_TAG" 22.04 latest; do
mise oci push --tool crane --image-dir ./mise-oci "codercom/oss-dogfood:$tag"
done
- name: Push final Ubuntu 26.04 image
if: matrix.image-version == '26.04' && github.ref == 'refs/heads/main'
env:
FINAL_SHA: ${{ steps.shas.outputs.final_sha }}
DOCKER_TAG: ${{ steps.docker-tag-name.outputs.tag }}
run: |
set -euo pipefail
for tag in "${FINAL_SHA}-26.04" "$DOCKER_TAG" 26.04; do
mise oci push --tool crane --image-dir ./mise-oci "codercom/oss-dogfood:$tag"
done
- name: Build and push vscode-coder image - name: Build and push vscode-coder image
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
@@ -201,59 +343,8 @@ jobs:
env: env:
DOCKER_TAG: ${{ steps.docker-tag-name.outputs.tag }} DOCKER_TAG: ${{ steps.docker-tag-name.outputs.tag }}
# Validate that the Ubuntu dogfood images contain working tooling.
# Failures here block template deployment (deploy_template).
test_image:
needs: build_image
strategy:
fail-fast: false
matrix:
image-version: ["22.04", "26.04"]
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
persist-credentials: false
- name: Set up Depot CLI
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
# Near-instant cache hit from build_image; loads into local daemon
# without pushing to a registry.
- name: Load dogfood image from Depot cache
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
with:
project: b4q6ltmpzh
token: ${{ secrets.DEPOT_TOKEN }}
buildx-fallback: true
context: "{{defaultContext}}"
file: dogfood/coder/ubuntu-${{ matrix.image-version }}/Dockerfile
secrets: |
github_token=${{ secrets.GITHUB_TOKEN }}
pull: true
load: true
push: false
tags: "dogfood-test:${{ matrix.image-version }}"
- name: Test image tooling
run: ./scripts/dogfood_test_image.sh "dogfood-test:${{ matrix.image-version }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
deploy_template: deploy_template:
needs: needs: build_image
- build_image
- test_image
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage) # Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
+9
View File
@@ -96,6 +96,15 @@ __debug_bin*
# Local agent configuration # Local agent configuration
AGENTS.local.md AGENTS.local.md
# mise local overrides
mise.local.toml
.mise.local.toml
mise.*.local.toml
.mise.*.local.toml
# `mise oci build` writes its OCI image layout here by default.
mise-oci/
/.env /.env
# Ignore plans written by AI agents. # Ignore plans written by AI agents.
-6
View File
@@ -1636,12 +1636,6 @@ else
endif endif
.PHONY: test-e2e .PHONY: test-e2e
dogfood/coder/nix.hash: flake.nix flake.lock
sha256sum flake.nix flake.lock >./dogfood/coder/nix.hash
dogfood/coder/mise.hash: mise.toml mise.lock
sha256sum mise.toml mise.lock >./dogfood/coder/mise.hash
# Count the number of test databases created per test package. # Count the number of test databases created per test package.
count-test-databases: count-test-databases:
PGPASSWORD=postgres psql -h localhost -U postgres -d coder_testing -P pager=off -c 'SELECT test_package, count(*) as count from test_databases GROUP BY test_package ORDER BY count DESC' PGPASSWORD=postgres psql -h localhost -U postgres -d coder_testing -P pager=off -c 'SELECT test_package, count(*) as count from test_databases GROUP BY test_package ORDER BY count DESC'
+57 -40
View File
@@ -3,62 +3,79 @@
# tag names. # tag names.
build_tag ?= $(shell git rev-parse --abbrev-ref HEAD | sed "s/\\//-/") build_tag ?= $(shell git rev-parse --abbrev-ref HEAD | sed "s/\\//-/")
# The Dockerfiles consume the repo root as build context so they can # The base Dockerfile consumes the repo root as build context so it can
# reach the project mise.toml. Each variant still tracks its own # reach the distro-specific files/ tree and configure-chrome-flags.sh
# files/ tree under dogfood/coder/ubuntu-<release>/. # under dogfood/coder/ubuntu-<release>/.
REPO_ROOT := $(shell git rev-parse --show-toplevel) REPO_ROOT := $(shell git rev-parse --show-toplevel)
# Mise's aqua backend exhausts GitHub's unauthenticated API quota # Pick a container runtime. On macOS we prefer Apple's `container` CLI
# quickly. Plumb a token through to the mise install layer when one # when present (it produces a Linux VM-backed amd64 image without
# is available. Two equivalent ways to supply it: # Docker Desktop); otherwise fall back to docker. Linux always uses
# GITHUB_TOKEN=ghp_... - taken straight from the environment # docker.
# (matches GitHub Actions, where OS := $(shell uname -s)
# secrets.GITHUB_TOKEN is auto-provided) ifeq ($(OS),Darwin)
# GITHUB_TOKEN_FILE=/path - read the token from a file CONTAINER_RUNTIME ?= $(shell command -v container >/dev/null 2>&1 && echo container || echo docker)
# If neither is set the build still runs but may hit 403s. else
ifneq ($(GITHUB_TOKEN_FILE),) CONTAINER_RUNTIME ?= docker
docker_secret_arg := --secret id=github_token,src="$(GITHUB_TOKEN_FILE)"
else ifneq ($(GITHUB_TOKEN),)
docker_secret_arg := --secret id=github_token,env=GITHUB_TOKEN
endif endif
# Apple's `container` defaults to the host arch; the dogfood image is
# amd64-only, so pin it.
ifeq ($(CONTAINER_RUNTIME),container)
PLATFORM_ARG := --platform linux/amd64
else
PLATFORM_ARG :=
endif
ifeq ($(OS),Linux)
# `mise oci build` packages already-installed tools; the install
# has to run first. The macOS wrapper does this inside the
# container; on Linux we chain it here.
MISE_OCI := mise install --yes && MISE_EXPERIMENTAL=1 mise oci
else
MISE_OCI := CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) $(REPO_ROOT)/scripts/dogfood/mise-oci-wrapper.sh
endif
.PHONY: build build-ubuntu-22.04 build-ubuntu-26.04 \
build-base-ubuntu-22.04 build-base-ubuntu-26.04 \
update-keys update-keys-ubuntu-22.04 update-keys-ubuntu-26.04
build: build-ubuntu-22.04 build-ubuntu-26.04 build: build-ubuntu-22.04 build-ubuntu-26.04
.PHONY: build
build-ubuntu-22.04: # Caveat: `build-ubuntu-*` requires the base image to be pullable from a
DOCKER_BUILDKIT=1 docker build \ # registry that `mise oci`'s HTTPS client can reach (ghcr.io, a local
-f dogfood/coder/ubuntu-22.04/Dockerfile \ # `registry:2` sidecar, etc.). `--from coderdev/oss-dogfood-base:*-local`
-t "codercom/oss-dogfood:22.04-$(build_tag)" \ # only resolves when a registry mirror is set up alongside; without it,
$(docker_secret_arg) \ # `mise oci build` fails because the wrapper container cannot see the
# host's local image store. The `build-base-ubuntu-*` targets on their
# own work end to end without any registry. See
# scripts/dogfood/mise-oci-wrapper.sh for the full story.
build-base-ubuntu-22.04:
$(CONTAINER_RUNTIME) build $(PLATFORM_ARG) \
-f "$(REPO_ROOT)/dogfood/coder/ubuntu-22.04/Dockerfile.base" \
-t "coderdev/oss-dogfood-base:22.04-local" \
"$(REPO_ROOT)" "$(REPO_ROOT)"
.PHONY: build-ubuntu-22.04
build-ubuntu-26.04: build-base-ubuntu-26.04:
DOCKER_BUILDKIT=1 docker build \ $(CONTAINER_RUNTIME) build $(PLATFORM_ARG) \
-f dogfood/coder/ubuntu-26.04/Dockerfile \ -f "$(REPO_ROOT)/dogfood/coder/ubuntu-26.04/Dockerfile.base" \
-t "codercom/oss-dogfood:26.04-$(build_tag)" \ -t "coderdev/oss-dogfood-base:26.04-local" \
$(docker_secret_arg) \
"$(REPO_ROOT)" "$(REPO_ROOT)"
.PHONY: build-ubuntu-26.04
push: push-ubuntu-22.04 push-ubuntu-26.04 build-ubuntu-22.04: build-base-ubuntu-22.04
.PHONY: push $(MISE_OCI) build \
--from "coderdev/oss-dogfood-base:22.04-local" \
--tag "codercom/oss-dogfood:22.04-$(build_tag)"
push-ubuntu-22.04: build-ubuntu-22.04 build-ubuntu-26.04: build-base-ubuntu-26.04
docker push ${build_tag} $(MISE_OCI) build \
.PHONY: push-ubuntu-22.04 --from "coderdev/oss-dogfood-base:26.04-local" \
--tag "codercom/oss-dogfood:26.04-$(build_tag)"
push-ubuntu-26.04: build-ubuntu-26.04
docker push ${build_tag}
.PHONY: push-ubuntu-26.04
update-keys: update-keys-ubuntu-22.04 update-keys-ubuntu-26.04 update-keys: update-keys-ubuntu-22.04 update-keys-ubuntu-26.04
.PHONY: update-keys
update-keys-ubuntu-22.04: update-keys-ubuntu-22.04:
./ubuntu-22.04/update-keys.sh ./ubuntu-22.04/update-keys.sh
.PHONY: update-keys-ubuntu-22.04
update-keys-ubuntu-26.04: update-keys-ubuntu-26.04:
./ubuntu-26.04/update-keys.sh ./ubuntu-26.04/update-keys.sh
.PHONY: update-keys-ubuntu-26.04
+35 -7
View File
@@ -687,10 +687,41 @@ resource "coder_script" "install-deps" {
coder exp sync want install-deps git-clone coder exp sync want install-deps git-clone
coder exp sync start install-deps coder exp sync start install-deps
# Seed a user-owned mise trust config on the persistent home
# volume. The image ships /etc/mise/conf.d/00-coder-trust.toml as
# a fallback, but mise's `trusted_config_paths` setting doesn't
# merge across config layers, so anything the user adds to their
# own file would otherwise replace the system fallback. Writing
# this file once (if-absent) seeds the defaults under
# ~/.config/mise/conf.d/ where the user can edit it and the edits
# survive workspace restart.
TRUST_FILE="$HOME/.config/mise/conf.d/00-coder-trust.toml"
if [ ! -f "$TRUST_FILE" ]; then
mkdir -p "$(dirname "$TRUST_FILE")"
cat > "$TRUST_FILE" <<'TRUST'
# mise trust paths for the dogfood workspace. Edit to add your own
# paths; this file lives on the persistent home volume so changes
# survive workspace restart. The install-deps coder_script only
# writes this file when it's absent.
[settings]
trusted_config_paths = [
"/home/coder/coder",
"/etc/mise",
]
TRUST
fi
# Install playwright dependencies # Install playwright dependencies
# We want to use the playwright version from site/package.json # We want to use the playwright version from site/package.json
cd "${local.repo_dir}" && make clean cd "${local.repo_dir}" && make clean
cd "${local.repo_dir}/site" && pnpm install cd "${local.repo_dir}/site" && pnpm install
# Two playwright installs: site/'s @playwright/test and
# @playwright/mcp@0.0.75 bundle different playwright-core versions
# with different chromium revisions, and both are used at runtime
# (site tests + the claude-code/codex MCP servers below).
cd "${local.repo_dir}/site" && pnpm exec playwright install chromium
npx --yes --package=@playwright/mcp@0.0.75 playwright-core install --no-shell chromium
EOT EOT
} }
@@ -823,13 +854,10 @@ data "docker_registry_image" "dogfood" {
resource "docker_image" "dogfood" { resource "docker_image" "dogfood" {
name = "${local.image_tags[data.coder_parameter.image_type.value]}@${data.docker_registry_image.dogfood.sha256_digest}" name = "${local.image_tags[data.coder_parameter.image_type.value]}@${data.docker_registry_image.dogfood.sha256_digest}"
# CI rebuilds and pushes when any baked-in input changes, so the
# digest captures every effective change on its own.
pull_triggers = [ pull_triggers = [
data.docker_registry_image.dogfood.sha256_digest, data.docker_registry_image.dogfood.sha256_digest,
sha1(join("", [for f in fileset(path.module, "files/*") : filesha1(f)])),
filesha1("ubuntu-22.04/Dockerfile"),
filesha1("ubuntu-26.04/Dockerfile"),
filesha1("nix.hash"),
filesha1("mise.hash"),
] ]
keep_locally = true keep_locally = true
} }
@@ -969,7 +997,7 @@ module "claude-code" {
"mcpServers": { "mcpServers": {
"playwright": { "playwright": {
"command": "npx", "command": "npx",
"args": ["--", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"] "args": ["--", "@playwright/mcp@0.0.75", "--headless", "--isolated", "--no-sandbox"]
} }
} }
} }
@@ -1000,7 +1028,7 @@ module "codex" {
mcp = <<-EOT mcp = <<-EOT
[mcp_servers.playwright] [mcp_servers.playwright]
command = "npx" command = "npx"
args = ["--", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"] args = ["--", "@playwright/mcp@0.0.75", "--headless", "--isolated", "--no-sandbox"]
type = "stdio" type = "stdio"
EOT EOT
} }
-2
View File
@@ -1,2 +0,0 @@
b5226f4cb3256b5f67df1344f46968f7275b1b8309380506d25782168bab5622 mise.toml
b5cf72024409932659abde978440fca1d01a75bb11f1476e2410f7d4b83aa9c0 mise.lock
-2
View File
@@ -1,2 +0,0 @@
f09cd2cbbcdf00f5e855c6ddecab6008d11d871dc4ca5e1bc90aa14d4e3a2cfd flake.nix
0d2489a26d149dade9c57ba33acfdb309b38100ac253ed0c67a2eca04a187e37 flake.lock
@@ -38,6 +38,7 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
bat \ bat \
bats \ bats \
bind9-dnsutils \ bind9-dnsutils \
bison \
build-essential \ build-essential \
ca-certificates \ ca-certificates \
containerd.io \ containerd.io \
@@ -50,6 +51,7 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
fd-find \ fd-find \
file \ file \
fish \ fish \
flex \
gettext-base \ gettext-base \
git \ git \
gnupg \ gnupg \
@@ -66,6 +68,8 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
language-pack-en \ language-pack-en \
less \ less \
libgbm-dev \ libgbm-dev \
libicu-dev \
libreadline-dev \
libssl-dev \ libssl-dev \
lsb-release \ lsb-release \
lsof \ lsof \
@@ -93,10 +97,12 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
tmux \ tmux \
traceroute \ traceroute \
unzip \ unzip \
uuid-dev \
vim \ vim \
wget \ wget \
xauth \ xauth \
zip \ zip \
zlib1g-dev \
zsh \ zsh \
zstd && \ zstd && \
# Delete package cache to avoid consuming space in layer # Delete package cache to avoid consuming space in layer
@@ -183,45 +189,28 @@ RUN install --directory --owner=coder --group=coder --mode=0755 "${MISE_INSTALL_
test -x /usr/local/bin/mise && \ test -x /usr/local/bin/mise && \
sudo --login --user=coder /bin/bash -lc 'set -euo pipefail && mise_bin="$(readlink --canonicalize /usr/local/bin/mise)" && test -w "$(dirname "$mise_bin")" && /usr/local/bin/mise --version && /usr/local/bin/mise self-update --help >/dev/null && /usr/local/bin/mise upgrade --help >/dev/null' sudo --login --user=coder /bin/bash -lc 'set -euo pipefail && mise_bin="$(readlink --canonicalize /usr/local/bin/mise)" && test -w "$(dirname "$mise_bin")" && /usr/local/bin/mise --version && /usr/local/bin/mise self-update --help >/dev/null && /usr/local/bin/mise upgrade --help >/dev/null'
# Trusted paths skip mise's per-config trust prompt for the baked-in ENV MISE_DATA_DIR=/home/coder/.local/share/mise
# system config and the coder repo when cloned at the canonical
# /home/coder/coder location. Other repos a user clones still get
# the one-time `mise trust` prompt; pre-trusting all of /home/coder
# would let any mise.toml under the home dir auto-run [hooks]/[tasks].
ENV MISE_DATA_DIR=/home/coder/.local/share/mise \
MISE_TRUSTED_CONFIG_PATHS=/home/coder/coder:/etc/mise
# Bake the project manifest in as mise's system config and ship # Bake a system fallback for trusted_config_paths so the canonical
# the lockfile alongside it so mise verifies download checksums # /home/coder/coder repo and the mise-oci-synthesized /etc/mise/config.toml
# during install. We do NOT override MISE_GLOBAL_CONFIG_FILE; that # are trusted without a per-config prompt. The workspace template
# would re-target `mise use --global` away from the user's # (dogfood/coder/main.tf install-deps coder_script) seeds a matching
# ~/.config/mise/config.toml (on the home volume) into this # user-owned ~/.config/mise/conf.d/00-coder-trust.toml on workspace
# image-only path, breaking the workflow. # start, which the user can edit to add their own paths; that file
# # lives on the persistent home volume and overrides this fallback.
# We pre-create /etc/mise as 0755 because COPY's implicitly-created RUN install --directory --mode=0755 /etc/mise /etc/mise/conf.d
# parent dirs inherit the --chmod, which would leave /etc/mise COPY --chmod=0644 <<'EOF' /etc/mise/conf.d/00-coder-trust.toml
# without the `x` bit and unreachable to the coder user. We also [settings]
# chown to coder so mise can write the temp lockfile it uses for trusted_config_paths = [
# atomic rename when updating /etc/mise/mise.lock during installs. "/home/coder/coder",
RUN install --directory --owner=coder --group=coder --mode=0755 /etc/mise "/etc/mise",
COPY --chown=coder:coder --chmod=0644 mise.toml /etc/mise/config.toml ]
COPY --chown=coder:coder --chmod=0644 mise.lock /etc/mise/mise.lock EOF
# Pre-install tools into /opt/mise/data so they survive the home # Reserve the mount_point declared in mise.toml [oci]. The path is
# volume's copy-on-first-mount. MISE_SHARED_INSTALL_DIRS (set below) # duplicated below in MISE_SHARED_INSTALL_DIRS and PATH; if it ever
# exposes them at runtime; MISE_DATA_DIR stays on the home volume. # changes, update all three plus mise.toml.
# github_token authenticates aqua's API calls (optional secret).
RUN install --directory --owner=coder --group=coder --mode=0755 /opt/mise /opt/mise/data RUN install --directory --owner=coder --group=coder --mode=0755 /opt/mise /opt/mise/data
RUN --mount=type=secret,id=github_token,required=false \
gh_token="$(cat /run/secrets/github_token 2>/dev/null || true)" && \
sudo --user=coder env \
"MISE_DATA_DIR=/opt/mise/data" \
"MISE_TRUSTED_CONFIG_PATHS=$MISE_TRUSTED_CONFIG_PATHS" \
"GITHUB_TOKEN=$gh_token" \
/usr/local/bin/mise install --yes && \
PATH="/opt/mise/data/shims:$PATH" MISE_DATA_DIR=/opt/mise/data pnpm dlx playwright@1.47.0 install --with-deps chromium && \
rm -rf /opt/mise/data/cache /opt/mise/data/downloads && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Homebrew as the coder user so the supported Linux prefix remains # Install Homebrew as the coder user so the supported Linux prefix remains
# writable after the image build. # writable after the image build.
@@ -229,10 +218,12 @@ RUN sudo --login --user=coder env NONINTERACTIVE=1 CI=1 /bin/bash -lc 'set -euo
test -x /home/linuxbrew/.linuxbrew/bin/brew && \ test -x /home/linuxbrew/.linuxbrew/bin/brew && \
sudo --login --user=coder /bin/bash -lc '/home/linuxbrew/.linuxbrew/bin/brew --version' sudo --login --user=coder /bin/bash -lc '/home/linuxbrew/.linuxbrew/bin/brew --version'
# Adjust OpenSSH config # Adjust OpenSSH config and drop the apt lists / cache that survived
# the package installs above. No later step in this image needs apt.
RUN echo "PermitUserEnvironment yes" >>/etc/ssh/sshd_config && \ RUN echo "PermitUserEnvironment yes" >>/etc/ssh/sshd_config && \
echo "X11Forwarding yes" >>/etc/ssh/sshd_config && \ echo "X11Forwarding yes" >>/etc/ssh/sshd_config && \
echo "X11UseLocalhost no" >>/etc/ssh/sshd_config echo "X11UseLocalhost no" >>/etc/ssh/sshd_config && \
apt-get clean && rm -rf /var/lib/apt/lists/*
USER coder USER coder
@@ -37,6 +37,7 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
bat \ bat \
bats \ bats \
bind9-dnsutils \ bind9-dnsutils \
bison \
build-essential \ build-essential \
ca-certificates \ ca-certificates \
containerd.io \ containerd.io \
@@ -49,6 +50,7 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
fd-find \ fd-find \
file \ file \
fish \ fish \
flex \
gettext-base \ gettext-base \
git \ git \
gnupg \ gnupg \
@@ -65,6 +67,8 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
language-pack-en \ language-pack-en \
less \ less \
libgbm-dev \ libgbm-dev \
libicu-dev \
libreadline-dev \
libssl-dev \ libssl-dev \
lsb-release \ lsb-release \
lsof \ lsof \
@@ -92,10 +96,12 @@ RUN sed -i 's|http://archive.ubuntu.com/ubuntu/|http://mirrors.edge.kernel.org/u
tmux \ tmux \
traceroute \ traceroute \
unzip \ unzip \
uuid-dev \
vim \ vim \
wget \ wget \
xauth \ xauth \
zip \ zip \
zlib1g-dev \
zsh \ zsh \
zstd && \ zstd && \
# Keep Docker's engine, CLI, runtime, and plugins on the versions selected by # Keep Docker's engine, CLI, runtime, and plugins on the versions selected by
@@ -193,45 +199,28 @@ RUN install --directory --owner=coder --group=coder --mode=0755 "${MISE_INSTALL_
test -x /usr/local/bin/mise && \ test -x /usr/local/bin/mise && \
sudo --login --user=coder /bin/bash -lc 'set -euo pipefail && mise_bin="$(readlink --canonicalize /usr/local/bin/mise)" && test -w "$(dirname "$mise_bin")" && /usr/local/bin/mise --version && /usr/local/bin/mise self-update --help >/dev/null && /usr/local/bin/mise upgrade --help >/dev/null' sudo --login --user=coder /bin/bash -lc 'set -euo pipefail && mise_bin="$(readlink --canonicalize /usr/local/bin/mise)" && test -w "$(dirname "$mise_bin")" && /usr/local/bin/mise --version && /usr/local/bin/mise self-update --help >/dev/null && /usr/local/bin/mise upgrade --help >/dev/null'
# Trusted paths skip mise's per-config trust prompt for the baked-in ENV MISE_DATA_DIR=/home/coder/.local/share/mise
# system config and the coder repo when cloned at the canonical
# /home/coder/coder location. Other repos a user clones still get
# the one-time `mise trust` prompt; pre-trusting all of /home/coder
# would let any mise.toml under the home dir auto-run [hooks]/[tasks].
ENV MISE_DATA_DIR=/home/coder/.local/share/mise \
MISE_TRUSTED_CONFIG_PATHS=/home/coder/coder:/etc/mise
# Bake the project manifest in as mise's system config and ship # Bake a system fallback for trusted_config_paths so the canonical
# the lockfile alongside it so mise verifies download checksums # /home/coder/coder repo and the mise-oci-synthesized /etc/mise/config.toml
# during install. We do NOT override MISE_GLOBAL_CONFIG_FILE; that # are trusted without a per-config prompt. The workspace template
# would re-target `mise use --global` away from the user's # (dogfood/coder/main.tf install-deps coder_script) seeds a matching
# ~/.config/mise/config.toml (on the home volume) into this # user-owned ~/.config/mise/conf.d/00-coder-trust.toml on workspace
# image-only path, breaking the workflow. # start, which the user can edit to add their own paths; that file
# # lives on the persistent home volume and overrides this fallback.
# We pre-create /etc/mise as 0755 because COPY's implicitly-created RUN install --directory --mode=0755 /etc/mise /etc/mise/conf.d
# parent dirs inherit the --chmod, which would leave /etc/mise COPY --chmod=0644 <<'EOF' /etc/mise/conf.d/00-coder-trust.toml
# without the `x` bit and unreachable to the coder user. We also [settings]
# chown to coder so mise can write the temp lockfile it uses for trusted_config_paths = [
# atomic rename when updating /etc/mise/mise.lock during installs. "/home/coder/coder",
RUN install --directory --owner=coder --group=coder --mode=0755 /etc/mise "/etc/mise",
COPY --chown=coder:coder --chmod=0644 mise.toml /etc/mise/config.toml ]
COPY --chown=coder:coder --chmod=0644 mise.lock /etc/mise/mise.lock EOF
# Pre-install tools into /opt/mise/data so they survive the home # Reserve the mount_point declared in mise.toml [oci]. The path is
# volume's copy-on-first-mount. MISE_SHARED_INSTALL_DIRS (set below) # duplicated below in MISE_SHARED_INSTALL_DIRS and PATH; if it ever
# exposes them at runtime; MISE_DATA_DIR stays on the home volume. # changes, update all three plus mise.toml.
# github_token authenticates aqua's API calls (optional secret).
RUN install --directory --owner=coder --group=coder --mode=0755 /opt/mise /opt/mise/data RUN install --directory --owner=coder --group=coder --mode=0755 /opt/mise /opt/mise/data
RUN --mount=type=secret,id=github_token,required=false \
gh_token="$(cat /run/secrets/github_token 2>/dev/null || true)" && \
sudo --user=coder env \
"MISE_DATA_DIR=/opt/mise/data" \
"MISE_TRUSTED_CONFIG_PATHS=$MISE_TRUSTED_CONFIG_PATHS" \
"GITHUB_TOKEN=$gh_token" \
/usr/local/bin/mise install --yes && \
PATH="/opt/mise/data/shims:$PATH" MISE_DATA_DIR=/opt/mise/data pnpm dlx playwright@1.47.0 install --with-deps chromium && \
rm -rf /opt/mise/data/cache /opt/mise/data/downloads && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Homebrew as the coder user so the supported Linux prefix remains # Install Homebrew as the coder user so the supported Linux prefix remains
# writable after the image build. # writable after the image build.
@@ -239,10 +228,12 @@ RUN sudo --login --user=coder env NONINTERACTIVE=1 CI=1 /bin/bash -lc 'set -euo
test -x /home/linuxbrew/.linuxbrew/bin/brew && \ test -x /home/linuxbrew/.linuxbrew/bin/brew && \
sudo --login --user=coder /bin/bash -lc '/home/linuxbrew/.linuxbrew/bin/brew --version' sudo --login --user=coder /bin/bash -lc '/home/linuxbrew/.linuxbrew/bin/brew --version'
# Adjust OpenSSH config # Adjust OpenSSH config and drop the apt lists / cache that survived
# the package installs above. No later step in this image needs apt.
RUN echo "PermitUserEnvironment yes" >>/etc/ssh/sshd_config && \ RUN echo "PermitUserEnvironment yes" >>/etc/ssh/sshd_config && \
echo "X11Forwarding yes" >>/etc/ssh/sshd_config && \ echo "X11Forwarding yes" >>/etc/ssh/sshd_config && \
echo "X11UseLocalhost no" >>/etc/ssh/sshd_config echo "X11UseLocalhost no" >>/etc/ssh/sshd_config && \
apt-get clean && rm -rf /var/lib/apt/lists/*
USER coder USER coder
-14
View File
@@ -198,7 +198,6 @@
pango pango
pixman pixman
pkg-config pkg-config
playwright-driver.browsers
pnpm pnpm
postgresql_16 postgresql_16
proto_gen_go_1_30 proto_gen_go_1_30
@@ -278,16 +277,6 @@
''; '';
}; };
in in
# "Keep in mind that you need to use the same version of playwright in your node playwright project as in your nixpkgs, or else playwright will try to use browsers versions that aren't installed!"
# - https://nixos.wiki/wiki/Playwright
assert pkgs.lib.assertMsg
(
(pkgs.lib.importJSON ./site/package.json).devDependencies."@playwright/test"
== pkgs.playwright-driver.version
)
"There is a mismatch between the playwright versions in the ./nix.flake (${pkgs.playwright-driver.version}) and the ./site/package.json (${
(pkgs.lib.importJSON ./site/package.json).devDependencies."@playwright/test"
}) file. Please make sure that they use the exact same version.";
rec { rec {
inherit formatter; inherit formatter;
@@ -301,9 +290,6 @@
{ {
buildInputs = devShellPackages; buildInputs = devShellPackages;
PLAYWRIGHT_BROWSERS_PATH = pkgs.playwright-driver.browsers;
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
LOCALE_ARCHIVE = LOCALE_ARCHIVE =
with pkgs; with pkgs;
lib.optionalDrvAttr stdenv.isLinux "${glibcLocales}/lib/locale/locale-archive"; lib.optionalDrvAttr stdenv.isLinux "${glibcLocales}/lib/locale/locale-archive";
+50
View File
@@ -288,6 +288,54 @@ url = "https://github.com/sigstore/cosign/releases/download/v2.4.3/cosign-window
checksum = "sha256:a2ac24e197111c9430cb2a98f10a641164381afb83df036504868e4ea5720800" checksum = "sha256:a2ac24e197111c9430cb2a98f10a641164381afb83df036504868e4ea5720800"
url = "https://github.com/sigstore/cosign/releases/download/v2.4.3/cosign-windows-amd64.exe" url = "https://github.com/sigstore/cosign/releases/download/v2.4.3/cosign-windows-amd64.exe"
[[tools.crane]]
version = "0.21.6"
backend = "aqua:google/go-containerregistry"
[tools.crane."platforms.linux-arm64"]
checksum = "sha256:6f61571ca0c2a5da27c2927fcb143255ccb2b74b8977dfcb44645b372ab0f951"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Linux_arm64.tar.gz"
[tools.crane."platforms.linux-arm64-musl"]
checksum = "sha256:6f61571ca0c2a5da27c2927fcb143255ccb2b74b8977dfcb44645b372ab0f951"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Linux_arm64.tar.gz"
[tools.crane."platforms.linux-x64"]
checksum = "sha256:7ebbdcd05b652345c1f5105f8475e518534b90d66f3bdb50017be63f426ea435"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Linux_x86_64.tar.gz"
[tools.crane."platforms.linux-x64-baseline"]
checksum = "sha256:7ebbdcd05b652345c1f5105f8475e518534b90d66f3bdb50017be63f426ea435"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Linux_x86_64.tar.gz"
[tools.crane."platforms.linux-x64-musl"]
checksum = "sha256:7ebbdcd05b652345c1f5105f8475e518534b90d66f3bdb50017be63f426ea435"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Linux_x86_64.tar.gz"
[tools.crane."platforms.linux-x64-musl-baseline"]
checksum = "sha256:7ebbdcd05b652345c1f5105f8475e518534b90d66f3bdb50017be63f426ea435"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Linux_x86_64.tar.gz"
[tools.crane."platforms.macos-arm64"]
checksum = "sha256:a124f297d1e63e8b6c63c2463e43565290d2fd074c1dadb5ca73d737bc7b2484"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Darwin_arm64.tar.gz"
[tools.crane."platforms.macos-x64"]
checksum = "sha256:f1e653737a1d6e8a412734d0ac25009e04eccec98853be2eb59b8c744dede834"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Darwin_x86_64.tar.gz"
[tools.crane."platforms.macos-x64-baseline"]
checksum = "sha256:f1e653737a1d6e8a412734d0ac25009e04eccec98853be2eb59b8c744dede834"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Darwin_x86_64.tar.gz"
[tools.crane."platforms.windows-x64"]
checksum = "sha256:fb78f814f68ab47266458f319ca7e642a303453ea25c8993a14eb9850c56e870"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Windows_x86_64.tar.gz"
[tools.crane."platforms.windows-x64-baseline"]
checksum = "sha256:fb78f814f68ab47266458f319ca7e642a303453ea25c8993a14eb9850c56e870"
url = "https://github.com/google/go-containerregistry/releases/download/v0.21.6/go-containerregistry_Windows_x86_64.tar.gz"
[[tools.doctl]] [[tools.doctl]]
version = "1.158.0" version = "1.158.0"
backend = "aqua:digitalocean/doctl" backend = "aqua:digitalocean/doctl"
@@ -734,6 +782,7 @@ url = "https://github.com/protocolbuffers/protobuf/releases/download/v23.4/proto
url = "https://github.com/protocolbuffers/protobuf/releases/download/v23.4/protoc-23.4-linux-aarch_64.zip" url = "https://github.com/protocolbuffers/protobuf/releases/download/v23.4/protoc-23.4-linux-aarch_64.zip"
[tools.protoc."platforms.linux-x64"] [tools.protoc."platforms.linux-x64"]
checksum = "blake3:b1d1a517cb9c8c3cbfc98c708f93e6d3bd8b3ce0e2db1ad8c1491ae8a4067ad2"
url = "https://github.com/protocolbuffers/protobuf/releases/download/v23.4/protoc-23.4-linux-x86_64.zip" url = "https://github.com/protocolbuffers/protobuf/releases/download/v23.4/protoc-23.4-linux-x86_64.zip"
[tools.protoc."platforms.linux-x64-baseline"] [tools.protoc."platforms.linux-x64-baseline"]
@@ -771,6 +820,7 @@ url = "https://github.com/protocolbuffers/protobuf-go/releases/download/v1.30.0/
url = "https://github.com/protocolbuffers/protobuf-go/releases/download/v1.30.0/protoc-gen-go.v1.30.0.linux.arm64.tar.gz" url = "https://github.com/protocolbuffers/protobuf-go/releases/download/v1.30.0/protoc-gen-go.v1.30.0.linux.arm64.tar.gz"
[tools.protoc-gen-go."platforms.linux-x64"] [tools.protoc-gen-go."platforms.linux-x64"]
checksum = "blake3:127ed3a8005b199a8451c258ea8fe8ae0f68dd01b4e52c21c881eb7f1d69a333"
url = "https://github.com/protocolbuffers/protobuf-go/releases/download/v1.30.0/protoc-gen-go.v1.30.0.linux.amd64.tar.gz" url = "https://github.com/protocolbuffers/protobuf-go/releases/download/v1.30.0/protoc-gen-go.v1.30.0.linux.amd64.tar.gz"
[tools.protoc-gen-go."platforms.linux-x64-baseline"] [tools.protoc-gen-go."platforms.linux-x64-baseline"]
+21
View File
@@ -1,3 +1,7 @@
# Keep in lockstep with MISE_VERSION in dogfood/coder/ubuntu-*/Dockerfile.base,
# .github/workflows/dogfood.yaml, and scripts/dogfood/mise-oci-wrapper.sh.
min_version = "2026.5.12"
[settings] [settings]
lockfile = true lockfile = true
@@ -28,6 +32,10 @@ protoc-gen-go = "1.30.0"
# Infrastructure, release, and lint CLIs. # Infrastructure, release, and lint CLIs.
"aqua:ahmetb/kubectx/kubens" = "0.9.4" "aqua:ahmetb/kubectx/kubens" = "0.9.4"
cosign = "2.4.3" cosign = "2.4.3"
# crane is the registry client `mise oci push` shells out to. Sourced
# here so it travels with the rest of the mise toolset (one source of
# truth, deterministic version, no apt drift across CI / wrapper).
crane = "0.21.6"
golangci-lint = "1.64.8" golangci-lint = "1.64.8"
helm = "3.21.0" helm = "3.21.0"
kubectx = "0.9.4" kubectx = "0.9.4"
@@ -61,3 +69,16 @@ lazygit = "0.61.1"
[tools."go:github.com/coder/sqlc/cmd/sqlc"] [tools."go:github.com/coder/sqlc/cmd/sqlc"]
version = "337309bfb9524f38466a5090e310040fc7af0203" version = "337309bfb9524f38466a5090e310040fc7af0203"
install_env = { CGO_ENABLED = "1" } install_env = { CGO_ENABLED = "1" }
# Consumed by `mise oci build` to produce the dogfood image on top of
# ghcr.io/coder/oss-dogfood-base. The `from` and `--tag` fields are
# overridden by CLI args at build time per distro; `mount_point`,
# `user`, and `workdir` always apply.
#
# mount_point MUST match the path the base image reserves and exposes
# via `MISE_SHARED_INSTALL_DIRS`. Both Dockerfile.base files hardcode
# /opt/mise/data in their `install --directory`, ENV, and PATH lines.
[oci]
mount_point = "/opt/mise/data"
user = "coder"
workdir = "/home/coder"
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Deterministic 12-char content hash of base-image inputs for a distro.
# Used as a cache key for the ghcr.io/coder/oss-dogfood-base tag so
# commits that don't touch the base inputs reuse the previous build.
#
# This is NOT a strict content address: the base Dockerfile still
# pulls dynamic resources at build time (gh/buildx releases/latest,
# chrome stable_current_amd64.deb, apt mirror state, sh.rustup.rs).
# Two runs with identical checked-in files can still produce slightly
# different bytes. That's acceptable here because the dynamic drift
# is small and the cache-hit savings (no full base rebuild for a
# typo-fix commit, doc change, mise.toml bump, etc.) is large.
set -euo pipefail
# 12 hex chars matches docker/OCI short-digest displays.
HASH_LEN=12
distro="${1:?usage: $0 <22.04|26.04>}"
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
paths=(
"dogfood/coder/ubuntu-${distro}/Dockerfile.base"
"dogfood/coder/ubuntu-${distro}/files"
)
if [ "$distro" = "22.04" ]; then
paths+=("dogfood/coder/ubuntu-${distro}/configure-chrome-flags.sh")
fi
# Skip editor turds; .swp / ~-files / dotfiles are noise for a build
# hash. Include symlinks too: `COPY dogfood/coder/ubuntu-*/files /`
# bakes their target paths into the image, so swapping a symlink
# changes base content and must invalidate the cache key.
find "${paths[@]}" \( -type f -o -type l \) \
! -name '.*' \
! -name '*.swp' \
! -name '*~' \
-print0 |
LC_ALL=C sort -z |
xargs -0 sha256sum |
sha256sum |
cut -c"1-$HASH_LEN"
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Deterministic 12-char content hash of (base inputs + mise inputs) for
# a distro. Used as the primary tag for the dogfood image produced by
# `mise oci build`, so re-running CI on an unchanged commit reuses the
# previous tag. Same cache-key (not strict content address) semantics
# as `compute-base-sha.sh`.
set -euo pipefail
# 12 hex chars; see comment in compute-base-sha.sh.
HASH_LEN=12
distro="${1:?usage: $0 <22.04|26.04>}"
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
base_sha="$("$repo_root/scripts/dogfood/compute-base-sha.sh" "$distro")"
mise_hash="$(sha256sum mise.toml mise.lock | sha256sum | cut -c"1-$HASH_LEN")"
printf '%s\n' "$base_sha-$mise_hash" | sha256sum | cut -c"1-$HASH_LEN"
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Local-only helper: runs `mise oci ...` inside a Linux container so
# macOS and Windows developers don't need a local Linux VM or a host
# install of mise. CI runs `mise oci` directly on its Linux runner; it
# does not use this script.
#
# Builds a small Debian-based wrapper image with the mise binary on
# first invocation, then reuses it. Pinning to the same `MISE_VERSION`
# baked into `Dockerfile.base` avoids depending on jdxcode/mise Docker
# Hub publication cadence, which lags upstream GitHub releases by days.
#
# `oci build --from <ref>` requires <ref> to be a registry-resolvable
# reference; the host's local Docker daemon images are not visible
# inside the wrapper. See the Makefile comment.
#
# Honors CONTAINER_RUNTIME=docker (default) or CONTAINER_RUNTIME=container
# (Apple's `container` CLI on macOS).
set -euo pipefail
# Keep MISE_VERSION + MISE_SHA256 in lockstep with the same vars in
# .github/workflows/dogfood.yaml and dogfood/coder/ubuntu-*/Dockerfile.base.
# A `min_version` check in mise.toml catches downgrades.
MISE_VERSION="v2026.5.12"
MISE_SHA256="a238972a3162d710b85b28c324372e96ca4e4b486c81fe78695000d9fbc77c48"
# Bump the -rN suffix when the Dockerfile heredoc below changes
# (mise version, apt packages, trust config, etc.) so cached wrapper
# images get rebuilt automatically.
WRAPPER_REVISION="r2"
RUNTIME="${CONTAINER_RUNTIME:-docker}"
WRAPPER_IMAGE="coderdev/mise-oci-wrapper:$MISE_VERSION-$WRAPPER_REVISION"
# Mount the repo root rather than $PWD: `make -C dogfood/coder` invokes
# the wrapper from dogfood/coder/, but the project mise.toml/mise.lock
# `mise oci build` consumes live at the repo root.
REPO_ROOT="$(git rev-parse --show-toplevel)"
platform_arg=()
if [ "$RUNTIME" = "container" ]; then
platform_arg=(--platform linux/amd64)
fi
# Build the wrapper image on first invocation. The tag includes the
# mise version so a bump automatically invalidates the cache; the old
# image becomes orphaned and the user can prune it manually.
if ! "$RUNTIME" image inspect "$WRAPPER_IMAGE" >/dev/null 2>&1; then
echo "[$0] Building $WRAPPER_IMAGE (first-time setup)..." >&2
build_dir="$(mktemp -d)"
trap 'rm -rf "$build_dir"' EXIT
cat >"$build_dir/Dockerfile" <<DOCKERFILE
FROM debian:bookworm-slim
# crane (the registry client mise oci shells out to) is installed via
# mise.toml at run time, not here. Keeps the image lean and avoids
# version drift between this base layer and what mise oci uses.
RUN apt-get update -qq && \\
apt-get install -y -qq --no-install-recommends \\
ca-certificates curl && \\
rm -rf /var/lib/apt/lists/* && \\
curl -sSLf "https://github.com/jdx/mise/releases/download/${MISE_VERSION}/mise-${MISE_VERSION}-linux-x64" -o /usr/local/bin/mise && \\
echo "${MISE_SHA256} /usr/local/bin/mise" | sha256sum -c && \\
chmod +x /usr/local/bin/mise && \\
install --directory --mode=0755 /etc/mise /etc/mise/conf.d && \\
printf '[settings]\\ntrusted_config_paths = ["/src"]\\n' > /etc/mise/conf.d/00-trust.toml
DOCKERFILE
"$RUNTIME" build ${platform_arg[@]+"${platform_arg[@]}"} -t "$WRAPPER_IMAGE" "$build_dir"
rm -rf "$build_dir"
trap - EXIT
fi
token_arg=()
if [ -n "${GITHUB_TOKEN:-}" ]; then
token_arg=(-e "GITHUB_TOKEN=$GITHUB_TOKEN")
fi
# Mount ~/.docker when present so crane can find registry creds.
# Apple `container` CLI users without Docker Desktop won't have it;
# local builds don't push, so the skip is fine.
docker_config_arg=()
if [ -d "$HOME/.docker" ]; then
docker_config_arg=(-v "$HOME/.docker:/root/.docker:ro")
fi
# `oci build` needs all mise tools installed so it can package them
# into layers. `oci push` needs crane on PATH (mise oci shells out to
# it). Both end up running `mise install` first; build installs every
# tool, push only crane. The `export PATH=...` exposes mise's shims
# dir so `which crane` succeeds when mise oci spawns it as a child.
# Single quotes are intentional: $HOME and $@ expand inside the
# container's `sh -c`, not in this script.
# shellcheck disable=SC2016
inner_cmd='mise oci "$@"'
case "${1:-}" in
build)
# shellcheck disable=SC2016
inner_cmd='mise install --yes && export PATH="$HOME/.local/share/mise/shims:$PATH" && mise oci "$@"'
;;
push)
# shellcheck disable=SC2016
inner_cmd='mise install --yes crane && export PATH="$HOME/.local/share/mise/shims:$PATH" && mise oci "$@"'
;;
esac
exec "$RUNTIME" run --rm ${platform_arg[@]+"${platform_arg[@]}"} \
-v "$REPO_ROOT":/src -w /src \
${docker_config_arg[@]+"${docker_config_arg[@]}"} \
-e MISE_EXPERIMENTAL=1 \
${token_arg[@]+"${token_arg[@]}"} \
--entrypoint /bin/sh \
"$WRAPPER_IMAGE" \
-c "$inner_cmd" -- "$@"
+9 -5
View File
@@ -50,17 +50,21 @@ else
fi fi
# Helper: run a make target inside the image. # Helper: run a make target inside the image.
# Caches are persisted in named Docker volumes so that subsequent steps (and #
# repeated local runs) reuse downloaded modules and compiled artifacts. # Mounts /home/coder/ as a single named volume to mirror the dogfood
# workspace template (dogfood/coder/main.tf), so caches (Go modules,
# Go build, pnpm store, mise data, etc.) persist the same way they do
# in real workspaces. Per-cache subpath volumes would come up
# root-owned on first mount because Docker creates non-existent
# subpaths root-owned; the home-level volume inherits coder:coder
# from the image's existing /home/coder (`useradd --create-home`).
run_make() { run_make() {
docker run --rm \ docker run --rm \
--volume coder-dogfood-home:/home/coder \
--volume "$(pwd)":/home/coder/coder \ --volume "$(pwd)":/home/coder/coder \
--env GIT_CONFIG_COUNT=1 \ --env GIT_CONFIG_COUNT=1 \
--env GIT_CONFIG_KEY_0=safe.directory \ --env GIT_CONFIG_KEY_0=safe.directory \
--env GIT_CONFIG_VALUE_0=/home/coder/coder \ --env GIT_CONFIG_VALUE_0=/home/coder/coder \
--volume coder-dogfood-gomod:/home/coder/go/pkg/mod \
--volume coder-dogfood-gobuild:/home/coder/.cache/go-build \
--volume coder-dogfood-pnpm:/home/coder/.local/share/pnpm/store \
--workdir /home/coder/coder \ --workdir /home/coder/coder \
--network=host \ --network=host \
--env GITHUB_TOKEN \ --env GITHUB_TOKEN \
-2
View File
@@ -37,6 +37,4 @@ echo "protoc-gen-go version: $PROTOC_GEN_GO_REV"
PROTOC_GEN_GO_SHA256=$(nix-prefetch-git https://github.com/protocolbuffers/protobuf-go --rev "$PROTOC_GEN_GO_REV" | jq -r .hash) PROTOC_GEN_GO_SHA256=$(nix-prefetch-git https://github.com/protocolbuffers/protobuf-go --rev "$PROTOC_GEN_GO_REV" | jq -r .hash)
sed -i "s#\(sha256 = \"\)[^\"]*#\1${PROTOC_GEN_GO_SHA256}#" ./flake.nix sed -i "s#\(sha256 = \"\)[^\"]*#\1${PROTOC_GEN_GO_SHA256}#" ./flake.nix
make dogfood/coder/nix.hash
echo "Flake updated successfully!" echo "Flake updated successfully!"