Skip to content

feat(build): CLI owns the docker build via agent recipes (#443) - #445

Merged
antoinetoussaint-byte merged 4 commits into
mainfrom
feat/cli-owned-docker-build
Aug 21, 2026
Merged

feat(build): CLI owns the docker build via agent recipes (#443)#445
antoinetoussaint-byte merged 4 commits into
mainfrom
feat/cli-owned-docker-build

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byteantoinetoussaint-byte commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Part of #443. Depends on codefly-dev/core#332merged and released as core v0.3.6; this PR is now pinned to that tag and ready for review.

Summary

  • Moves docker build out of the agents and into the CLI, mirroring the Deploy pattern. When an agent returns a DockerBuildPlan, the CLI passes an output_directory (the committed services/<svc>/builder dir), verifies the emitted recipe tree against the plan inventory, then runs docker buildx per recipe — multi-arch, pushed as a manifest list — so images are no longer tied to the builder's host architecture (the arm64-on-Apple-silicon failure) and the recipe is a durable, committed artifact.
  • A service can emit many recipes (app image + migration image + …); each is built.
  • Agents that still return a DockerBuildResult keep the legacy in-process build + CLI push, so the fleet migrates one agent at a time — the CLI branches on the response type, no capability negotiation needed.

Hardening (from review)

  • Provisions a docker-container buildx builder for multi-platform (the default docker driver rejects --platform a,b); single-platform local --load stays on the default builder.
  • Snapshot digest read from buildx --metadata-file (a pushed multi-arch build never lands in the local image store, so docker image inspect can't recover it).
  • Stages the agent's declared dockerignore at the buildx-discovered <dockerfile>.dockerignore path (buildx never finds builder/dockerignore).
  • Refuses to push unless linux/amd64 is targeted (arm64-only/empty recipe → exec format error on amd64 nodes).
  • Rejects empty recipe lists, contains recipe context and Dockerfile paths to the recipe tree, streams build output, and no longer pre-creates builder/.

Rollout

  1. core#332 — merged, released as v0.3.6
  2. this PR — CLI executor (legacy path preserved), pinned to v0.3.6 ✅
  3. Fleet — adopt WithBuildPlan per agent repo (~30), then modules/workspaces re-sync.

Supersedes the interim durable-recipe archive in #444; once this lands, durability is a property of the contract and #444 can close.

Test plan

  • go build ./pkg/orchestration/ + full go test ./pkg/orchestration/ against released core v0.3.6
  • unit tests: buildx argv (multi-arch --push + --builder + --metadata-file; single-platform --load), arch floor, dockerignore staging/cleanup/no-clobber, context + dockerfile containment, digest parsing, no-mkdir
  • golangci-lint clean
  • end-to-end against a migrated pilot agent (next: service-go-grpc)

When an agent returns a DockerBuildPlan (core#332), the CLI now owns the
docker build instead of the agent building in-process:
- Pass output_directory (the committed services/<svc>/builder dir) in the
BuildRequest so the agent emits its recipes there.
- Verify the emitted recipe tree against the plan inventory (core
VerifyDockerBuildPlan), then run docker buildx per recipe — multi-arch and
pushed as a manifest list when pushing — so images are no longer tied to the
builder's host architecture and the recipe is a durable, committed artifact.
- A service may emit many recipes (app image, migration image, …); each is
built.
Agents that still return a DockerBuildResult keep the legacy in-process build +
CLI push path, so the fleet migrates one agent at a time.
Depends on codefly-dev/core#332 (pinned here as a pseudo-version; re-pin to the
core release tag before merge).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review of the executor found four ways the docker-buildx path breaks the
moment an agent emits a recipe. Fix the real causes:
- Multi-arch requires a container-driver builder: the default buildx builder
uses the "docker" driver, which rejects `--platform a,b`. Provision a
dedicated docker-container builder (ensureBuildxBuilder) and pass `--builder`
for multi-platform builds; single-platform local `--load` still uses the
default builder.
- Snapshot digest after `--push`: a pushed multi-arch build never lands in the
local image store, so `docker image inspect` fails. Read the manifest digest
from buildx `--metadata-file` (containerimage.digest) instead.
- Dropped dockerignore: buildx only discovers `<dockerfile>.dockerignore` or
`<context>/.dockerignore`, never the `builder/dockerignore` name agents emit,
so the ignore was silently unapplied and `COPY . .` pulled in node_modules/
.git. Stage the declared ignore at the discovered sibling path for the build,
with cleanup.
- Missing arch floor: the plan path trusted the agent's platform list, so an
arm64-only (or empty, host-arch) recipe would push an image that crashes with
exec format error on amd64 nodes — the exact failure verifyImageArchitecture
guarded. Refuse to push unless linux/amd64 is targeted.
Also: reject an empty recipe list (was a silent no-op success); stream build
output instead of buffering it until failure; stop pre-creating builder/ so a
legacy agent that ignores output_directory leaves no empty directory; reject a
recipe context that escapes the service directory.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
buildRecipe already rejected a build context that escapes the service
directory (recipeContext), but resolved the Dockerfile with a bare
filepath.Join and no containment check. VerifyDockerBuildPlan digests only
the recipe file tree, not the recipe fields, so a recipe with a "../"
Dockerfile (e.g. "../../../../etc/passwd") pointed docker buildx -f at an
out-of-tree file. Guard the Dockerfile the same way recipeContext guards the
context, and cover it with a test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qU5NUAyTGahfbYMbM23t7
Re-pin from the core#332 pseudo-version to the released tag now that core
v0.3.6 ships DockerBuildPlan / output_directory and BuildDockerBuildPlan /
VerifyDockerBuildPlan / WithBuildPlan.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte marked this pull request as ready for review August 21, 2026 20:44
@antoinetoussaint-byte
antoinetoussaint-byte merged commit 1701823 into mainAug 21, 2026
5 checks passed
This was referenced Aug 21, 2026
Sign up for freeto 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

@antoinetoussaint-byte