[fix] deploy rebuilds a project's own image - #99
Merged
Conversation
`docker compose up -d` builds a service only when its image is MISSING, so
a project with a build: section was built once and never again. Editing a
Dockerfile and hitting Deploy silently kept the old image running, with
the CLI reporting nothing worse than "Container Running". Deploy now
passes --build; it is a no-op for image-only services. POST {"build":
false} opts out, and the MCP deploy tool matches the UI.
Retires the "upload build: contexts to a remote host" roadmap item as
based on a wrong premise: Docker's build API sends the context as a tar
from the client, so a remote daemon has always received the local folder
and built the image there. Verified end to end against a second daemon,
together with the bind seeding it is often confused with.
This was referenced Jul 31, 2026
malickyeu
added a commit
that referenced
this pull request
Jul 31, 2026
…104) README/docs/README/NEXT: stacks can now be edited & redeployed, project deploy rebuilds, build: contexts upload with the build. docs/mcp.md: the MCP deploy tool passes --build like the UI, and why that isn't a wider surface. NEXT: the version matrix is CI-verified, not just local. Records what repeated testing turned up, in the places it will be read before the next test is written (feature-tests skill, docs/testing.md, CONTRIBUTING): mutation-test every guard and check it fails for the right reason — three tests here passed while guarding nothing, because compose validation or a shared status code was doing the rejecting. Plus the daemon fixture traps: t.Context() is cancelled before t.Cleanup runs, a pentest whose guard fails leaves real containers behind, and a killed run poisons the next one. Also logs a gap found while auditing: StackRedeploy still runs a plain up -d, so a CLI stack with build: has the same staleness bug #99 fixed for Projects.
malickyeu
added a commit
that referenced
this pull request
Jul 31, 2026
StackRedeploy ran a plain `docker compose up -d`, so a CLI-discovered stack declaring build: kept running the image from its first deploy no matter what changed in its Dockerfile or context on the host — the same staleness bug fixed for Projects in #99, on the other code path. Found while auditing the docs against the merged code: the two deploy paths are separate, and only one had been corrected. The test builds the stack with the real compose CLI so the labels StackRedeploy works from are genuine, then edits the context and redeploys through the app. Verified by mutation: without --build it fails with marker "v1".
10 tasks
malickyeu
added a commit
that referenced
this pull request
Jul 31, 2026
StackRedeploy ran a plain `docker compose up -d`, so a CLI-discovered stack declaring build: kept running the image from its first deploy no matter what changed in its Dockerfile or context on the host — the same staleness bug fixed for Projects in #99, on the other code path. Found while auditing the docs against the merged code: the two deploy paths are separate, and only one had been corrected. The test builds the stack with the real compose CLI so the labels StackRedeploy works from are genuine, then edits the context and redeploys through the app. Verified by mutation: without --build it fails with marker "v1".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This started as the roadmap item "upload
build:contexts to a remote host". Itends somewhere else, so the short version first:
The roadmap item was based on a wrong premise, and building it would have been
wasted work. Docker's build API takes the build context as a tar stream from
the client, so a remote daemon has always received your local
./appfolder andbuilt the image there. Verified before writing any code: the image ends up only on
the remote daemon and contains a file that exists only in the local context.
That is genuinely different from a bind mount — which nothing uploads, and which is
precisely why the
dcseed-*volume seeding exists. Conflating the two is what putthe item on the roadmap. It's now retired in
NEXT.mdwith the reasoning, so itdoesn't come back.
What the investigation did find is a real bug, and it's the fix here.
docker compose up -dbuilds a service only when its image is missing. So aproject with a
build:section was built on its first deploy and never again:every later deploy reused the original image no matter what changed in the
Dockerfile or its context. Edit a Dockerfile in the project editor, hit Deploy, and
the CLI reports
Container Runningand exits 0 — a wrong answer delivered as asuccess, which is the worst shape for a bug in a tool whose whole promise is "the
files in the editor are what runs."
Deploy now passes
--build.Type of change
Checklist
go test -short ./...andgo vet ./...passgofmtgate is clean (gofmt -l $(git ls-files '*.go')after staging)cd web && npx tsc --noEmit)web/distdocs/and added aCHANGELOG.mdentryNotes for reviewers
Why
--buildunconditionally rather than a toggle. It is a no-op for servicesthat only pull an image, so the cost lands exactly where the benefit is: on
projects that actually declare
build:, which are precisely the ones that weresilently going stale. That avoids a UI toggle nobody would find before being bitten
once.
POSTwith{"build": false}opts out for the rare case where re-sending alarge context over a slow link matters more than freshness — the request field is a
*boolso absent means rebuild, andfalseis distinguishable from unset.MCP matches the UI (
mcpDeployProjectpassestrue). Not a widening of theMCP surface:
upalready builds a missing image, so deploying a project with abuild:section could always run its Dockerfile. What it fixes is theinconsistency where the same project deployed via MCP would keep a stale image
while the UI refreshed it.
Two tests, and both were checked against the bug rather than assumed:
TestIntegrationComposeUpRebuildsChangedContextpins both halves — thatwithout
--builda context change is ignored, and with it the change lands.The negative half is the point: a test asserting only "the new content appears"
would pass for the wrong reason if compose ever rebuilt incidentally, and would
never have caught the original bug.
TestRemoteBuildContextDeployEndToEnd(gated onDC_REMOTE_DOCKER) covers thecombination nothing else reached: a project that builds its own image and
bind-mounts a project file, deployed to a daemon that can't see the folder.
The two mechanisms travel completely differently and meet in one
-f compose.yml -f override.jsoninvocation. It also asserts the build contextis not misclassified as a bind needing seeding.
Verified by mutation: flipping that deploy to
build=falsefails it with/marker.txt = "BUILT-V1"after the edit. Run against adocker:28-dindsidecar at
tcp://127.0.0.1:12500.Also corrected a UI string that had become false. The remote-host hint still
said bind-mounting projects were "blocked at deploy … until file sync lands" —
seeding shipped a while ago. It now describes what actually happens, including that
build:contexts are uploaded with the build.Test data: all containers/images created during verification were removed; the
dind sidecar is torn down separately.