| 1 | --- |
| 2 | title: 2.0.0-beta.60 - Docker, MicroVMs & RSC |
| 3 | date: 2026-07-06T22:00:00Z |
| 4 | category: release |
| 5 | excerpt: v2.0.0-beta.60 adds a Docker provider, AWS Lambda MicroVMs, Workers for Platforms, React Server Components on Vite, class-based Workflows on async Workers, dramatically faster Cloudflare Container starts, and a ground-up docs overhaul. |
| 6 | --- |
| 7 | |
| 8 | beta.60 is a big one: a new **Docker provider**, **AWS Lambda |
| 9 | MicroVMs**, **Workers for Platforms**, **React Server Components** |
| 10 | on the Vite pipeline, class-based **Workflows on async Workers**, |
| 11 | Cloudflare **Containers** that start fast and stay up, and a |
| 12 | ground-up overhaul of the docs. No breaking changes this release. |
| 13 | |
| 14 | ## Docker provider |
| 15 | |
| 16 | `alchemy/Docker` brings `Image`, `RemoteImage`, `Container`, |
| 17 | `Volume`, and `Network` as Stack resources |
| 18 | ([#649](https://github.com/alchemy-run/alchemy/pull/649)). |
| 19 | They drive the `docker` CLI's active context — Docker Desktop, a |
| 20 | remote/SSH context, a CI daemon — and live in the same Stack as |
| 21 | your cloud resources. Thanks **Austin**! |
| 22 | |
| 23 | ```typescript |
| 24 | const image = yield* Docker.RemoteImage("postgres-image", { |
| 25 | name: "postgres", |
| 26 | tag: "18-alpine", |
| 27 | }); |
| 28 | const network = yield* Docker.Network("app-network"); |
| 29 | const data = yield* Docker.Volume("postgres-data"); |
| 30 | |
| 31 | const postgres = yield* Docker.Container("postgres", { |
| 32 | image, |
| 33 | environment: { POSTGRES_PASSWORD: password }, // Redacted-safe |
| 34 | ports: [{ external: 15432, internal: 5432 }], |
| 35 | volumes: [{ hostPath: data.name, containerPath: "/var/lib/postgresql/data" }], |
| 36 | networks: [{ name: network.name, aliases: ["postgres"] }], |
| 37 | start: true, |
| 38 | }); |
| 39 | ``` |
| 40 | |
| 41 | Secrets are passed via process env (never on the CLI), pulled tags |
| 42 | are pinned so re-deploys are no-ops, and containers created outside |
| 43 | alchemy follow the standard adoption rules. See the new |
| 44 | [Docker hub](/docker) and the |
| 45 | [local services guide](/docker/local-services). |
| 46 | |
| 47 | ## AWS Lambda MicroVMs |
| 48 | |
| 49 | `AWS.Lambda.MicrovmImage` is a new Platform for snapshot-booted |
| 50 | microVMs ([#712](https://github.com/alchemy-run/alchemy/pull/712)). |
| 51 | Author the in-VM server in TypeScript — alchemy bundles it, |
| 52 | generates the Dockerfile, and builds the snapshot server-side — or |
| 53 | bring your own Dockerfile or a prebuilt artifact: |
| 54 | |
| 55 | ```typescript |
| 56 | export class Sandbox extends AWS.Lambda.MicrovmImage< |
| 57 | Sandbox, |
| 58 | { hello: (message: string) => Effect.Effect<string> } |
| 59 | >()("Sandbox") {} |
| 60 | |
| 61 | export default Sandbox.make( |
| 62 | { main: import.meta.filename, buildRole }, |
| 63 | Effect.gen(function* () { |
| 64 | return { |
| 65 | hello: (message) => Effect.succeed(`hello, ${message}!`), |
| 66 | fetch: Effect.gen(function* () { /* raw HTTP route */ }), |
| 67 | }; |
| 68 | }), |
| 69 | ); |
| 70 | ``` |
| 71 | |
| 72 | Every instance operation — `RunMicrovm`, `GetMicrovm`, |
| 73 | suspend/resume/terminate, auth tokens, image builds — is a |
| 74 | `Binding.Service` with least-privilege IAM scoping, and |
| 75 | `connectMicrovm` gives you typed RPC into the running VM: |
| 76 | |
| 77 | ```typescript |
| 78 | const sandbox = yield* AWS.Lambda.connectMicrovm(Sandbox, { endpoint, authToken }); |
| 79 | const reply = yield* sandbox.hello("world"); |
| 80 | ``` |
| 81 | |
| 82 | It works cross-cloud too: binding a MicroVM operation from a |
| 83 | `Cloudflare.Worker` mints an IAM user + assume-role Role so the |
| 84 | Worker can drive AWS MicroVMs at runtime. |
| 85 | |
| 86 | We also benchmarked MicroVMs against Cloudflare Containers — 700 |
| 87 | isolated cold boots. MicroVMs boot in 2–4.5s regardless of what's |
| 88 | inside; full results in |
| 89 | [Benchmarking Cloudflare Containers vs AWS MicroVMs](/blog/2026-07-01-microvm-cold-starts). |
| 90 | Docs: [MicroVMs](/aws/compute/microvms). |
| 91 | |
| 92 | ## Workers for Platforms |
| 93 | |
| 94 | Deploy user Workers into a dispatch namespace and route to them |
| 95 | dynamically ([#715](https://github.com/alchemy-run/alchemy/pull/715)). |
| 96 | A Worker deploys *into* a namespace with the new `namespace` prop — |
| 97 | no duplicate resource type: |
| 98 | |
| 99 | ```typescript |
| 100 | const ns = yield* Cloudflare.WorkersForPlatforms.DispatchNamespace("Customers", {}); |
| 101 | |
| 102 | const userWorker = yield* Cloudflare.Worker("CustomerA", { |
| 103 | namespace: ns.name, |
| 104 | script: `export default { fetch() { return new Response("hi") } }`, |
| 105 | }); |
| 106 | ``` |
| 107 | |
| 108 | The platform Worker binds the namespace Effect-natively, or via |
| 109 | `env` for async Workers: |
| 110 | |
| 111 | ```typescript |
| 112 | // Effect-native — yield the binding, get a typed client |
| 113 | const dispatch = yield* Cloudflare.WorkersForPlatforms.Get(namespace); |
| 114 | const userWorker = yield* dispatch.get("customer-a"); |
| 115 | ``` |
| 116 | |
| 117 | ```typescript |
| 118 | // async — DispatchNamespace is a valid env binding |
| 119 | const platform = Cloudflare.Worker("Platform", { |
| 120 | main: "./handler.ts", |
| 121 | env: { DISPATCH: namespace }, |
| 122 | }); |
| 123 | // handler.ts: env.DISPATCH.get("customer-a").fetch(request) |
| 124 | ``` |
| 125 | |
| 126 | Docs: [Workers for Platforms](/cloudflare/compute/workers-for-platforms). |
| 127 | |
| 128 | ## React Server Components on Vite |
| 129 | |
| 130 | `Cloudflare.Vite` now supports builds that emit more than one |
| 131 | server environment — most notably RSC |
| 132 | ([#685](https://github.com/alchemy-run/alchemy/pull/685)). |
| 133 | `viteEnvironments` selects which environment produces the deployed |
| 134 | Worker entry and which additional server environments are bundled |
| 135 | alongside it: |
| 136 | |
| 137 | ```typescript |
| 138 | const app = yield* Cloudflare.Vite("ReactRouterRSC", { |
| 139 | compatibility: { flags: ["nodejs_compat"] }, |
| 140 | viteEnvironments: { entry: "rsc", children: ["ssr"] }, |
| 141 | }); |
| 142 | ``` |
| 143 | |
| 144 | A single-environment SSR build needs no configuration — the |
| 145 | default is `{ entry: "ssr", children: [] }` — and the `client` |
| 146 | environment always deploys as static assets. The same PR replaces |
| 147 | the dev-server restart fingerprint with an exact structural |
| 148 | signature, so config changes are never silently missed. Docs: |
| 149 | [Frontends on Cloudflare](/cloudflare/frontend/frontends). |
| 150 | |
| 151 | ## Workflows on async Workers |
| 152 | |
| 153 | Class-based Cloudflare Workflows now bind to async (non-Effect) |
| 154 | Workers via `env`, mirroring class-based Durable Objects |
| 155 | ([#707](https://github.com/alchemy-run/alchemy/pull/707)): |
| 156 | |
| 157 | ```typescript |
| 158 | export const AsyncWorker = Cloudflare.Worker("Async", { |
| 159 | main: "./src/worker.ts", |
| 160 | env: { |
| 161 | MY_WORKFLOW: Cloudflare.Workflow<{ value: string }>("MyWorkflow", { |
| 162 | className: "MyWorkflow", |
| 163 | }), |
| 164 | }, |
| 165 | }); |
| 166 | export type Env = Cloudflare.InferEnv<typeof AsyncWorker>; |
| 167 | // env.MY_WORKFLOW is the native Workflow<{ value: string }> |
| 168 | ``` |
| 169 | |
| 170 | Your `WorkflowEntrypoint` class stays plain `cloudflare:workers` |
| 171 | code; alchemy provisions the workflow and emits the binding. |
| 172 | Cross-script references work with `scriptName`, same as async DOs. |
| 173 | Docs: [Workflows](/cloudflare/compute/workflows). |
| 174 | |
| 175 | ## Containers start fast and stay up |
| 176 | |
| 177 | Cloudflare Containers deployed with alchemy behaved much worse |
| 178 | than the same image deployed with wrangler — at 100 concurrent |
| 179 | cold starts, ~2/100 succeeded. Two root causes, both fixed |
| 180 | ([#708](https://github.com/alchemy-run/alchemy/pull/708)): |
| 181 | |
| 182 | - **Wrangler-parity defaults** — `maxInstances` defaulted to `1`, |
| 183 | serializing every Durable Object through one container slot. Now |
| 184 | `maxInstances: 20`, scale-from-zero, and `instanceType: "lite"` |
| 185 | when no custom limits are set, matching wrangler. |
| 186 | - **Readiness polling** — probes had no per-probe timeout, so a |
| 187 | not-yet-listening port could hang for minutes. The loop now |
| 188 | mirrors `@cloudflare/containers` exactly. |
| 189 | |
| 190 | Result: 100/100 cold starts, on par with wrangler. And a container |
| 191 | that stops or crashes is now transparently restarted on the next |
| 192 | request, while a crash-looping container fails fast instead of |
| 193 | burning the readiness budget |
| 194 | ([#711](https://github.com/alchemy-run/alchemy/pull/711)). |
| 195 | Docs: [Containers](/cloudflare/compute/containers). |
| 196 | |
| 197 | ## Effectful compute on ECS and EC2 |
| 198 | |
| 199 | The `ServerHost` + `{ fetch }` pattern now works end to end on |
| 200 | `AWS.ECS.Task` |
| 201 | ([#713](https://github.com/alchemy-run/alchemy/pull/713)): |
| 202 | the container entry bundles a Bun HTTP server, ships every chunk |
| 203 | into the image, and builds for the architecture the task declares |
| 204 | — verified by a smoke test that deploys a real Fargate service |
| 205 | behind an ALB. |
| 206 | |
| 207 | The hosted `AWS.EC2.Instance` serves HTTP the same way, with a |
| 208 | reboot-safe systemd unit that self-heals transient boot failures |
| 209 | ([#714](https://github.com/alchemy-run/alchemy/pull/714)). |
| 210 | And there's a new `AWS.EC2.KeyPair` resource — the generated |
| 211 | private key is captured once as a `Redacted` secret: |
| 212 | |
| 213 | ```typescript |
| 214 | const keyPair = yield* AWS.EC2.KeyPair("DeployKey", { keyType: "ed25519" }); |
| 215 | // keyPair.keyName -> AWS.EC2.Instance({ keyName }) |
| 216 | // keyPair.privateKey -> Redacted<string> |
| 217 | ``` |
| 218 | |
| 219 | ## The docs got a ground-up overhaul |
| 220 | |
| 221 | The website is restructured into per-provider hubs behind a |
| 222 | horizontal tab bar — `Core · CLI · Cloudflare · AWS · PlanetScale · |
| 223 | Neon · More ▾` — with ~100 new or reworked pages |
| 224 | ([#721](https://github.com/alchemy-run/alchemy/pull/721)): |
| 225 | |
| 226 | - **Core** — Infrastructure as Code, Infrastructure as Effects, |
| 227 | APIs, Environments, State Store, Project structure, Testing. |
| 228 | - **CLI** — every command documented, including the first-ever |
| 229 | docs for `alchemy unsafe nuke` and adopting resources. |
| 230 | - **Cloudflare / AWS hubs** — setup, five-part tutorials, block |
| 231 | pages and guides grouped by role (Compute, Frontend, APIs, Data, |
| 232 | Messaging, AI, …). |
| 233 | - Every page written against source, tests, and fixtures; every |
| 234 | moved URL 301s. |
| 235 | |
| 236 | Start at [the docs](/) and pick your provider. |
| 237 | |
| 238 | ## Also in this release |
| 239 | |
| 240 | - **`--yes` is fully non-interactive** |
| 241 | ([#728](https://github.com/alchemy-run/alchemy/pull/728)) — |
| 242 | `alchemy deploy --yes` now auto-accepts the Cloudflare state |
| 243 | store upgrade and deploy prompts instead of hanging on a non-TTY, |
| 244 | so CI needs no workarounds. |
| 245 | - **Partial state-store deploys are detected and recovered** |
| 246 | ([#700](https://github.com/alchemy-run/alchemy/pull/700)) — |
| 247 | the store health check now verifies a real store is serving, not |
| 248 | just that a pre-create stub exists. |
| 249 | - **`Command.Build` memo includes command + env** |
| 250 | ([#738](https://github.com/alchemy-run/alchemy/pull/738)) — |
| 251 | two `StaticSite` builds differing only in env (per-stage |
| 252 | `EXPO_PUBLIC_*`, API ids) no longer reuse each other's output. |
| 253 | - **Retained resources report as `retained`** |
| 254 | ([#739](https://github.com/alchemy-run/alchemy/pull/739)) — |
| 255 | destroy no longer lists `RemovalPolicy: "retain"` resources as |
| 256 | deleted. Thanks **bjorntechTobbe**! |
| 257 | - **The CLI works on Windows** |
| 258 | ([#696](https://github.com/alchemy-run/alchemy/pull/696)) — |
| 259 | the stack entry loads via a `file://` URL. Thanks **d3lay**! |
| 260 | - **Durable Object classes created outside alchemy adopt cleanly** |
| 261 | ([#495](https://github.com/alchemy-run/alchemy/pull/495)) — |
| 262 | class migrations fall back to matching the observed binding, so |
| 263 | adopting a wrangler/dashboard Worker reuses its DO classes |
| 264 | instead of failing to re-create them. |
| 265 | - **Access Application survives state loss** |
| 266 | ([#742](https://github.com/alchemy-run/alchemy/pull/742)) — |
| 267 | `read` falls back to a domain scan and gates takeover behind |
| 268 | `--adopt`, instead of blindly creating a duplicate app with a |
| 269 | fresh `aud`. Thanks **Andy Jefferson**! |
| 270 | - **Pooled connection origins** — Neon `Project`/`Branch` |
| 271 | ([#718](https://github.com/alchemy-run/alchemy/pull/718)) |
| 272 | and PlanetScale `PostgresRole` |
| 273 | ([#717](https://github.com/alchemy-run/alchemy/pull/717)) |
| 274 | expose `pooledOrigin`, and PlanetScale branch replica intent |
| 275 | persists so `replicas: 0` converges |
| 276 | ([#719](https://github.com/alchemy-run/alchemy/pull/719)). |
| 277 | Thanks **Alex**! |
| 278 | - **AWS env-auth fixes** — `Region` is provided to STS during login |
| 279 | ([#709](https://github.com/alchemy-run/alchemy/pull/709)) |
| 280 | and the `Region`/`AWSEnvironment` layer cycle in the account-id |
| 281 | lookup is broken |
| 282 | ([#710](https://github.com/alchemy-run/alchemy/pull/710)). |
| 283 | |
| 284 | ## Where to go next |
| 285 | |
| 286 | - [Docker](/docker) |
| 287 | - [MicroVMs](/aws/compute/microvms) |
| 288 | - [Workers for Platforms](/cloudflare/compute/workers-for-platforms) |
| 289 | - [Workflows](/cloudflare/compute/workflows) |
| 290 | - [Benchmarking Cloudflare Containers vs AWS MicroVMs](/blog/2026-07-01-microvm-cold-starts) |
| 291 | - [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta60) |
| 292 | - [Compare v2.0.0-beta.59 → v2.0.0-beta.60](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.59...v2.0.0-beta.60) |