Summary
- Current Cloudflare Container resource always builds and pushes an image on deploy.
- Add support to use a prebuilt/static image reference (e.g., from Cloudflare Registry or a remote registry) without rebuilding.
- Keep current behavior as default; introduce a clear, validated path to skip build/push.
Motivation
- Faster CI/CD by reusing immutable, prebuilt images.
- Support external registries and promotion workflows (dev → staging → prod).
- Deterministic deploys with signed/attested images.
- Aligns with Docker provider patterns already present in the repo.
Current Behavior
alchemy/src/cloudflare/container.ts always derives from docker/Image and triggers docker build and push to Cloudflare registry.
alchemy/src/docker/image.ts is the only path; no direct acceptance of a prebuilt image reference.
worker deployment consumes the image produced by the build step.
Key files:
alchemy/src/cloudflare/container.ts — Container resource delegates to Image and pushes to CF registry.
alchemy/src/cloudflare/worker.ts — Uses the image from the Container resource during ContainerApplication.
alchemy/test/cloudflare/container.test.ts — Tests build/update/adoption via build flow.
examples/cloudflare-container/* — Demonstrates only the build flow.
alchemy/src/docker/remote-image.ts — Remote prebuilt image resource exists but is unused by Cloudflare container.
Proposed Changes
- Extend
ContainerProps to accept a prebuilt image and allow skipping build/push.
- Validate mutual exclusivity between build and prebuilt-image paths.
- Preserve current defaults and ergonomics.
Proposed props (flat, backwards-compatible):
image?: string | RemoteImage — Prebuilt image reference (e.g., <image name>:<tag> per current wrangler pattern).
push?: boolean — When image is provided and not in Cloudflare registry, optionally retag and push to Cloudflare. Default false.
- Existing
build-related props remain supported and unchanged.
Behavior:
- If
image is provided:
- Skip
docker build.
- If
image is a string without a registry host: assume registry.cloudflare.com/<acct>/<image>:<tag>.
- If
image points to a non-Cloudflare registry:
- Cloudflare currently requires images to be in the Cloudflare registry. Enforce
push: true to retag and push into CF; otherwise, throw a clear validation error.
- If
push: false and the image is already in CF registry: no push performed.
- If
image is not provided:
- Preserve current behavior (build and push).
- Validate that
image and build are not used together.
API Examples
- Build and push (unchanged):
await Container("api", { name: "my-api", build: { context: "./", dockerfile: "Dockerfile" } });
- Use prebuilt CF image without push:
await Container("api", { name: "my-api", image: "my-api:1.2.3" });
- Rehost a remote image into CF registry:
const base = await RemoteImage("base", { image: "ghcr.io/org/app:1.2.3" });
await Container("api", { name: "my-api", image: base, push: true });
Acceptance Criteria
- Passing
image: "<name>:<tag>" (implicitly CF registry) deploys without building or pushing.
- Passing
image: RemoteImage(...) with push: true retags and pushes to CF registry, then deploys.
- If
image references a non-CF registry and push !== true, a clear validation error is thrown (since CF currently only accepts CF-hosted images).
- Mutual exclusivity enforced between
image and build.
- Tests cover:
- pure build flow (existing),
- prebuilt-CF-image flow without push,
- remote-image + push-to-CF flow.
- Docs updated with both flows and guidance on when to use which.
Implementation Plan
- Update
alchemy/src/cloudflare/container.ts:
- Extend
ContainerProps with image?, push?
- Branch logic: prebuilt vs build.
- If
image host is non-CF and push !== true, throw validation error; if push: true, retag/push using existing Docker APIs.
- Default CF registry resolution when
image is string without a host.
- Validate mutual exclusivity and provide actionable error messages.
- Adjust
alchemy/src/cloudflare/worker.ts if needed to consume a consistent imageRef from the Container output (should be backward-compatible).
- Tests:
- Add new cases in
alchemy/test/cloudflare/container.test.ts.
- Examples:
- Add a short variant in
examples/cloudflare-container showing prebuilt usage.
- Docs:
- Update
alchemy-web/.../providers/cloudflare/container.md to document both flows and registry nuances.
Backward Compatibility
- No breaking changes; existing build-based usage remains the default path.
- New props are optional and orthogonal.
Constraints (Resolved)
- Cloudflare currently only accepts images hosted in the Cloudflare registry. Non-CF images must be retagged/pushed into CF for deployment.
- No CF registry rate limits or tag naming constraints impact retagging.
Future-Proofing
- Once Cloudflare supports external registries, relax validation to allow direct deploys from non-CF registries with no push. Keep code paths structured to make this a small change.
Risks
- Misconfiguration when
image is not present/accessible in CF; mitigated by validation and clear errors.
References
- Build-only behavior:
alchemy/src/cloudflare/container.ts
- Build pipeline:
alchemy/src/docker/image.ts
- Remote images:
alchemy/src/docker/remote-image.ts
- Worker consumption:
alchemy/src/cloudflare/worker.ts
- Example and docs currently demonstrate build flow only.
Summary
Motivation
Current Behavior
alchemy/src/cloudflare/container.tsalways derives fromdocker/Imageand triggersdocker buildand push to Cloudflare registry.alchemy/src/docker/image.tsis the only path; no direct acceptance of a prebuilt image reference.workerdeployment consumes the image produced by the build step.Key files:
alchemy/src/cloudflare/container.ts— Container resource delegates toImageand pushes to CF registry.alchemy/src/cloudflare/worker.ts— Uses the image from the Container resource duringContainerApplication.alchemy/test/cloudflare/container.test.ts— Tests build/update/adoption via build flow.examples/cloudflare-container/*— Demonstrates only the build flow.alchemy/src/docker/remote-image.ts— Remote prebuilt image resource exists but is unused by Cloudflare container.Proposed Changes
ContainerPropsto accept a prebuilt image and allow skipping build/push.Proposed props (flat, backwards-compatible):
image?: string | RemoteImage— Prebuilt image reference (e.g.,<image name>:<tag>per current wrangler pattern).push?: boolean— Whenimageis provided and not in Cloudflare registry, optionally retag and push to Cloudflare. Defaultfalse.build-related props remain supported and unchanged.Behavior:
imageis provided:docker build.imageis astringwithout a registry host: assumeregistry.cloudflare.com/<acct>/<image>:<tag>.imagepoints to a non-Cloudflare registry:push: trueto retag and push into CF; otherwise, throw a clear validation error.push: falseand the image is already in CF registry: no push performed.imageis not provided:imageandbuildare not used together.API Examples
await Container("api", { name: "my-api", build: { context: "./", dockerfile: "Dockerfile" } });await Container("api", { name: "my-api", image: "my-api:1.2.3" });const base = await RemoteImage("base", { image: "ghcr.io/org/app:1.2.3" });await Container("api", { name: "my-api", image: base, push: true });Acceptance Criteria
image: "<name>:<tag>"(implicitly CF registry) deploys without building or pushing.image: RemoteImage(...)withpush: trueretags and pushes to CF registry, then deploys.imagereferences a non-CF registry andpush !== true, a clear validation error is thrown (since CF currently only accepts CF-hosted images).imageandbuild.Implementation Plan
alchemy/src/cloudflare/container.ts:ContainerPropswithimage?,push?imagehost is non-CF andpush !== true, throw validation error; ifpush: true, retag/push using existing Docker APIs.imageis string without a host.alchemy/src/cloudflare/worker.tsif needed to consume a consistentimageReffrom the Container output (should be backward-compatible).alchemy/test/cloudflare/container.test.ts.examples/cloudflare-containershowing prebuilt usage.alchemy-web/.../providers/cloudflare/container.mdto document both flows and registry nuances.Backward Compatibility
Constraints (Resolved)
Future-Proofing
Risks
imageis not present/accessible in CF; mitigated by validation and clear errors.References
alchemy/src/cloudflare/container.tsalchemy/src/docker/image.tsalchemy/src/docker/remote-image.tsalchemy/src/cloudflare/worker.ts