Skip to content

Cloudflare Containers: support prebuilt/static images (skip rebuilds) #1109

@adunne09

Description

@adunne09

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions