Skip to content

2.0.0-beta.60 - Docker, MicroVMs & RSC

beta.60 is a big one: a new Docker provider, AWS Lambda MicroVMs, Workers for Platforms, React Server Components on the Vite pipeline, class-based Workflows on async Workers, Cloudflare Containers that start fast and stay up, and a ground-up overhaul of the docs. No breaking changes this release.

alchemy/Docker brings Image, RemoteImage, Container, Volume, and Network as Stack resources (#649). They drive the docker CLI’s active context — Docker Desktop, a remote/SSH context, a CI daemon — and live in the same Stack as your cloud resources. Thanks Austin!

const image = yield* Docker.RemoteImage("postgres-image", {
name: "postgres",
tag: "18-alpine",
});
const network = yield* Docker.Network("app-network");
const data = yield* Docker.Volume("postgres-data");
const postgres = yield* Docker.Container("postgres", {
image,
environment: { POSTGRES_PASSWORD: password }, // Redacted-safe
ports: [{ external: 15432, internal: 5432 }],
volumes: [{ hostPath: data.name, containerPath: "/var/lib/postgresql/data" }],
networks: [{ name: network.name, aliases: ["postgres"] }],
start: true,
});

Secrets are passed via process env (never on the CLI), pulled tags are pinned so re-deploys are no-ops, and containers created outside alchemy follow the standard adoption rules. See the new Docker hub and the local services guide.

AWS.Lambda.MicrovmImage is a new Platform for snapshot-booted microVMs (#712). Author the in-VM server in TypeScript — alchemy bundles it, generates the Dockerfile, and builds the snapshot server-side — or bring your own Dockerfile or a prebuilt artifact:

export class Sandbox extends AWS.Lambda.MicrovmImage<
Sandbox,
{ hello: (message: string) => Effect.Effect<string> }
>()("Sandbox") {}
export default Sandbox.make(
{ main: import.meta.filename, buildRole },
Effect.gen(function* () {
return {
hello: (message) => Effect.succeed(`hello, ${message}!`),
fetch: Effect.gen(function* () { /* raw HTTP route */ }),
};
}),
);

Every instance operation — RunMicrovm, GetMicrovm, suspend/resume/terminate, auth tokens, image builds — is a Binding.Service with least-privilege IAM scoping, and connectMicrovm gives you typed RPC into the running VM:

const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, { endpoint, authToken });
const reply = yield* sandbox.hello("world");

It works cross-cloud too: binding a MicroVM operation from a Cloudflare.Worker mints an IAM user + assume-role Role so the Worker can drive AWS MicroVMs at runtime.

We also benchmarked MicroVMs against Cloudflare Containers — 700 isolated cold boots. MicroVMs boot in 2–4.5s regardless of what’s inside; full results in Benchmarking Cloudflare Containers vs AWS MicroVMs. Docs: MicroVMs.

Deploy user Workers into a dispatch namespace and route to them dynamically (#715). A Worker deploys into a namespace with the new namespace prop — no duplicate resource type:

const ns = yield* Cloudflare.WorkersForPlatforms.DispatchNamespace("Customers", {});
const userWorker = yield* Cloudflare.Worker("CustomerA", {
namespace: ns.name,
script: `export default { fetch() { return new Response("hi") } }`,
});

The platform Worker binds the namespace Effect-natively, or via env for async Workers:

// Effect-native — yield the binding, get a typed client
const dispatch = yield* Cloudflare.WorkersForPlatforms.Get(namespace);
const userWorker = yield* dispatch.get("customer-a");
// async — DispatchNamespace is a valid env binding
const platform = Cloudflare.Worker("Platform", {
main: "./handler.ts",
env: { DISPATCH: namespace },
});
// handler.ts: env.DISPATCH.get("customer-a").fetch(request)

Docs: Workers for Platforms.

Cloudflare.Vite now supports builds that emit more than one server environment — most notably RSC (#685). viteEnvironments selects which environment produces the deployed Worker entry and which additional server environments are bundled alongside it:

const app = yield* Cloudflare.Vite("ReactRouterRSC", {
compatibility: { flags: ["nodejs_compat"] },
viteEnvironments: { entry: "rsc", children: ["ssr"] },
});

A single-environment SSR build needs no configuration — the default is { entry: "ssr", children: [] } — and the client environment always deploys as static assets. The same PR replaces the dev-server restart fingerprint with an exact structural signature, so config changes are never silently missed. Docs: Frontends on Cloudflare.

Class-based Cloudflare Workflows now bind to async (non-Effect) Workers via env, mirroring class-based Durable Objects (#707):

export const AsyncWorker = Cloudflare.Worker("Async", {
main: "./src/worker.ts",
env: {
MY_WORKFLOW: Cloudflare.Workflow<{ value: string }>("MyWorkflow", {
className: "MyWorkflow",
}),
},
});
export type Env = Cloudflare.InferEnv<typeof AsyncWorker>;
// env.MY_WORKFLOW is the native Workflow<{ value: string }>

Your WorkflowEntrypoint class stays plain cloudflare:workers code; alchemy provisions the workflow and emits the binding. Cross-script references work with scriptName, same as async DOs. Docs: Workflows.

Cloudflare Containers deployed with alchemy behaved much worse than the same image deployed with wrangler — at 100 concurrent cold starts, ~2/100 succeeded. Two root causes, both fixed (#708):

  • Wrangler-parity defaultsmaxInstances defaulted to 1, serializing every Durable Object through one container slot. Now maxInstances: 20, scale-from-zero, and instanceType: "lite" when no custom limits are set, matching wrangler.
  • Readiness polling — probes had no per-probe timeout, so a not-yet-listening port could hang for minutes. The loop now mirrors @cloudflare/containers exactly.

Result: 100/100 cold starts, on par with wrangler. And a container that stops or crashes is now transparently restarted on the next request, while a crash-looping container fails fast instead of burning the readiness budget (#711). Docs: Containers.

The ServerHost + { fetch } pattern now works end to end on AWS.ECS.Task (#713): the container entry bundles a Bun HTTP server, ships every chunk into the image, and builds for the architecture the task declares — verified by a smoke test that deploys a real Fargate service behind an ALB.

The hosted AWS.EC2.Instance serves HTTP the same way, with a reboot-safe systemd unit that self-heals transient boot failures (#714). And there’s a new AWS.EC2.KeyPair resource — the generated private key is captured once as a Redacted secret:

const keyPair = yield* AWS.EC2.KeyPair("DeployKey", { keyType: "ed25519" });
// keyPair.keyName -> AWS.EC2.Instance({ keyName })
// keyPair.privateKey -> Redacted<string>

The website is restructured into per-provider hubs behind a horizontal tab bar — Core · CLI · Cloudflare · AWS · PlanetScale · Neon · More ▾ — with ~100 new or reworked pages (#721):

  • Core — Infrastructure as Code, Infrastructure as Effects, APIs, Environments, State Store, Project structure, Testing.
  • CLI — every command documented, including the first-ever docs for alchemy unsafe nuke and adopting resources.
  • Cloudflare / AWS hubs — setup, five-part tutorials, block pages and guides grouped by role (Compute, Frontend, APIs, Data, Messaging, AI, …).
  • Every page written against source, tests, and fixtures; every moved URL 301s.

Start at the docs and pick your provider.

  • --yes is fully non-interactive (#728) — alchemy deploy --yes now auto-accepts the Cloudflare state store upgrade and deploy prompts instead of hanging on a non-TTY, so CI needs no workarounds.
  • Partial state-store deploys are detected and recovered (#700) — the store health check now verifies a real store is serving, not just that a pre-create stub exists.
  • Command.Build memo includes command + env (#738) — two StaticSite builds differing only in env (per-stage EXPO_PUBLIC_*, API ids) no longer reuse each other’s output.
  • Retained resources report as retained (#739) — destroy no longer lists RemovalPolicy: "retain" resources as deleted. Thanks bjorntechTobbe!
  • The CLI works on Windows (#696) — the stack entry loads via a file:// URL. Thanks d3lay!
  • Durable Object classes created outside alchemy adopt cleanly (#495) — class migrations fall back to matching the observed binding, so adopting a wrangler/dashboard Worker reuses its DO classes instead of failing to re-create them.
  • Access Application survives state loss (#742) — read falls back to a domain scan and gates takeover behind --adopt, instead of blindly creating a duplicate app with a fresh aud. Thanks Andy Jefferson!
  • Pooled connection origins — Neon Project/Branch (#718) and PlanetScale PostgresRole (#717) expose pooledOrigin, and PlanetScale branch replica intent persists so replicas: 0 converges (#719). Thanks Alex!
  • AWS env-auth fixesRegion is provided to STS during login (#709) and the Region/AWSEnvironment layer cycle in the account-id lookup is broken (#710).