1---
2title: EC2
3description: Launch virtual machines with the Instance resource — as a raw compute primitive, or hosting a bundled long-lived Effect program served straight off the box.
4---
5
6**EC2** is for when you need the machine itself: an OS you
7control, GPUs, custom daemons, software that expects a host, or
8predictable dedicated capacity. The
9[`Instance`](/providers/aws/ec2/instance) resource plays two
10roles — a **low-level compute primitive** (launch an AMI, attach
11networking, done), or a **host for a bundled Effect program**
12that Alchemy deploys onto the machine and keeps running.
13
14This is the lowest-level of Alchemy's four runtimes: unlike
15[Lambda](/aws/compute/lambda), [ECS](/aws/compute/ecs), and
16[EKS](/aws/compute/eks), you own patching,
17scaling, and availability. Reach for it deliberately — see
18[Choosing a runtime](/aws/compute/choosing-a-runtime).
19
20## Launch an instance
21
22The minimal instance is an AMI plus an instance type. AMI IDs
23are per-region, so resolve one with Alchemy's image helpers
24instead of hard-coding — `amazonLinux()` finds the latest Amazon
25Linux AMI (there are also `amazonLinux2023`, `amazonLinux2`,
26`ubuntu2404`, `ubuntu2204`, an id-only `image()` finder, and the
27underlying `getAmi()` data source returning the full image
28description). Each returns an `Output` resolved at deploy time:
29
30```typescript
31const instance = yield* AWS.EC2.Instance("AppInstance", {
32 imageId: AWS.EC2.amazonLinux(),
33 instanceType: "t3.micro",
34 subnetId: subnet.subnetId,
35});
36```
37
38The resolved instance exposes `instanceId`, `publicIpAddress`,
39`privateIpAddress`, DNS names, and the rest of the observed
40state — see the [`Instance` reference](/providers/aws/ec2/instance)
41for every prop and attribute.
42
43## Host an Effect program
44
45Give the instance a `main` entrypoint and it becomes a runtime,
46same class pattern as Lambda and ECS. Because instance props
47(the network, the AMI) are resolved by the stack, the props slot
48takes an `Effect.gen` block. The composition is also re-executed
49inside the deployed instance's bundle — that's safe as-is:
50resource yields resolve to references at runtime, and the AMI
51helper returns an `Output` that only resolves at deploy time:
52
53```typescript
54// src/server.ts
55import * as AWS from "alchemy/AWS";
56import * as Effect from "effect/Effect";
57import { HttpServerRequest } from "effect/http/HttpServerRequest";
58import * as HttpServerResponse from "effect/http/HttpServerResponse";
59
60export default class Server extends AWS.EC2.Instance<Server>()(
61 "Server",
62 Effect.gen(function* () {
63 const network = yield* AWS.EC2.Network("AppNetwork", {
64 cidrBlock: "10.81.0.0/16",
65 availabilityZones: 1,
66 });
67 const securityGroup = yield* AWS.EC2.SecurityGroup("AppSg", {
68 vpcId: network.vpcId,
69 description: "app ingress",
70 ingress: [
71 {
72 ipProtocol: "tcp",
73 fromPort: 3000,
74 toPort: 3000,
75 cidrIpv4: "0.0.0.0/0",
76 description: "app",
77 },
78 ],
79 egress: [
80 {
81 ipProtocol: "-1",
82 cidrIpv4: "0.0.0.0/0",
83 description: "all outbound",
84 },
85 ],
86 });
87
88 return {
89 main: import.meta.url,
90 imageId: AWS.EC2.amazonLinux(),
91 instanceType: "t3.small",
92 subnetId: network.publicSubnetIds[0],
93 securityGroupIds: [securityGroup.groupId],
94 associatePublicIpAddress: true,
95 port: 3000,
96 };
97 }),
98 Effect.gen(function* () {
99 return {
100 fetch: Effect.gen(function* () {
101 const request = yield* HttpServerRequest;
102 const url = new URL(request.url, "http://instance");
103 if (url.pathname === "/health") {
104 return yield* HttpServerResponse.json({ ok: true });
105 }
106 return HttpServerResponse.text("hello from EC2");
107 }),
108 };
109 }),
110) {}
111```
112
113At deploy time Alchemy bundles the program, uploads it to S3,
114and boots the instance with a user-data script that installs
115Bun and writes a **systemd unit**. The unit syncs the bundle and
116environment from S3 on every start and runs with
117`Restart=always`, so a flaky network install self-heals and the
118service survives reboots. The `{ fetch }` handler is served by
119the instance's HTTP server on `port`; `ServerHost` + `host.run`
120background loops work exactly as they do on [ECS](/aws/compute/ecs).
121
122Hosted instances also get an Alchemy-managed IAM role and
123instance profile. Bindings use the same contract as Lambda —
124environment variables plus IAM policy statements attached to
125that role — and you can widen it with managed policies:
126
127```typescript
128// e.g. make the instance manageable via SSM Session Manager
129roleManagedPolicyArns: [
130 "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
131],
132```
133
134## SSH access
135
136For direct access, create an Alchemy-managed
137[`KeyPair`](/providers/aws/ec2/keypair) and pass its name to the
138instance. The generated private key comes back as a `Redacted`
139output on the resource:
140
141```typescript
142const key = yield* AWS.EC2.KeyPair("AppKeyPair", {
143 keyType: "ed25519",
144});
145
146// then on the instance props:
147// keyName: key.keyName
148```
149
150## Networking
151
152An instance is only as reachable as the network you launch it
153into. The example above leans on the
154[`Network`](/providers/aws/ec2/network) helper for a
155public-subnet VPC; when you need explicit control — private
156subnets, NAT, VPC endpoints, custom route tables — compose the
157primitives directly. [VPC & networking](/aws/networking) walks
158through the whole set.
159
160## Where next
161
162- [Choosing a runtime](/aws/compute/choosing-a-runtime) — Lambda first,
163 ECS for containers, EKS for Kubernetes, EC2 when you need the
164 machine.
165- [VPC & networking](/aws/networking) — the `Network` helper and
166 the VPC primitives an instance lives in.
167- [`Instance` reference](/providers/aws/ec2/instance),
168 [`KeyPair` reference](/providers/aws/ec2/keypair) — every prop
169 and attribute.