1import * as Containers from "@distilled.cloud/cloudflare/containers";
2import * as Redacted from "effect/Redacted";
3import type * as Bundle from "../../Bundle/Bundle.ts";
4import * as ProviderLayer from "../../Local/ProviderLayer.ts";
5import {
6 type Main,
7 type PlatformProps,
8 type PlatformServices,
9} from "../../Platform.ts";
10import { Resource } from "../../Resource.ts";
11import type { ProcessServices } from "../../Server/Process.ts";
12import type { Providers } from "../Providers.ts";
13import type { InlineDockerfile } from "../../Docker/Dockerfile.ts";
14import { ContainerTypeId } from "./Container.ts";
15import { LiveContainerProvider } from "./ContainerProvider.ts";
16import { LocalContainerProvider } from "./LocalContainerProvider.ts";
17
18export { Credentials } from "@distilled.cloud/cloudflare/Credentials";
19
20export namespace ContainerApplication {
21 export type InstanceType = NonNullable<
22 Containers.CreateContainerApplicationRequest["configuration"]["instanceType"]
23 >;
24 export type SchedulingPolicy = NonNullable<
25 Containers.CreateContainerApplicationRequest["schedulingPolicy"]
26 >;
27 export type Observability = NonNullable<
28 Containers.CreateContainerApplicationRequest["configuration"]["observability"]
29 >;
30 export type Secret = NonNullable<
31 Containers.CreateContainerApplicationRequest["configuration"]["secrets"]
32 >[number];
33 export type Disk = NonNullable<
34 Containers.CreateContainerApplicationRequest["configuration"]["disk"]
35 >;
36 export type EnvironmentVariable = NonNullable<
37 Containers.CreateContainerApplicationRequest["configuration"]["environmentVariables"]
38 >[number];
39 export type Label = NonNullable<
40 Containers.CreateContainerApplicationRequest["configuration"]["labels"]
41 >[number];
42 export type Network = NonNullable<
43 Containers.CreateContainerApplicationRequest["configuration"]["network"]
44 >;
45 export type Dns = NonNullable<
46 Containers.CreateContainerApplicationRequest["configuration"]["dns"]
47 >;
48 export type Port = NonNullable<
49 Containers.CreateContainerApplicationRequest["configuration"]["ports"]
50 >[number];
51 export type Check = NonNullable<
52 Containers.CreateContainerApplicationRequest["configuration"]["checks"]
53 >[number];
54 export type Constraints = {
55 tier?: number;
56 };
57 export type Affinities = {
58 colocation?: "datacenter";
59 };
60 export type Configuration =
61 Containers.CreateContainerApplicationRequest["configuration"];
62 export interface Rollout {
63 strategy?: "rolling" | "immediate";
64 kind?: "full_auto";
65 stepPercentage?: number;
66 }
67}
68
69/**
70 * Configuration shared by every container image source: application naming,
71 * scaling, placement, runtime environment, and rollout settings. The image
72 * source itself is declared by the variant interfaces
73 * ({@link EffectfulContainerProps} / {@link ExternalContainerProps} /
74 * {@link RemoteContainerProps}) that extend this base.
75 */
76export interface ContainerApplicationPropsBase extends PlatformProps {
77 /**
78 * Human-readable application name. If omitted, Alchemy derives a deterministic
79 * physical name from the stack, stage, and logical ID.
80 */
81 name?: string;
82 /**
83 * Name of the exported Durable Object class this container backs when the
84 * Container is bound on an **async** Worker's `env`. Defaults to the
85 * binding name (the `env` key). Ignored by the Effect-native path, where
86 * the class name comes from the hosting Durable Object.
87 */
88 className?: string;
89 /**
90 * Initial number of instances to maintain. Matches wrangler, which forces
91 * this to 0 whenever {@link maxInstances} is set (pure scale-from-zero).
92 * @default 0
93 */
94 instances?: number;
95 /**
96 * Maximum number of instances the application may scale to. Matches
97 * wrangler's default of 20. A value of 1 serializes every Durable Object
98 * instance through a single container slot, so the default lets containers
99 * scale concurrently out of the box.
100 * @default 20
101 */
102 maxInstances?: number;
103 /**
104 * Scheduling policy used by Cloudflare's containers control plane.
105 * @default "default"
106 */
107 schedulingPolicy?: ContainerApplication.SchedulingPolicy;
108 /**
109 * Instance type for each deployment. Defaults to wrangler's `"lite"` tier
110 * (1/16 vCPU, 256 MiB, 2 GB disk) when no explicit {@link vcpu}/{@link memory}/
111 * {@link memoryMib}/{@link disk} is set. (`"dev"` is wrangler's deprecated
112 * alias for `"lite"`.)
113 * @default "lite"
114 */
115 instanceType?: ContainerApplication.InstanceType;
116 /**
117 * Observability settings for the deployment.
118 */
119 observability?: ContainerApplication.Observability;
120 /**
121 * SSH public keys to install into the deployment.
122 */
123 sshPublicKeyIds?: string[];
124 /**
125 * Secrets exposed to the container runtime as environment variables.
126 */
127 secrets?: ContainerApplication.Secret[];
128 /**
129 * CPU allocation override for each deployment.
130 */
131 vcpu?: number;
132 /**
133 * Memory allocation override for each deployment.
134 */
135 memory?: string;
136 /**
137 * Memory allocation override for each deployment, in MiB.
138 * Custom sizing requires at least 1 {@link vcpu} and 3072 MiB of memory
139 * per vCPU for the first 4 vCPUs.
140 */
141 memoryMib?: number;
142 /**
143 * Disk allocation override for each deployment.
144 */
145 disk?: ContainerApplication.Disk;
146 /**
147 * Plain environment variables passed to the container runtime.
148 */
149 environmentVariables?: ContainerApplication.EnvironmentVariable[];
150 /**
151 * Labels attached to the deployment.
152 */
153 labels?: ContainerApplication.Label[];
154 /**
155 * Network configuration for the deployment.
156 */
157 network?: ContainerApplication.Network;
158 /**
159 * Command override for the container image.
160 */
161 command?: string[];
162 /**
163 * Entrypoint override for the container image.
164 */
165 entrypoint?: string[];
166 /**
167 * DNS configuration for the deployment.
168 */
169 dns?: ContainerApplication.Dns;
170 /**
171 * Exposed ports for the deployment.
172 */
173 ports?: ContainerApplication.Port[];
174 /**
175 * Health and readiness checks for the deployment.
176 */
177 checks?: ContainerApplication.Check[];
178 /**
179 * Resource constraints for the application.
180 */
181 constraints?: ContainerApplication.Constraints;
182 /**
183 * Affinity hints for scheduling.
184 */
185 affinities?: ContainerApplication.Affinities;
186 /**
187 * Progressive rollout settings applied after updates.
188 */
189 rollout?: ContainerApplication.Rollout;
190 /**
191 * Container registry host to use for generated Dockerfile builds.
192 * @default "registry.cloudflare.com"
193 */
194 registryId?: string;
195 /**
196 * Image publication configuration. Builds are cached by default in a
197 * repository named after the application's physical name. Generated names
198 * include the stage and resource instance, so updates within that stage can
199 * reuse images. Replacement or destroy/recreate can select a new repository.
200 * Set `repository` to share finished images and build layers across stages.
201 *
202 * @example
203 * ```typescript
204 * // Default: cached in this application's generated repository.
205 * const app = yield* Cloudflare.Container("Web", { context: "./web" }).Application;
206 * // Repository: registry.cloudflare.com/<account-id>/<app.applicationName>
207 * ```
208 */
209 publish?: {
210 /**
211 * Destination repository name within the current Cloudflare account's
212 * container registry, for example `"web"`. This is not a source image,
213 * registry hostname, account ID, tag, or fully qualified image reference.
214 * Alchemy lowercases the name and adds the registry host and account ID:
215 * `registry.cloudflare.com/<account-id>/web` with the default `registryId`.
216 * The container application's name is independent of this repository name.
217 * Omitting `publish` uses the application's physical name instead and still
218 * enables caching. Generated names are scoped to the stage and resource
219 * instance; an explicit repository allows cross-stage reuse.
220 *
221 * For Dockerfile and Effect-native builds, Alchemy publishes a content-hash
222 * tag and an inline layer-cache tag, then deploys an immutable manifest
223 * digest. The build hash and manifest digest are different identifiers.
224 * Builds targeting this repository import reusable layers from its shared
225 * `:buildcache` tag, even when their full input hashes differ. The tag points
226 * to the latest exported inline cache, not an aggregate of all historical
227 * images. A finished-image cache hit leaves this layer-cache tag unchanged.
228 *
229 * Applications and stages in the same account can share `"web"`. Matching
230 * build inputs reuse the published image; changed inputs produce another
231 * hash tag in the same repository. Finished-image cache reuse applies to
232 * builds, not to re-publishing remote images. Pin base images and downloaded
233 * dependencies: changes outside the build context cannot invalidate its
234 * content hash.
235 *
236 * External remote images are re-published into this repository without
237 * building them. Images already in the target registry keep their existing
238 * repository; setting this option does not copy them into another one.
239 *
240 * @example
241 * ```typescript
242 * const app = yield* Cloudflare.Container("WebProduction", {
243 * context: "./web",
244 * publish: { repository: "web" },
245 * }).Application;
246 *
247 * // Published build:
248 * // registry.cloudflare.com/<account-id>/web:<build-hash>
249 * // Shared build-layer cache:
250 * // registry.cloudflare.com/<account-id>/web:buildcache
251 * // Deployed image (app.configuration.image):
252 * // registry.cloudflare.com/<account-id>/web@sha256:<manifest-digest>
253 * ```
254 */
255 repository: string;
256 };
257 /**
258 * Environment variables passed to the container runtime.
259 */
260 env?: Record<string, any>;
261 /**
262 * Exports passed to the container runtime.
263 */
264 exports?: string[];
265}
266
267/**
268 * Bundle an Effect-native program into a generated image. Alchemy bundles
269 * {@link main} and bakes it in as the container's entrypoint. The
270 * environment the program runs in comes from {@link image} or an inline
271 * {@link dockerfile} (exclusive with each other), defaulting to the
272 * runtime's base image.
273 */
274export interface EffectfulContainerProps extends ContainerApplicationPropsBase {
275 /** Entrypoint file for the Effect program, typically `import.meta.url`. */
276 main: string;
277 /**
278 * Environment image for the generated Dockerfile — a plain registry
279 * reference, e.g. `"oven/bun:latest"`. Alchemy synthesizes the `FROM` line
280 * and appends the statements that copy the bundled program and set the
281 * entrypoint. The image must be able to run the {@link runtime}.
282 * Exclusive with {@link dockerfile}.
283 *
284 * @default `oven/bun:1` for `runtime: "bun"`, `node:22-slim` for `runtime: "node"`
285 */
286 image?: string;
287 /**
288 * Inline environment Dockerfile content (typically `Dockerfile.inline`).
289 * Replaces the generated `FROM` line — carry your own `FROM` plus any
290 * extra build steps (system packages, config); the bundled program is
291 * layered on top. Exclusive with {@link image}. Never interpolate secrets
292 * into inline content — it is baked into image layers.
293 */
294 dockerfile?: InlineDockerfile;
295 /**
296 * Exported handler symbol inside the bundled module.
297 * @default "default"
298 */
299 handler?: string;
300 /**
301 * Runtime environment for the container program.
302 *
303 * @default "bun"
304 */
305 runtime?: "bun" | "node";
306 /**
307 * Module specifiers that Rolldown should mark as external when bundling
308 * the container entrypoint. The matching packages are installed inside the
309 * image via the runtime's package manager (`bun add` for `runtime: "bun"`,
310 * `npm install` for `runtime: "node"`) before the entrypoint runs.
311 *
312 * Use this for native dependencies that must not be bundled (e.g. `sharp`,
313 * `impit`) or for packages that intentionally ship in the base image.
314 *
315 * Install inside the image is controlled by {@link autoInstallExternals}
316 * (default `true`); set it to `false` if your environment ({@link image}
317 * or inline {@link dockerfile}) already ships these packages and you want
318 * to avoid the redundant step.
319 */
320 external?: string[];
321 /**
322 * Whether to auto-install the packages listed in {@link external} inside
323 * the container image (via `bun add` or `npm install`) before running the
324 * entrypoint.
325 *
326 * @default true
327 *
328 * Set to `false` when your environment image already ships these packages
329 * (for example, a base image that pre-installs `sharp`), to avoid the
330 * redundant install step.
331 */
332 autoInstallExternals?: boolean;
333 /**
334 * Bundler configuration for {@link main}. Unused code is tree-shaken.
335 * `effect`, alchemy, and `@distilled.cloud` are marked pure so unused
336 * parts prune more aggressively. List extra packages with
337 * `pure.packages`, or disable with `pure: false`.
338 */
339 build?: Bundle.BundleConfig;
340}
341
342/**
343 * Build the container image from your own Dockerfile — no Effect program is
344 * bundled. The image is shipped as-is.
345 */
346export interface ExternalContainerProps extends ContainerApplicationPropsBase {
347 /**
348 * The build context directory containing the Dockerfile and any files it
349 * copies. Only valid with a `dockerfile` PATH (not inline content).
350 *
351 * @default `./`
352 */
353 context?: string;
354 /**
355 * The Dockerfile to build. A string is a **path** resolved relative to
356 * {@link context} (default `<context>/Dockerfile`); an
357 * {@link InlineDockerfile} (typically `Dockerfile.inline`) is the whole
358 * Dockerfile's content, built in an empty generated context (exclusive
359 * with {@link context}).
360 */
361 dockerfile?: string | InlineDockerfile;
362}
363
364/**
365 * Deploy a pre-built remote image — Alchemy pulls it and re-pushes it to
366 * Cloudflare's managed registry without building anything.
367 */
368export interface RemoteContainerProps extends ContainerApplicationPropsBase {
369 /**
370 * The pre-built image to pull and re-push.
371 *
372 * E.g. `ghcr.io/alpine/alpine:latest`
373 *
374 * When the reference already points at the target registry (the
375 * {@link ContainerApplicationPropsBase.registryId | registryId} host,
376 * `registry.cloudflare.com` by default) — e.g. a digest reference pushed
377 * by CI like `registry.cloudflare.com/<accountId>/app@sha256:...` — it is
378 * deployed as-is and the docker pull/push round-trip is skipped entirely.
379 */
380 image: string;
381}
382
383/**
384 * Container application props — the image comes from exactly one of three
385 * sources, declared flat on the props: `main` (bundled Effect program,
386 * composing with `image` / inline `dockerfile` as its environment),
387 * `context`/`dockerfile` (user Dockerfile), or `image` (pre-built remote
388 * image).
389 */
390export type ContainerApplicationProps =
391 | EffectfulContainerProps
392 | ExternalContainerProps
393 | RemoteContainerProps;
394
395/**
396 * INTERNAL — the loose provider-side view across the three variants: every
397 * variant-specific field optional at its widest type. Each union member is
398 * assignable to this shape, so provider/bundle code annotates helper params
399 * with it instead of narrowing the union at every property access.
400 */
401export interface AnyContainerApplicationProps extends ContainerApplicationPropsBase {
402 main?: string;
403 image?: string;
404 context?: string;
405 dockerfile?: string | InlineDockerfile;
406 handler?: string;
407 runtime?: "bun" | "node";
408 external?: string[];
409 autoInstallExternals?: boolean;
410 build?: Bundle.BundleConfig;
411}
412
413export type ContainerServices =
414 | ContainerApplication
415 | PlatformServices
416 | ProcessServices;
417
418export type ContainerShape = Main<ContainerServices>;
419
420/**
421 * A Cloudflare Container Application — the deployed, scalable unit that runs a
422 * containerized program on Cloudflare's compute platform. Alchemy bundles the
423 * `main` entrypoint, builds a Docker image, pushes it to the Cloudflare
424 * registry, and reconciles the application's scaling and runtime configuration.
425 *
426 * This is the lower-level resource backing the {@link Container} platform
427 * binding; in application code you typically extend `Cloudflare.Container` to
428 * define and bind a container to a Durable Object rather than referencing this
429 * resource directly. The same props shape (`main`, `instanceType`, `instances`,
430 * etc.) is accepted by the `Cloudflare.Container(...)` class form shown below.
431 *
432 * @internal
433 * ### Defining a Container Application
434 * Point `main` at the container's entrypoint file; Alchemy bundles it and uses
435 * it as the image's entrypoint. The application name is derived deterministically
436 * from the stack, stage, and logical ID unless you set an explicit `name`, and
437 * `handler` selects which export to run when it isn't the default.
438 *
439 * **Example:** Minimal container
440 * ```typescript
441 * import * as Cloudflare from "alchemy/Cloudflare";
442 *
443 * export class Sandbox extends Cloudflare.Container<Sandbox>()("Sandbox", {
444 * main: import.meta.url,
445 * }) {}
446 * ```
447 *
448 * The single `main` prop is enough to ship a container: Alchemy bundles the
449 * entrypoint, builds and pushes the image, and provisions an application with
450 * one instance. Reach for the other props only when you need to scale, expose
451 * ports, or customize the build.
452 *
453 * **Example:** Named container with a non-default handler export
454 * ```typescript
455 * export class Worker extends Cloudflare.Container<Worker>()("Worker", {
456 * main: import.meta.url,
457 * handler: "runWorker",
458 * name: "background-worker",
459 * }) {}
460 * ```
461 *
462 * `name` pins a stable application name (instead of the generated one), which is
463 * useful for adopting an existing application, while `handler` runs the named
464 * `runWorker` export rather than the module's default.
465 *
466 * ### Image Sources
467 * The image is resolved from exactly one of three props, checked in order:
468 * `main` (bundle an Effect program into a generated image), then `image`
469 * (pull and re-push a remote image), then `context` / `dockerfile` (build
470 * your own Dockerfile). Only `main` injects an Effect runtime; the other two
471 * ship an arbitrary image unchanged.
472 *
473 * **Example:** Build your own Dockerfile (`context` / `dockerfile`)
474 * ```typescript
475 * export class Web extends Cloudflare.Container<Web>()("Web", {
476 * context: `${import.meta.dirname}/context`,
477 * dockerfile: "Dockerfile",
478 * }) {}
479 * ```
480 *
481 * Alchemy builds `dockerfile` against the `context` directory with no `main`
482 * bundling. `dockerfile` is resolved relative to `context` and defaults to
483 * `<context>/Dockerfile`.
484 *
485 * **Example:** Remote image (`image`)
486 * ```typescript
487 * export class Echo extends Cloudflare.Container<Echo>()("Echo", {
488 * image: "mendhak/http-https-echo:latest",
489 * }) {}
490 * ```
491 *
492 * Alchemy pulls the pre-built public image and re-pushes it to Cloudflare's
493 * managed registry instead of building anything.
494 *
495 * ### Bundling & Dependencies
496 * By default the entrypoint is bundled for the `bun` runtime. Use `runtime` to
497 * switch to Node, `external` to keep native/precompiled packages out of the
498 * bundle (auto-installed in the image unless `autoInstallExternals` is `false`),
499 * `image` (or an inline `dockerfile`) to pick the environment the generated
500 * Dockerfile starts `FROM`, and `registryId` to override the registry host.
501 *
502 * **Example:** Node runtime with external native deps
503 * ```typescript
504 * export class ImageApi extends Cloudflare.Container<ImageApi>()("ImageApi", {
505 * main: import.meta.url,
506 * runtime: "node",
507 * external: ["sharp"],
508 * autoInstallExternals: true,
509 * }) {}
510 * ```
511 *
512 * Marking `sharp` as `external` stops Rolldown from bundling the native module;
513 * because `autoInstallExternals` is `true`, Alchemy runs `npm install sharp`
514 * inside the image so the dependency is present at runtime.
515 *
516 * **Example:** Custom environment image and registry
517 * ```typescript
518 * export class Custom extends Cloudflare.Container<Custom>()("Custom", {
519 * main: import.meta.url,
520 * image: "oven/bun:1",
521 * autoInstallExternals: false,
522 * registryId: "registry.cloudflare.com",
523 * }) {}
524 * ```
525 *
526 * Alchemy generates the Dockerfile — `FROM` your `image`, then the
527 * program-copy and entrypoint steps — so you control the starting image;
528 * `autoInstallExternals: false` skips the redundant install step when the
529 * environment already ships your `external` packages.
530 *
531 * **Example:** Inline environment Dockerfile (extra build steps)
532 * ```typescript
533 * import * as Dockerfile from "alchemy/Docker/Dockerfile";
534 *
535 * export class Transcoder extends Cloudflare.Container<Transcoder>()(
536 * "Transcoder",
537 * {
538 * main: import.meta.url,
539 * dockerfile: Dockerfile.inline`
540 * FROM oven/bun:1
541 * RUN apt-get update && apt-get install -y ffmpeg
542 * `,
543 * },
544 * ) {}
545 * ```
546 *
547 * Inline `dockerfile` content replaces the generated `FROM` line, so the
548 * environment can run extra build steps (system packages, config) while the
549 * bundled program is still layered on top.
550 *
551 * ### Bundling & Tree-shaking
552 * `main` is bundled with rolldown at deploy time. Unused code is
553 * tree-shaken. `effect`, alchemy, and `@distilled.cloud` are marked
554 * pure so unused parts prune more aggressively. Your app is not
555 * marked pure.
556 *
557 * **Example:** Mark additional packages as pure
558 * Only list packages with no top-level side effects.
559 * ```typescript
560 * {
561 * main: import.meta.url,
562 * build: {
563 * pure: { packages: ["my-lib", "@my-scope/*"] },
564 * },
565 * }
566 * ```
567 *
568 * **Example:** Turn it off
569 * ```typescript
570 * {
571 * main: import.meta.url,
572 * build: { pure: false },
573 * }
574 * ```
575 *
576 * ### Scaling & Instance Types
577 * Control the desired and maximum instance counts with `instances`/`maxInstances`
578 * and pick a compute size with `instanceType`. For finer control, override
579 * `vcpu`, `memory`, and `disk` directly.
580 *
581 * **Example:** Autoscaling with a larger instance type
582 * ```typescript
583 * export class Sandbox extends Cloudflare.Container<Sandbox>()("Sandbox", {
584 * main: import.meta.url,
585 * instanceType: "standard-1",
586 * instances: 1,
587 * maxInstances: 5,
588 * }) {}
589 * ```
590 *
591 * The application keeps one instance running and may scale out to five under
592 * load, each on the `standard-1` size. Use a larger `instanceType` (or the
593 * explicit overrides below) when the default `dev` size is too small.
594 *
595 * **Example:** Explicit CPU, memory, and disk overrides
596 * ```typescript
597 * export class Heavy extends Cloudflare.Container<Heavy>()("Heavy", {
598 * main: import.meta.url,
599 * vcpu: 2,
600 * memory: "4GB",
601 * disk: { size: "10GB" },
602 * }) {}
603 * ```
604 *
605 * These props override the per-instance resource allocation independently of
606 * `instanceType`, which is handy when a workload needs, say, extra disk for
607 * scratch space without bumping every other dimension.
608 *
609 * ### Runtime Configuration
610 * Inject configuration with `environmentVariables` (plain values) and `secrets`
611 * (references to stored secrets), and override the image's `command` or
612 * `entrypoint`. `labels` attach metadata to the deployment.
613 *
614 * **Example:** Environment variables, secrets, and a command override
615 * ```typescript
616 * export class Api extends Cloudflare.Container<Api>()("Api", {
617 * main: import.meta.url,
618 * environmentVariables: [{ name: "LOG_LEVEL", value: "info" }],
619 * secrets: [{ name: "API_KEY", type: "env", secret: "my-stored-secret" }],
620 * command: ["bun", "run", "start"],
621 * labels: [{ name: "team", value: "payments" }],
622 * }) {}
623 * ```
624 *
625 * `environmentVariables` are visible plain values, while `secrets` map a stored
626 * secret into the runtime as an env var without exposing it in config; `command`
627 * overrides the container's startup command and `labels` tag the deployment for
628 * organization.
629 *
630 * **Example:** Passing env and selecting runtime exports
631 * ```typescript
632 * export class Job extends Cloudflare.Container<Job>()("Job", {
633 * main: import.meta.url,
634 * env: { REGION: "wnam", FEATURE_FLAG: "on" },
635 * exports: ["default"],
636 * }) {}
637 * ```
638 *
639 * `env` injects values into the bundled program's runtime context (as opposed to
640 * the deployment-level `environmentVariables`), and `exports` declares which
641 * symbols from the entrypoint module the runtime should wire up.
642 *
643 * ### Networking & Health Checks
644 * Configure outbound/inbound networking with `network` and `dns`, expose
645 * `ports`, and gate readiness with `checks`.
646 *
647 * **Example:** Ports, network mode, DNS, and a health check
648 * ```typescript
649 * export class Web extends Cloudflare.Container<Web>()("Web", {
650 * main: import.meta.url,
651 * ports: [{ name: "http", port: 8080 }],
652 * network: { assignIpv4: "predefined", mode: "public" },
653 * dns: { servers: ["1.1.1.1"], searches: ["internal"] },
654 * checks: [{ name: "ready", type: "http", port: "8080", tls: false }],
655 * }) {}
656 * ```
657 *
658 * `ports` publishes the named port the program listens on, `network` controls IP
659 * assignment and public/private reachability, `dns` overrides resolver settings,
660 * and `checks` tells Cloudflare how to probe the container before routing
661 * traffic to it.
662 *
663 * ### Observability & Access
664 * Turn on log shipping with `observability` and install `sshPublicKeyIds` for
665 * interactive access to running instances.
666 *
667 * **Example:** Enable logs and grant SSH access
668 * ```typescript
669 * export class Api extends Cloudflare.Container<Api>()("Api", {
670 * main: import.meta.url,
671 * observability: { logs: { enabled: true } },
672 * sshPublicKeyIds: ["ssh-key-id-123"],
673 * }) {}
674 * ```
675 *
676 * `observability.logs.enabled` streams the container's logs into Cloudflare's
677 * telemetry pipeline (queryable via the resource's `logs`/`tail` operations),
678 * and `sshPublicKeyIds` authorizes the listed keys to connect to instances for
679 * debugging.
680 *
681 * ### Scheduling & Placement
682 * Influence where and how Cloudflare schedules instances with `schedulingPolicy`,
683 * `constraints`, and `affinities`.
684 *
685 * **Example:** Pin scheduling policy and placement
686 * ```typescript
687 * export class Edge extends Cloudflare.Container<Edge>()("Edge", {
688 * main: import.meta.url,
689 * schedulingPolicy: "regional",
690 * constraints: { tier: 1 },
691 * affinities: { colocation: "datacenter" },
692 * }) {}
693 * ```
694 *
695 * `schedulingPolicy` selects the control-plane placement strategy,
696 * `constraints.tier` restricts which capacity tier instances may land on, and
697 * `affinities.colocation` keeps related instances in the same datacenter to
698 * reduce inter-instance latency.
699 *
700 * ### Rollouts
701 * When an update changes the configuration, `rollout` controls how the new
702 * version is rolled out across instances.
703 *
704 * **Example:** Progressive rollout on update
705 * ```typescript
706 * export class Api extends Cloudflare.Container<Api>()("Api", {
707 * main: import.meta.url,
708 * instances: 4,
709 * maxInstances: 4,
710 * rollout: { strategy: "rolling", stepPercentage: 25 },
711 * }) {}
712 * ```
713 *
714 * A `rolling` strategy with `stepPercentage: 25` replaces instances in 25%
715 * increments so the application stays available during the update; the default
716 * `immediate` strategy swaps everything at once. Steps advance automatically
717 * as new instances become healthy; each replaced instance receives `SIGTERM`
718 * and has 15 minutes to shut down cleanly before `SIGKILL`.
719 *
720 * Rollouts replace instances — they do not split requests between two image
721 * versions (request-level traffic splitting exists one layer up, on the
722 * Worker, via `version.traffic`). The fronting Worker and Durable Object cut
723 * over immediately while instances roll, so keep the Worker-to-container
724 * protocol compatible across both image versions until a rollout completes.
725 *
726 * @resource
727 * @product Containers
728 * @category Workers & Compute
729 */
730export interface ContainerApplication<Shape = unknown> extends Resource<
731 ContainerTypeId,
732 ContainerApplicationProps,
733 {
734 /**
735 * Cloudflare-assigned unique identifier of the container application.
736 */
737 applicationId: string;
738 /**
739 * The resolved application name (either the provided `name` or the
740 * deterministic physical name derived from the stack, stage, and logical ID).
741 */
742 applicationName: string;
743 /**
744 * The Cloudflare account ID that owns the application.
745 */
746 accountId: string;
747 /**
748 * The scheduling policy in effect for the application's deployments.
749 */
750 schedulingPolicy: ContainerApplication.SchedulingPolicy;
751 /**
752 * The current desired number of instances.
753 */
754 instances: number;
755 /**
756 * The maximum number of instances the application may scale to.
757 */
758 maxInstances: number;
759 /**
760 * Resource constraints applied to the application, if any.
761 */
762 constraints: ContainerApplication.Constraints | undefined;
763 /**
764 * Scheduling affinity hints applied to the application, if any.
765 */
766 affinities: ContainerApplication.Affinities | undefined;
767 /**
768 * The resolved deployment configuration (image, networking, secrets, ports,
769 * checks, etc.) currently applied to the application.
770 */
771 configuration: ContainerApplication.Configuration;
772 /**
773 * The Durable Object namespace attached to the application, if it is bound
774 * to one.
775 */
776 durableObjects:
777 | {
778 namespaceId: string;
779 }
780 | undefined;
781 /**
782 * ISO-8601 timestamp of when the application was created.
783 */
784 createdAt: string;
785 /**
786 * The application's configuration version, incremented on each update.
787 */
788 version: number;
789 /**
790 * Internal hashes of the built image and desired application
791 * configuration, used to skip unchanged builds and updates.
792 */
793 hash?: {
794 image: string;
795 digest?: string;
796 configuration?: string;
797 };
798 dev: DevContainerImage | undefined;
799 },
800 {
801 /**
802 * Durable Object namespace attached to the container application.
803 */
804 durableObjects?: {
805 namespaceId: string;
806 };
807 /**
808 * Environment variables injected into the container runtime via the binding.
809 */
810 env?: Record<string, any>;
811 },
812 Providers
813> {
814 /** @internal phantom */
815 Shape: Shape;
816}
817
818// `DevContainerImage` stays here (consumed by the Attrs `dev` field above and
819// imported by Worker.ts). The provider helpers that previously lived here
820// (resolveDurableObjectApplicationRecovery, the readiness schedule/retry, etc.)
821// were extracted to ContainerProvider.ts on this branch.
822export type DevContainerImage =
823 | DevContainerImage.Build
824 | DevContainerImage.Pull
825 | DevContainerImage.Ref;
826
827export declare namespace DevContainerImage {
828 interface Base {
829 readonly env?: Record<string, string | Redacted.Redacted<string>>;
830 }
831 export interface Build extends Base {
832 readonly dockerfile: string;
833 readonly context?: string;
834 readonly buildArgs?: Record<string, string>;
835 }
836 export interface Pull extends Base {
837 readonly imageUri: string;
838 }
839 export interface Ref extends Base {
840 readonly tag: string;
841 }
842}
843
844export const ContainerProvider = () =>
845 // `{ Type }` instead of `ContainerPlatform` — importing the platform here
846 // would create a module cycle (ContainerPlatform.ts imports this file).
847 ProviderLayer.dual(
848 { Type: ContainerTypeId },
849 {
850 live: () => LiveContainerProvider(),
851 local: () => LocalContainerProvider(),
852 },
853 );