| 1 | --- |
| 2 | title: Local development |
| 3 | description: alchemy dev runs your AWS stack on your machine — Lambda in Docker containers, local S3/DynamoDB/SQS, event sources firing, hot reload — with no AWS account or credentials required. |
| 4 | --- |
| 5 | |
| 6 | import Terminal from "../../../components/Terminal.astro"; |
| 7 | |
| 8 | `alchemy dev` runs your AWS stack entirely on your machine — no |
| 9 | AWS account, no credentials, no config. Lambda functions execute |
| 10 | in Docker containers behind working Function URLs, websites run |
| 11 | their frameworks' own dev servers with native HMR, ECS tasks run |
| 12 | as real containers, and the surrounding services (S3, DynamoDB, |
| 13 | SQS, SNS, EventBridge, and many more) are emulated locally. Code |
| 14 | changes hot reload in ~100–500ms. |
| 15 | |
| 16 | ```sh |
| 17 | alchemy dev |
| 18 | # no AWS credentials — using the local emulator |
| 19 | ``` |
| 20 | |
| 21 | <Terminal content={`[g]✓[/g] [b]Files[/b] [d](AWS.S3.Bucket)[/d] [g]created[/g] [d](local)[/d] |
| 22 | [g]✓[/g] [b]Users[/b] [d](AWS.DynamoDB.Table)[/d] [g]created[/g] [d](local)[/d] |
| 23 | [g]✓[/g] [b]Jobs[/b] [d](AWS.SQS.Queue)[/d] [g]created[/g] [d](local)[/d] |
| 24 | [g]✓[/g] [b]Api[/b] [d](AWS.Lambda.Function)[/d] [g]created[/g] [d](local → docker)[/d] |
| 25 | [s2][d]• http://b71ff5c0….lambda-url.us-east-1.localhost:4566/[/d] |
| 26 | |
| 27 | [d]Watching for changes ...[/d]`} /> |
| 28 | |
| 29 | ## How it works |
| 30 | |
| 31 | Local AWS emulation is powered by |
| 32 | [our fork](https://github.com/alchemy-run/floci) of |
| 33 | [floci](https://floci.io), an MIT-licensed LocalStack-style |
| 34 | emulator. The emulator starts automatically in Docker — one |
| 35 | container (`alchemy-floci`) shared across projects and sessions — |
| 36 | and your stack deploys into it in seconds. Resource identifiers |
| 37 | carry the `dev:` marker and local endpoints |
| 38 | (`localhost:4566`), which doubles as proof that no cloud call |
| 39 | ran. |
| 40 | |
| 41 | We hold the emulator to the same bar as the real cloud: alchemy's |
| 42 | live AWS test suites — the control-plane lifecycle tests and the |
| 43 | data-plane binding tests that normally run against real AWS — |
| 44 | also run against the emulator, and every fidelity gap they |
| 45 | surface becomes a fix in the fork. |
| 46 | |
| 47 | ## Lambda runs in containers |
| 48 | |
| 49 | Each Lambda function executes in a local runtime container behind |
| 50 | a working Function URL: |
| 51 | |
| 52 | ```typescript |
| 53 | export default class Api extends AWS.Lambda.Function<Api>()( |
| 54 | "Api", |
| 55 | { main: import.meta.url, functionUrl: true }, |
| 56 | Effect.gen(function* () { |
| 57 | const files = yield* AWS.S3.Bucket("Files"); |
| 58 | const jobs = yield* AWS.SQS.Queue("Jobs"); |
| 59 | |
| 60 | const putObject = yield* AWS.S3.PutObject(files); |
| 61 | const sendMessage = yield* AWS.SQS.SendMessage(jobs); |
| 62 | |
| 63 | return { |
| 64 | fetch: Effect.gen(function* () { |
| 65 | yield* putObject({ Key: "hello.txt", Body: "hello" }); |
| 66 | yield* sendMessage({ MessageBody: "process hello.txt" }); |
| 67 | return yield* HttpServerResponse.text("ok"); |
| 68 | }).pipe(Effect.orDie), |
| 69 | }; |
| 70 | }).pipe( |
| 71 | Effect.provide([AWS.S3.PutObjectHttp, AWS.SQS.SendMessageHttp]), |
| 72 | ), |
| 73 | ) {} |
| 74 | ``` |
| 75 | |
| 76 | In dev, the function serves at a working local Function URL |
| 77 | (`http://….lambda-url.us-east-1.localhost:4566/`), `putObject` |
| 78 | lands in the local bucket, and `sendMessage` lands in the local |
| 79 | queue. The SDK honors the standard `AWS_ENDPOINT_URL` environment |
| 80 | override that the emulator injects into every dev container. |
| 81 | |
| 82 | Save the file and the running function serves the new code in |
| 83 | **~100–500ms** — no redeploy, no restart. The dev session watches |
| 84 | your code's module graph, rebuilds, and swaps the code under the |
| 85 | live Function URL. |
| 86 | |
| 87 | ## Websites run their own dev servers |
| 88 | |
| 89 | Every `AWS.Website.*` resource — Vite, Next.js, Astro, Nuxt, |
| 90 | SvelteKit, Waku, Octane, StaticSite — runs its framework's own |
| 91 | dev server under `alchemy dev`: native HMR, no cloud resources. |
| 92 | The site's `url` is the local dev server's address: |
| 93 | |
| 94 | ```typescript |
| 95 | const site = yield* AWS.Website.Vite("Web"); |
| 96 | // dev: site.url = http://localhost:5173 — Vite's own dev server, HMR included |
| 97 | // deploy: site.url = the CloudFront distribution |
| 98 | ``` |
| 99 | |
| 100 | The same stack deploys unchanged: `alchemy deploy` runs |
| 101 | `vite build` and serves the output from S3 through CloudFront. For |
| 102 | SSR frameworks, `server.environment` is delivered to the dev |
| 103 | server's process env, so config reads work the same locally and |
| 104 | deployed. See [AWS frontends](/aws/frontend/websites). |
| 105 | |
| 106 | ## ECS tasks run as containers |
| 107 | |
| 108 | ECS tasks and services run as real Docker containers, built and |
| 109 | pushed through a local OCI registry: |
| 110 | |
| 111 | ```typescript |
| 112 | // bundled Effect program — built into an image, pushed through |
| 113 | // a local registry, running as a real container seconds later |
| 114 | export default class Web extends AWS.ECS.Task<Web>()( |
| 115 | "Web", |
| 116 | { main: import.meta.url, port: 8080 }, |
| 117 | Effect.gen(function* () { |
| 118 | return { |
| 119 | fetch: Effect.gen(function* () { |
| 120 | return HttpServerResponse.text("hello from a local container"); |
| 121 | }), |
| 122 | }; |
| 123 | }), |
| 124 | ) {} |
| 125 | ``` |
| 126 | |
| 127 | Bring-your-own-Dockerfile containers work too: |
| 128 | `AWS.ECS.Service("Api", { context: "./api", port: 3000 })` builds |
| 129 | your image and runs it the same way. |
| 130 | |
| 131 | The whole live pipeline runs unchanged: image build, `docker |
| 132 | push`, task definitions, `RunTask`, and services that converge |
| 133 | exactly like AWS. Hot reload swaps a running container in ~6 |
| 134 | seconds — bundled tasks watch the deploy's exact module graph, |
| 135 | Dockerfile tasks watch the build context, and services roll their |
| 136 | tasks onto the new image revision. Emulated ALB listeners route |
| 137 | to the local containers; each task is also reachable directly at |
| 138 | its literal host port (`localhost:<port>`). |
| 139 | |
| 140 | ## Event sources fire locally |
| 141 | |
| 142 | The glue between services works end to end in the emulator: |
| 143 | SQS→Lambda event source mappings poll and deliver batches, S3 |
| 144 | notifications fire, EventBridge rules route, SNS subscriptions |
| 145 | deliver, DynamoDB Streams pump changes, and EventBridge Scheduler |
| 146 | schedules fire — so a `consumeQueueMessages` handler in a local |
| 147 | Lambda drains a local queue exactly like it would in the cloud. |
| 148 | |
| 149 | ## What's emulated |
| 150 | |
| 151 | Close to 40 services and over 200 resource types run against the |
| 152 | local emulator in dev: |
| 153 | |
| 154 | | Category | Services | |
| 155 | | ------------------- | ------------------------------------------------------------------------------------------------------------------- | |
| 156 | | Compute | Lambda (functions, layers, versions, aliases, event source mappings, permissions), ECS, ECR, Batch, Auto Scaling, Application Auto Scaling | |
| 157 | | APIs & delivery | API Gateway (REST), API Gateway v2 (HTTP & WebSocket), AppSync, CloudFront (Distributions, Functions, KeyValueStore), WAFv2, ACM | |
| 158 | | Storage & databases | S3, DynamoDB, RDS, Glue, Athena, S3 Vectors | |
| 159 | | Messaging & events | SQS, SNS, EventBridge, Kinesis, Firehose, Pipes, Scheduler, Step Functions | |
| 160 | | Networking & DNS | EC2 (VPCs, subnets, security groups, instances, …), ELBv2, Route 53, Cloud Map | |
| 161 | | Identity & security | IAM, Cognito, KMS, Secrets Manager | |
| 162 | | Config & ops | SSM Parameter Store, AppConfig, CloudWatch Logs, SES | |
| 163 | |
| 164 | Resources without local emulation run against the real cloud in |
| 165 | your personal [stage](/environments/stages) — a stack that mixes |
| 166 | emulated and live-only resources just works. |
| 167 | |
| 168 | ## Mix in the real cloud |
| 169 | |
| 170 | `Alchemy.remote()` opts any resource out of local emulation: |
| 171 | |
| 172 | ```typescript |
| 173 | // everything else is local; this one hits real AWS |
| 174 | const secret = yield* AWS.SecretsManager.Secret("ProdSecret", { ... }) |
| 175 | .pipe(Alchemy.remote()); |
| 176 | ``` |
| 177 | |
| 178 | If a remote resource needs credentials you don't have, |
| 179 | `alchemy dev` tells you before touching anything: |
| 180 | |
| 181 | ```text |
| 182 | AWS credentials are required, but none are configured for profile 'default'. |
| 183 | These resources require AWS credentials: |
| 184 | - ProdSecret (runs against the real cloud via Alchemy.remote()) |
| 185 | Run `alchemy profile edit --profile default --add AWS` to configure credentials, or set CI=1 to use environment-variable credentials. |
| 186 | ``` |
| 187 | |
| 188 | Switching a resource between local and live (or dev → deploy) |
| 189 | plans a **replacement** — dev state never silently becomes cloud |
| 190 | state. See |
| 191 | [Local development](/environments/local-development) for the |
| 192 | shared semantics. |
| 193 | |
| 194 | ## Emulator management |
| 195 | |
| 196 | The emulator container outlives dev sessions — Docker is the |
| 197 | supervisor, so there's nothing to babysit. Overrides when you |
| 198 | want them: |
| 199 | |
| 200 | ```sh |
| 201 | ALCHEMY_FLOCI_IMAGE=floci:dev alchemy dev # use your own emulator build |
| 202 | ``` |
| 203 | |
| 204 | The default image is our patched release |
| 205 | (`ghcr.io/alchemy-run/floci`). |
| 206 | |
| 207 | To wipe everything inside the emulator without recreating the |
| 208 | container, point [`nuke`](/cli/nuke) at the local providers: |
| 209 | |
| 210 | ```sh |
| 211 | bun alchemy unsafe nuke --local --include 'AWS.*' |
| 212 | ``` |
| 213 | |
| 214 | `--local` enumerates and deletes through each provider's local |
| 215 | implementation, so it only ever sees emulated resources — no |
| 216 | cloud credentials are involved and nothing in a real AWS account |
| 217 | can be touched. |
| 218 | |
| 219 | ## Test against the local stack |
| 220 | |
| 221 | The [test harness](/testing/test-harness) has the same dev mode — |
| 222 | `Test.make({ dev: true })` (available on both the Bun and Vitest |
| 223 | adapters, off by default) runs the suite's `deploy(Stack)` against |
| 224 | the local emulator instead of the real account, no separate |
| 225 | `alchemy dev` terminal needed: |
| 226 | |
| 227 | ```typescript |
| 228 | const { test, beforeAll, afterAll, deploy, destroy } = Test.make({ |
| 229 | providers: AWS.providers(), |
| 230 | dev: true, |
| 231 | }); |
| 232 | ``` |
| 233 | |
| 234 | Lambda functions run in local containers, the emulated services |
| 235 | back them, and function URL outputs point at the local gateway — |
| 236 | so the same HTTP assertions run with zero cloud credentials. See |
| 237 | [Test harness → dev](/testing/test-harness#dev) for the full |
| 238 | semantics. |
| 239 | |
| 240 | ## Where next |
| 241 | |
| 242 | - [Local development](/environments/local-development) — the |
| 243 | shared `alchemy dev` concepts: hot reload, `Alchemy.remote()`, |
| 244 | and dev vs deploy semantics. |
| 245 | - [AWS frontends](/aws/frontend/websites) — framework dev servers |
| 246 | under `alchemy dev`. |
| 247 | - [Stages](/environments/stages) — how live-in-dev resources stay |
| 248 | isolated per developer. |
| 249 | - [Testing](/testing) — run the same test suite against the local |
| 250 | emulator with `Test.make({ dev: true })`. |
| 251 | - [Local Providers](/infrastructure-as-code/local-provider) — |
| 252 | build the local implementation of a resource. |