Repository navigation
feat(sandbox): infra images keep their build contexts, and a sandbox that can't run an image's tar.gz builds it from that - #169
Merged
Conversation
…that can't run an image's tar.gz builds it from that The run builds the gateway and service-db for this machine, linux/arm64 on Apple silicon, so a remote amd64 VM refused them after it was created, Modal's container gateway refused them as images in this machine's registry, and a run whose deploys were all remote didn't build them at all. - DockerImageArtifact.rebuilt_for(platform): an image with a tar.gz and a build context whose tar.gz a VM of that platform can't run. VmSandbox.load_docker_images builds those from the context for the VM's own platform and loads every other tar.gz, so an image stays byte-identical wherever it can. - Accepts.NAME_OR_CONTEXT takes any image with a build context, and Modal's prepare_image builds one that is only a context, named for a registry Modal can't reach, or built for another platform than Modal's. - Modal's container gateway checks and prepares its stored service-db images like its other images, so EnvironmentGatewayProvider.accepts loses its prepared argument. - The infra puts keep their build contexts. The gateway's narrows to gateway/, the website browser's Dockerfile carries PLAYWRIGHT_MCP_VERSION's default (a context carries no build args), and db-mcp drops its BuildKit-only cache mount, pins its clone, and fails if its sed patch stops applying. - With local stores, a run builds the infra for remote deploys too, and refuses it up front with no Docker here. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dal drops a prepared image it then pulls Stock infra in local stores built before its images kept their build contexts is in this machine's registry, so on Modal preflight refused it and never scheduled the rebuild that would fix it; infra the run builds anew (missing, or built from other inputs) is no longer checked. And a Modal provider that prepared an image under a name, then a pullable one under the same name, built the first: prepare_image now drops the earlier entry when it pulls. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…uild inputs are its build contexts - `_build_and_put` builds an infra image from its Dockerfile's folder and stores that folder as its build context, so the context a sandbox rebuilds from can't drift from what was built here. - `build_inputs_digest` hashes those build contexts (`BuildContext.source_digest`) instead of a hand-kept file list, so a Dockerfile that copies a new file is rebuilt without anyone updating the list. - The website browser's Dockerfile `ARG` is its one Playwright MCP version; the unread Python constant goes. - Preflight and `ensure_default_envs` share one message for infra that needs docker. - Modal's `prepare_image` drops a check `by_name_problem` already makes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…fra Dockerfiles build on Docker's legacy builder - Slow integration tier, local backend: the local sandbox, given this machine's platform as a remote VM records its own, builds an image that is only a build context, and rebuilds one whose tar.gz records another platform from the context beside it (its tar.gz points nowhere, so loading it would fail the deploy). Each serves its card through the gateway. The first integration coverage of a sandbox building from a build context. - Unit: every shipped infra Dockerfile builds from its context alone on Docker's legacy builder, which a VM with no buildx uses: no BuildKit-only syntax, no build arg without a default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Oct 12, 2026
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.
The infra images (the gateway, the service-db's three, the website browser) now keep the build context they're built from, and a sandbox that can't run an image's tar.gz builds it from that instead. So a bundle run from an arm64 Mac deploys its gateway on a remote amd64 VM or on Modal, which it couldn't before.
Why
The run builds the infra for this machine. From Apple silicon that's linux/arm64, and a remote VM runs linux/amd64, so the VM refused the image after it was created, and the deploy failed. Modal's container gateway refused the infra outright: it pulls images by name, and they're in this machine's registry. A run whose deploys were all remote didn't build the infra at all, and asked for a manual put.
What
DockerImageArtifact.rebuilt_for(platform)is true for an image with both a tar.gz and a build context whose tar.gz the VM can't run.VmSandbox.load_docker_imagesbuilds those from the context, for the VM's own platform, and still loads every other tar.gz, so an image stays byte-identical wherever it can. The rule covers any image carrying both, which everyput()with a build context writes.Accepts.NAME_OR_CONTEXTtakes any image with a build context, andModalSandboxProvider.prepare_imagebuilds one that is only a context, named for a registry Modal can't reach, or built for another platform than Modal's.EnvironmentGatewayProvider.acceptsloses itspreparedargument.gateway/(its COPY paths adjusted).build_inputs_digestnow hashes those build contexts rather than a hand-kept file list, so what a run records is what a sandbox rebuilds from. The website browser's Dockerfile getsARG PLAYWRIGHT_MCP_VERSION=0.0.41, because a context carries no build args, and is now the only place the version lives: thePLAYWRIGHT_MCP_VERSIONPython constant, which nothing read any more, goes. db-mcp drops itsRUN --mountcache, which needs BuildKit (modal_vm's Docker has none) and does nothing in a fresh build, pins itsgit cloneto15c8e333(upstream HEAD), and fails its build if itssedpatch stops applying.The Dockerfile edits, and hashing the build contexts, change
build_inputs_digest, so a run rebuilds any stock-built infra in local stores once, now with its build context. Infra in other stores is unchanged until someone puts it again.Tests
Slow integration tier (public, no secrets): the local sandbox, given this machine's platform as a remote VM records its own, builds an image that is only a build context, and rebuilds one whose tar.gz records another platform from the context beside it (that tar.gz points nowhere, so loading it would fail the deploy); each serves its card through the gateway. No integration test covered building from a build context before. A unit test checks every shipped infra Dockerfile builds from its context alone on Docker's legacy builder (no BuildKit-only syntax, no build arg without a default), which would have caught db-mcp's
RUN --mount.Unit tier: 7,457 passed. New: a VM building from the context beside a tar.gz of another platform and loading one of its own;
rebuilt_forandload_problemfor each case; Modal building a tar.gz-plus-context image named for this machine's registry or another platform, and pulling one it can;Acceptsfor that shape; the container gateway preparing its service-db images; each infra put keeping its context, the one its build inputs cover; the build inputs covering every file in a build context but Python caches; preflight building infra for a remote deploy in local stores, refusing it elsewhere, and refusing it with no Docker; stock infra built from other inputs rebuilt for Modal rather than refused as it stands; a pulled image dropping one Modal prepared earlier under its name.Live checks
From an arm64 Mac with fresh local stores, a bundle whose MCP server deploys through the gateway with local Postgres state, checked by a verifier calling it through the gateway. Every run passed:
Cold builds in a modal_vm VM (2 vCPU, 8 GB): gateway 20 s, service-db 4 s, db-web 2 s, website browser 57 s. db-mcp failed there before this change, since
RUN --mountneeds BuildKit.🤖 Generated with Claude Code


Confidence Score: 5/5
The PR appears safe to merge, with both earlier findings fixed and no new actionable issues.Summary
Infra images now keep their build contexts. VMs and Modal can rebuild images they cannot run as stored. Local stores also build missing infra for remote deployments.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Stored infra image] --> B{Sandbox can run stored image?} B -->|Yes| C[Load tarball or pull image] B -->|No, build context available| D[Build from saved context] D --> E[Run on sandbox platform] C --> EReviews (4) · Last reviewed commit: "test: a VM builds images from their buil..." · Reviewed by Greptile