Skip to content

trading stevedore cutover: exploit the build-once Dockerfile (compile once, stamp 13 images) #39

Description

@blairham

Follow-up to pinpredict/trading#1967 (build-once Dockerfile, PR pinpredict/trading#1968) and part of the #38 migration. Captures the rollout plan and the findings from the 2026-07-05 verification session so they don't get lost.

Context — what's already done

  • trading Dockerfile restructured build-once (trading#1968, 3 commits): build stage restores+publishes all 12 .NET services with no build-arg references → byte-identical layers for every image; production stage stays PROJECT-parameterized and final. Verified: /app trees sha256-identical to the old per-service build (oms/reconciler/nadex-oegw); image sizes unchanged (±~25 B on ~124 MB); second image assembles in ~2 s from cache; full local stevedore build of the 13-image catalog passes with the publish layer CACHED on every image.
  • trading ci/stevedore-config branch ready (4 commits, rebased, local): includes two bugs found by actually building — missing RUNTIME_BASE (fixed by defaulting the Dockerfile to aspnet:10.0; every consumer already passed it) and settlements-csv needing context: . (its Dockerfile COPYs repo-root-relative paths).
  • Measured baseline: full 13-job docker-release matrix averages ~69 s/job ≈ ~15 runner-min and ~1.5 min wall per shared release (GHA cache warm).

Why the CI shape matters

Trading's 13 images cannot form a stevedore build group (grouping = identical image, many repos — e.g. dis's 5-way group; trading's images differ by content). So:

  • Matrix mode fan-out (plan → one runner per group): 13 cold BuildKit caches → the shared build stage compiles 13× (each a union compile) ≈ ~40–50 runner-min. Worse than today.
  • Single-job shape (the current draft stevedore-release.yml): one runner, one BuildKit; the solver dedups the shared build stage even across concurrent builds (parallel: 4 still compiles once) → ~4–5 runner-min per shared release.

Plan

  • Merge trading#1968 (byte-parity + boot-smoke verified; one full release wave on merge is expected — Dockerfile is a shared rebuild pattern).
  • Wire cross-run caching into the stevedore path: one type=gha scope now serves the shared build stage for all trading images (vs 13 per-service scopes thrashing the 10 GB repo cache limit under docker-release).
  • Clear the remaining stevedore migration readiness — blockers before docker-release runs on stevedore #38 blocker: feat: machine-readable release summary of pushed images blairham/stevedore#6 machine-readable publish summary → notify-dispatch.
  • Cut trading over on the single-job workflow shape: open the ci/stevedore-config PR, flip trading's ci.yml off docker-release.yml@pre-stevedore, drop the pin.
  • Optional stevedore feature (needed only if trading should later ride matrix mode for per-service xp-<name>-gha-push roles): cache affinity — coalesce plan groups that share (dockerfile, context) into one matrix entry with combined only: + merged pins, so same-Dockerfile images land on one runner while dis-style repos still fan out. File on blairham/stevedore if pursued.
  • Audit the other 12 repos' .stevedore.yaml for the settlements-csv bug class (context: vs Dockerfile COPY paths) — it only surfaces when stevedore actually builds, so run stevedore build locally per repo before opening each config PR.

Immediate wins that need no further work once #1968 merges

scripts/chaos-rig-*.sh and docker-compose builds (5–6 images serially from a developer checkout) drop from N full compiles to one compile + seconds per image, and builds are now hermetic from dirty checkouts (**/bin/**/obj excluded from the context).

Metadata

Metadata

Assignees

No one assigned

    Labels

    claude-followupFollow-up work captured from a Claude session

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions