EC2
EC2 is for when you need the machine itself: an OS you
control, GPUs, custom daemons, software that expects a host, or
predictable dedicated capacity. The
Instance resource plays two
roles — a low-level compute primitive (launch an AMI, attach
networking, done), or a host for a bundled Effect program
that Alchemy deploys onto the machine and keeps running.
This is the lowest-level of Alchemy’s four runtimes: unlike Lambda, ECS, and EKS, you own patching, scaling, and availability. Reach for it deliberately — see Choosing a runtime.
Launch an instance
Section titled “Launch an instance”The minimal instance is an AMI plus an instance type. AMI IDs
are per-region, so resolve one with Alchemy’s image helpers
instead of hard-coding — amazonLinux() finds the latest Amazon
Linux AMI (there are also amazonLinux2023, amazonLinux2,
ubuntu2404, ubuntu2204, an id-only image() finder, and the
underlying getAmi() data source returning the full image
description). Each returns an Output resolved at deploy time:
const instance = yield* AWS.EC2.Instance("AppInstance", { imageId: AWS.EC2.amazonLinux(), instanceType: "t3.micro", subnetId: subnet.subnetId,});The resolved instance exposes instanceId, publicIpAddress,
privateIpAddress, DNS names, and the rest of the observed
state — see the Instance reference
for every prop and attribute.
Host an Effect program
Section titled “Host an Effect program”Give the instance a main entrypoint and it becomes a runtime,
same class pattern as Lambda and ECS. Because instance props
(the network, the AMI) are resolved by the stack, the props slot
takes an Effect.gen block. The composition is also re-executed
inside the deployed instance’s bundle — that’s safe as-is:
resource yields resolve to references at runtime, and the AMI
helper returns an Output that only resolves at deploy time:
import * as AWS from "alchemy/AWS";import * as Effect from "effect/Effect";import { HttpServerRequest } from "effect/unstable/http/HttpServerRequest";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
export default class Server extends AWS.EC2.Instance<Server>()( "Server", Effect.gen(function* () { const network = yield* AWS.EC2.Network("AppNetwork", { cidrBlock: "10.81.0.0/16", availabilityZones: 1, }); const securityGroup = yield* AWS.EC2.SecurityGroup("AppSg", { vpcId: network.vpcId, description: "app ingress", ingress: [ { ipProtocol: "tcp", fromPort: 3000, toPort: 3000, cidrIpv4: "0.0.0.0/0", description: "app", }, ], egress: [ { ipProtocol: "-1", cidrIpv4: "0.0.0.0/0", description: "all outbound", }, ], });
return { main: import.meta.url, imageId: AWS.EC2.amazonLinux(), instanceType: "t3.small", subnetId: network.publicSubnetIds[0], securityGroupIds: [securityGroup.groupId], associatePublicIpAddress: true, port: 3000, }; }), Effect.gen(function* () { return { fetch: Effect.gen(function* () { const request = yield* HttpServerRequest; const url = new URL(request.url, "http://instance"); if (url.pathname === "/health") { return yield* HttpServerResponse.json({ ok: true }); } return HttpServerResponse.text("hello from EC2"); }), }; }),) {}At deploy time Alchemy bundles the program, uploads it to S3,
and boots the instance with a user-data script that installs
Bun and writes a systemd unit. The unit syncs the bundle and
environment from S3 on every start and runs with
Restart=always, so a flaky network install self-heals and the
service survives reboots. The { fetch } handler is served by
the instance’s HTTP server on port; ServerHost + host.run
background loops work exactly as they do on ECS.
Hosted instances also get an Alchemy-managed IAM role and instance profile. Bindings use the same contract as Lambda — environment variables plus IAM policy statements attached to that role — and you can widen it with managed policies:
// e.g. make the instance manageable via SSM Session ManagerroleManagedPolicyArns: [ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",],SSH access
Section titled “SSH access”For direct access, create an Alchemy-managed
KeyPair and pass its name to the
instance. The generated private key comes back as a Redacted
output on the resource:
const key = yield* AWS.EC2.KeyPair("AppKeyPair", { keyType: "ed25519",});
// then on the instance props:// keyName: key.keyNameNetworking
Section titled “Networking”An instance is only as reachable as the network you launch it
into. The example above leans on the
Network helper for a
public-subnet VPC; when you need explicit control — private
subnets, NAT, VPC endpoints, custom route tables — compose the
primitives directly. VPC & networking walks
through the whole set.
Where next
Section titled “Where next”- Choosing a runtime — Lambda first, ECS for containers, EKS for Kubernetes, EC2 when you need the machine.
- VPC & networking — the
Networkhelper and the VPC primitives an instance lives in. Instancereference,KeyPairreference — every prop and attribute.