Skip to content

ci: release smoke builds every Dockerfile's binary, migrates and boots web + admin - #115

Merged
jayleaton merged 3 commits into
mainfrom
ci/release-smoke
Oct 23, 2026
Merged

jayleaton merged 3 commits into
mainfrom
ci/release-smoke

Conversation

@jayleaton

@jayleaton jayleaton commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

Ported from jayleaton/auto-visuals #31 (why: #26, #29). CI never built the images or booted the binaries. So when an image could not start, or a production config refused to boot without an optional integration, the first place that showed up was a deploy. In the Imagen port, #29 was an admin binary that a new dependency (purego) left dynamically linked even with CGO_ENABLED=0, and #26 was an admin that would not boot in production without R2. Based on main (#105 and #106 are merged; also tested with #113's admin changes).

Summary

scripts/dockerfile-builds.sh   one line per Dockerfile, read from the Dockerfile itself:
                               <dockerfile> <build-stage ENV> <RUN go build … line> <-o path> <package>
scripts/release-smoke.sh
 1. build    each binary with exactly its Dockerfile's ENV + RUN line (env -i: no caller GOFLAGS/CGO leak)
             readelf finds a program interpreter -> fail "… is dynamically linked: it would not start on distroless/static"
 2. migrate  `server migrate`, then `admin migrate` on fresh databases (as pre-deploy does)
 3. boot     web + admin, APP_ENV=production, only production's required config -> /healthz 200

when:  scripts/affected.sh smoke=true  <- full run | a Dockerfile changed | an affected package is one a Dockerfile builds
                                          (test files alone: false)
where: a task in scripts/ci-check.sh (parallel, inside the single ci job; uses that job's Postgres)
       fan-out: the "other" leg;  Check timeout +1 min when it runs;  locally: make release-smoke

Docs: AGENTS.md "Verifying" has a Release smoke bullet, and the CI-layout line mentions it. README.md is untouched.

Evidence

Passing, on Actions:

  • run 39930070829, with cold release builds:
    ok    36.8s  smoke
    Dockerfile: CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /tmp/…/server ./apps/web/cmd/server
    server: statically linked   admin: statically linked   backup: statically linked
    web: migrated   admin: migrated
    web: /healthz 200   admin: /healthz 200
    == release smoke ok (31s)
    
  • run 39930614151, final head 65883a7, warm cache: ok 10.8s smoke, and the job took 54 s.

Measured cost:

run Check step job
#106, no smoke 50 s 65 s
this PR, smoke with cold release builds 61 s 74 s
this PR, smoke warm n/a 54 s

The smoke runs in parallel with the other tasks, so it adds at most about 11 s of wall time.

Failing on a dynamically linked build (the #29 bug, reintroduced). A scratch commit added _ "github.com/ebitengine/purego" to apps/admin/cmd/admin and changed nothing else. Run 39930402758 failed:

##[error]smoke failed (36.3s, exit 1)
server: statically linked
 (NEEDED) Shared library: [libdl.so.2]  [libpthread.so.0]  [libc.so.6]
release smoke: admin is dynamically linked: it would not start on distroless/static (apps/admin/Dockerfile). …

I deleted the scratch branch afterwards. The run's separate lint failure was gofmt on the scratch import.

When the smoke runs (AFFECTED_FILES=… scripts/affected.sh):

changed files smoke
AGENTS.md false
deploy/platform.yaml false
apps/web/internal/services/users_test.go only false
apps/web/internal/services/users.go true
apps/web/ui/static/app.css (embedded) true
apps/admin/Dockerfile true
scripts/release-smoke.sh (full run) true

make check-fast passed and make actionlint reported no issues. Per the load rules I did not run the smoke locally; Docker on this machine is also out of network pools for make db-up.

Merge Danger

Door: two-way. Only CI, scripts and docs change. Delete the task or the smoke output to turn it off.

Blast Radius: CI

A PR that touches what a Dockerfile builds now also has to build static, migrate fresh databases and boot web and admin in production mode with only the required config. A new setting that production requires has to be added to the smoke's env lists, which is intended: that is exactly what a deploy would need too.

🤖 Generated with Claude Code

@jayleaton
jayleaton force-pushed the ci/dev-main-branches branch from 944f96b to e34fc99 Compare October 23, 2026 09:26
jayleaton and others added 3 commits October 23, 2026 16:35
…s web + admin

Ported from auto-visuals #31 (why: #26, #29). CI never built the images
or booted the binaries, so an image that cannot start, or a production
config that refuses to boot without an optional integration, reached a
deploy first.

scripts/release-smoke.sh, in the single ci job (and make release-smoke):
1. builds every binary that has a Dockerfile with that Dockerfile's own
   build-stage ENV and RUN line (scripts/dockerfile-builds.sh reads them;
   nothing copies the flags), and fails if one is dynamically linked;
2. runs the binaries' own migrate on fresh databases;
3. boots web and admin with APP_ENV=production and only the config
   production requires, until /healthz answers 200.

It runs only when a build input changed: scripts/affected.sh outputs
smoke=true for a full run, a changed Dockerfile, or an affected package
that a Dockerfile builds (test files alone do not count).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayleaton
jayleaton changed the base branch from ci/dev-main-branches to main October 23, 2026 09:35
@jayleaton
jayleaton marked this pull request as ready for review October 23, 2026 09:36
@jayleaton
jayleaton merged commit 9a9d3bc into main Oct 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant