Skip to content

2.0.0-beta.57 - Containers in Dev & AI Search

Cloudflare Containers now run locally under alchemy dev — the same Container + Durable Object code that deploys to Cloudflare’s network docker-builds and runs on your machine, no code changes. beta.57 also adds Cloudflare AI Search (AutoRAG) as a one-call construct with a typed Worker binding, and a large AWS coverage wave.

A Container class is unchanged — declare its identity and props, provide the runtime implementation with .make():

src/SandboxContainer.ts
export class SandboxContainer extends Cloudflare.Container<
SandboxContainer,
{}
>()("SandboxContainer", {
main: import.meta.filename,
instanceType: "dev",
}) {}
export default SandboxContainer.make(
Effect.gen(function* () {
return SandboxContainer.of({
fetch: Effect.succeed(
HttpServerResponse.text("Hello from Sandbox container!"),
),
});
}),
);

A Durable Object binds it, starts it, and talks to it over a TCP port — again, nothing dev-specific:

src/SandboxDO.ts
export default class SandboxDO extends Cloudflare.DurableObjectNamespace<SandboxDO>()(
"SandboxDO",
Effect.gen(function* () {
const sandbox = yield* Cloudflare.Container.bind(SandboxContainer);
return Effect.gen(function* () {
const container = yield* Cloudflare.start(sandbox, {
enableInternet: true,
});
return {
fetch: Effect.gen(function* () {
const { fetch } = yield* container.getTcpPort(3000);
const response = yield* fetch(
HttpClientRequest.get("http://container/"),
);
return HttpServerResponse.text(yield* response.text);
}),
};
});
}),
) {}

As of beta.57, alchemy dev runs that container on your machine: the container program is bundled with rolldown, a Dockerfile is generated (FROM oven/bun:1 or node:22-slim depending on the container’s runtime, auto-installing any declared externals), and the local runtime docker-builds the image and wires it into the local Durable Object namespace.

Terminal window
bun alchemy dev
curl http://localhost:1338/sandbox
# Hello from Sandbox container!

Dev-created containers carry a dev:<uuid> application id, so a subsequent alchemy deploy knows to promote them to a real ContainerApplication (#636).

Cloudflare.AiSearch stands up a full retrieval-augmented-generation pipeline over an R2 bucket — chunking, embedding, vector search, reranking, and generation, all managed by Cloudflare:

export const Docs = Cloudflare.R2Bucket("Docs", {});
export const Search = Cloudflare.AiSearch("Search", {
source: Docs,
});

It’s a construct, not a single resource: for an R2 source it provisions a least-privilege AccountApiToken scoped to the AI Search Index Engine permission group, wraps it in the AiSearchToken service token the indexer requires, and creates the AiSearchInstance with everything wired together. Pass a URL as source instead and it indexes a website via the web crawler; prefix / include / exclude globs scope what gets indexed.

Cloudflare.AiSearchInstance.bind(search) gives a Worker a typed Effect client — chatCompletions for full RAG, search for retrieval-only:

export default class Api extends Cloudflare.Worker<Api>()(
"Api",
{ main: import.meta.filename },
Effect.gen(function* () {
const aiSearch = yield* Search;
const search = yield* Cloudflare.AiSearchInstance.bind(aiSearch);
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const url = new URL(request.url, "http://api");
const query = url.searchParams.get("q") ?? "What is this about?";
const answer = yield* search
.chatCompletions({ messages: [{ role: "user", content: query }] })
.pipe(Effect.orDie);
return yield* HttpServerResponse.json({
response: answer.choices[0]?.message.content,
sources: answer.chunks.map((c) => c.item.key),
});
}),
};
}).pipe(Effect.provide(Cloudflare.AiSearchInstanceBindingLive)),
) {}

answer.chunks are the source chunks the answer drew from, so citations come for free. The binding also works in alchemy dev. The new AI Search tutorial walks through the whole pipeline, including dropping down to the underlying AiSearchInstance / AiSearchNamespace / AiSearchToken resources (#639).

Five new resources land in one PR (#631): CloudFront VpcOrigin, ELBv2 ListenerRule and TrustStore (mTLS), and Route53 HostedZone and HealthCheck — each with live tests.

Existing resources get much deeper. S3 Bucket alone gains encryption (SSE-KMS/DSSE), public access block, CORS, lifecycle rules, object ownership/ACL, access logging, website hosting, replication, intelligent tiering, object lock, and inline policy:

const site = yield* S3.Bucket("Site", {
versioning: "Enabled",
encryption: { sseAlgorithm: "aws:kms", bucketKeyEnabled: true },
publicAccessBlock: { blockPublicAcls: true, blockPublicPolicy: true },
lifecycleRules: [
{
ID: "expire-old",
Status: "Enabled",
Filter: { Prefix: "logs/" },
Expiration: { Days: 30 },
},
],
website: { indexDocument: { suffix: "index.html" } },
});

CloudFront Distribution picks up origin groups, geo restriction, access logging, trusted key groups, origin shield, VPC origins, and gRPC; Route53 Record gains routing policies; and RDS, ECS, ELBv2, and SQS props all deepen — about 8,700 lines in total.

KMS keys, Lambda aliases, log-group config

Section titled “KMS keys, Lambda aliases, log-group config”

Thanks José Netto for a run of AWS contributions: new AWS.KMS.Key and AWS.KMS.Alias resources (#641), a Lambda Alias resource (#625), reservedConcurrentExecutions on Lambda Function (#628), and LogGroup configuration — retention, KMS encryption, log class, and deletion protection (#630).

const key = yield* KMS.Key("AppKey", {
description: "Application encryption key",
enableKeyRotation: true,
});
yield* KMS.Alias("AppAlias", {
aliasName: "alias/app",
targetKeyId: key.keyId,
});
const logs = yield* LogGroup("ApiLogs", {
retentionInDays: 30,
kmsKeyId: key.keyArn,
});

Weighted alias routing shifts traffic between Lambda versions declaratively:

const alias = yield* Lambda.Alias("LiveAlias", {
functionName: fn.functionName,
functionVersion: "2",
aliasName: "live",
routingConfig: {
AdditionalVersionWeights: { "3": 0.1 },
},
});

beta.56 grew the Cloudflare provider 10x in one release; beta.57 is the flywheel’s second turn — stabilizing that fleet against the real API. Live tests flagged rough edges in PageRule, Schema Validation, Images Variant, origin post-quantum encryption, Origin TLS client auth, and Pages Project, and each fix followed the same doctrine: patch the distilled SDK so the API’s actual behavior becomes a typed error tag, regenerate, and handle the tag — never a status-code check in alchemy code. The distilled submodule was repatched and regenerated six times during this release. Quota- and entitlement-bound live tests now skip cleanly instead of failing, and transient errors retry on their exact typed tag — e.g. Magic Network Monitoring retries MnnConfigMissing — so an entitled account runs the full suite unchanged.

  • diff.stables overrides provider.stables — a provider’s diff can now narrow stability per-update, fixing spurious replacements (#635).
  • Atomic local state writesLocalState writes to a temp file and renames, so a crash mid-write can’t corrupt state.
  • alchemy dev no longer watches node_modules, cutting file-watcher churn on large projects (#646).
  • Queue consumers created in dev promote cleanly to a real deploy (#647).
  • Local service bindings preserve their entrypoints in dev. Thanks Jonathan Beckman (#643).
  • AI Search bindings work in local dev. Thanks Jonathan Beckman (#642).
  • alchemy cloudflare create-token can now mint user API tokens and interactively select permissions.
  • Build.Command handles both relative and absolute paths.
  • Artifacts.cached is allowed in the local RpcProvider (#648).
  • GitHub Repository docs — examples for templates, rename, archive, and secrets. Thanks Justin Bennett (#629).
  • AWS lifecycle hardening — Scheduler, SQS DLQ wiring, SNS Topic, CloudFront, DynamoDB, EC2, IAM, and Kinesis operations retry eventual consistency and throttling more robustly.
  • Cloudflare provider docs reorganized and categorized, with missing pages added.