Files
coder/scripts/dogfood_test_image.sh
Thomas Kosiewski 51836e681e 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>
2026-05-26 14:52:21 +02:00

112 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Validates dogfood image tooling by running gen, fmt, lint, and build inside
# the image. Can be run locally or in CI (mirrors the test_image workflow job).
#
# Usage: ./scripts/dogfood_test_image.sh <image>
#
# Arguments:
# image Docker image to test, e.g. dogfood-test:22.04 or
# ghcr.io/coder/dogfood:latest
#
# Environment:
# GITHUB_TOKEN Passed into the container for authenticated API calls
# (optional for local runs).
# GITHUB_BASE_REF Base branch for diff-only lint checks (e.g. emdash).
# Set automatically by GitHub Actions for PRs.
# CI When set, fmt targets run in check-mode and actionlint
# is excluded from make lint (it runs separately in CI).
# STEPS Space-separated list of steps to run. Defaults to all.
# Valid values: gen fmt lint build check-unstaged
#
# Example:
# ./scripts/dogfood_test_image.sh dogfood-test:22.04
# STEPS="gen fmt" ./scripts/dogfood_test_image.sh dogfood-test:26.04
set -euo pipefail
# shellcheck source=scripts/lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
cdroot
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <image>" >&2
exit 1
fi
IMAGE="$1"
STEPS="${STEPS:-gen fmt lint build check-unstaged}"
log() {
echo "==> $*" >&2
}
# --- setup -------------------------------------------------------------------
if [[ -n "${CI:-}" ]]; then
log "Preparing checkout for container user (UID 1000)"
chmod -R a+rwX .
else
log "NOTE: if the container cannot write to the checkout, run: chmod -R a+rwX ."
fi
# Helper: run a make target inside the image.
#
# 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() {
docker run --rm \
--volume coder-dogfood-home:/home/coder \
--volume "$(pwd)":/home/coder/coder \
--env GIT_CONFIG_COUNT=1 \
--env GIT_CONFIG_KEY_0=safe.directory \
--env GIT_CONFIG_VALUE_0=/home/coder/coder \
--workdir /home/coder/coder \
--network=host \
--env GITHUB_TOKEN \
--env GITHUB_BASE_REF \
--env CI \
"$IMAGE" \
make "$@"
}
# --- steps -------------------------------------------------------------------
for step in $STEPS; do
case "$step" in
gen)
log "make gen (GEN_SKIP_GOLDEN=1, skips tests that need Docker/testcontainers)"
run_make --output-sync=line -j gen GEN_SKIP_GOLDEN=1
;;
fmt)
log "make fmt"
run_make --output-sync=line -j fmt
;;
lint)
log "make lint"
run_make --output-sync=line -j lint
;;
build)
log "make build (fat binary)"
run_make -j build/coder_linux_amd64
;;
check-unstaged)
# Runs on the host: inspects git state after container steps wrote
# generated/formatted files back via the volume mount.
log "Checking for unstaged files"
./scripts/check_unstaged.sh
;;
*)
echo "Unknown step: $step" >&2
echo "Valid steps: gen fmt lint build check-unstaged" >&2
exit 1
;;
esac
done
log "All steps passed."