
Let Your AI-DLC Agent Run to Completion on EC2
An EC2 box that runs Claude Code on Bedrock so an AI-DLC bolt finishes after you close the laptop — reached over SSM, no inbound ports, idle-stopped.
Let Your AI-DLC Agent Run to Completion on EC2
The whole promise of AI-DLC is that the agent does a bounded unit of work while you review at the gates. That only pays off if the agent can actually finish — and a laptop lid closing mid-bolt kills it. So I moved the agent onto a cloud box that runs the bolt to completion whether I'm watching or not.
The short version
AI-DLC — AWS's AI-Driven Development Life Cycle — reorganizes delivery around bolts: short, intensive cycles, hours or days long, in which the agent drafts requirements, design, code, and tests for one bounded unit of work while you approve at the gates. The awslabs/aidlc-workflows steering docs make that loop concrete.
Here's the rub. A bolt's construction phase can run a long time — long enough that you are not the bottleneck, the agent is. But if the agent lives on your laptop, the bolt lives and dies with your laptop. Close the lid on the train, drop Wi-Fi in a meeting, let the machine sleep — and the agent's half-finished unit of work dies with the session. You come back not to a completed bolt to review, but to a killed process to restart.
So I moved the agent onto a single EC2 instance that:
- runs Claude Code against Amazon Bedrock, authenticated by the instance's own IAM role — so a bolt that runs for hours never has credentials expire underneath it;
- serves a full VS Code in the browser (via code-server ), so I can drop in and drive the bolt from any machine;
- is reachable only through AWS Systems Manager Session Manager — no public IP, no open ports, no SSH key to lose;
- knows the difference between an idle box and an agent mid-bolt — it stops itself when nobody's home and no work is running, but never pulls the rug on a running bolt;
- and comes up from a double-click launcher that reconnects itself when my laptop sleeps, so the bolt keeps running in the cloud while I'm gone.
This is the interactive companion to a pattern I wrote about earlier — I stopped running Claude Code on my laptop . That post was about pushing agents fully into the pipeline (GitHub issues → Actions → PRs). This one is the other half: when you do want to sit down and drive a bolt interactively — approving at the gates, nudging the design — but you don't want the bolt tied to your laptop staying open.
Why the laptop is the wrong home for a bolt
Running a bolt on a laptop has three nagging problems, and all three cut against letting the agent finish. It ties the run to one machine, so the moment I switch context — grab lunch, hop to a call, go home — the bolt is stranded. It ties the runtime to my battery and my Wi-Fi, so a sleep or a dropped connection ends the run mid-construction. And getting cloud credentials onto the laptop — so the agent can actually do things in AWS — is a recurring chore of expiring tokens that expire right in the middle of the longest runs.
Moving the box to EC2 fixes all three at once, but it introduces new questions that are easy to get wrong: How do I reach it without exposing a port to the internet? How does the agent authenticate to Bedrock, over a multi-hour bolt, without me babysitting credentials? And — the one that quietly breaks everything — how do I stop paying for an idle box without killing an agent that's still working?
The answers turned out to be four AWS-native building blocks that fit together cleanly.
The architecture
Plain text
your laptop (any network)││ SSM Session Manager port-forward (outbound-only, IAM-authenticated)│ localhost:8080 ─────────────────► code-server (127.0.0.1:8080)│ localhost:3000 ─────────────────► dev server (127.0.0.1:3000)▼EC2 instance (no public ingress, no SSH)├─ code-server → VS Code in the browser├─ Claude Code → runs the bolt, talks to Bedrock via the instance role├─ idle-stop.timer → stops the box when idle — but never mid-bolt└─ IAM instance role → self-refreshing credentials for Bedrock + self-stop│└── Amazon Bedrock (Claude models)
Everything lives in one small CloudFormation template. The interesting parts aren't the VPC and subnet boilerplate — they're the four details below.
Four details that make it work
1. The agent authenticates to Bedrock through the instance role
This is the piece that makes the whole thing pleasant. Claude Code has first-class Amazon Bedrock support : set one environment variable and it uses your resolved AWS credentials instead of an Anthropic API key.
On an EC2 instance, the best credentials to hand it are the instance's own IAM role . Those credentials are delivered through the instance metadata service and rotate automatically — they never expire out from under a long-running agent, and there's nothing to
mwinit or refresh. That property matters a lot the first time you kick off an agent run on Friday and check it on Monday.The role needs just enough to call Bedrock (plus SSM, below):
YAML
ManagedPolicyArns:- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore # lets SSM manage the boxPolicies:- PolicyName: bedrock-invokePolicyDocument:Version: '2012-10-17'Statement:- Effect: AllowAction:- bedrock:InvokeModel- bedrock:InvokeModelWithResponseStream- bedrock:Converse- bedrock:ConverseStreamResource: "*"
Then a login-shell profile turns Claude Code toward Bedrock for every terminal on the box:
Bash
# /etc/profile.d/claude-bedrock.shexport CLAUDE_CODE_USE_BEDROCK=1export AWS_REGION=us-east-1export ANTHROPIC_MODEL=<a Bedrock inference-profile ID for the Claude model you enabled>
Point
ANTHROPIC_MODEL at a cross-region inference profile for the Claude model you've enabled in your account. Now typing claude in any shell on the box just works — no key, no prompt.One caution worth stating plainly: whatever you grant the instance role, your agent inherits. A convenient box is a powerful box. Scope the role to what you actually need, and remember that an autonomous run acts with those same permissions.
2. No inbound ports — reach it through SSM
The instinct is to open port 8080, or SSH in, or put the editor behind a load balancer. Don't. The security group here has zero inbound rules. Access happens entirely through Session Manager port forwarding , which the SSM agent establishes as an outbound connection to the AWS control plane — so there's nothing to reach in to.
Bash
aws ssm start-session --target <instance-id> \--document-name AWS-StartPortForwardingSession \--parameters '{"portNumber":["8080"],"localPortNumber":["8080"]}'
code-server binds to
127.0.0.1 on the box, so it's never exposed on the network at all — the tunnel maps it to localhost:8080 on my laptop, and the browser talks to loopback. Authentication and authorization ride on IAM: if you can't assume a role with ssm:StartSession to that instance, you can't reach the editor. That's a stronger and simpler story than managing a public endpoint and a password.If you also run a dev server on the box, forward a second port (say
localhost:3000 ) the same way and your app previews show up locally too. AWS-StartPortForwardingSession handles one port per session, so this is just a second session.3. Idle auto-stop that knows the difference between you and your bolt
This is the detail that makes "run to completion" real. An always-on box only makes sense if it isn't always running, so a systemd timer checks for activity every few minutes and calls
ec2 stop-instances on itself when the box has been quiet for a while. A stopped instance keeps its EBS volume — all your files, branches, and installed tools survive — but you stop paying for compute. Starting it back up takes about half a minute.The subtle part is defining "quiet," and getting it wrong is exactly the failure this whole article is about. The naive version — "no SSH connection" — will happily shut the box down in the middle of an hour-long bolt, killing the construction run right when you've left it alone to work. That's the single most common way people get this wrong. The activity check has to treat an agent mid-bolt as "busy" even when no human is connected:
Bash
active=0# someone is connected through SSM (an interactive shell OR a port-forward)pgrep -f 'ssm-session-worker' >/dev/null 2>&1 && active=1# code-server has a live browser connectionss -Htn state established 2>/dev/null | grep -q '127.0.0.1:8080' && active=1# meaningful CPU load means an agent is mid-run — don't pull the rug outawk '{ exit !($1 > 0.4) }' /proc/loadavg && active=1# only after N consecutive "idle" checks does the box stop itself
That CPU-load line is the whole point: it's what lets you kick off a bolt, close the laptop, and come back to a finished unit of work waiting at the gate instead of a box that stopped itself halfway through.
4. A launcher that reconnects itself
The last mile is ergonomics. I want to double-click something, not remember a CLI incantation. So the box comes up from a small launcher script (wrapped in a macOS
.app ) that starts the instance if it's stopped, copies the code-server password to the clipboard, opens the browser, and holds the tunnels open.The detail that took a couple of iterations: a port-forward session dies whenever the laptop sleeps or the network changes (you'll see a
broken pipe ). The first version just left a dead terminal window, and I had to re-run it. The fix is to run each tunnel in a self-healing retry loop — and, since the box may have idle-stopped while I was away, to restart it before reconnecting:Bash
forward() { # $1 = remote port, $2 = local portwhile [ ! -e "$STOP" ]; doensure_running # start the box if it idle-stopped while awayfree_port "$2" # clear any wedged local listener firstaws ssm start-session --target "$IID" \--document-name AWS-StartPortForwardingSession \--parameters "{\"portNumber\":[\"$1\"],\"localPortNumber\":[\"$2\"]}"[ -e "$STOP" ] && breakecho "tunnel on $2 dropped — reconnecting in 3s..."sleep 3done}
Two things I learned the hard way:
-
session-manager-pluginreparents itself, so killing theawsprocess you launched does not stop the tunnel. To reliably tear one down, kill whatever is listening on the local port (lsof -ti tcp:8080 -sTCP:LISTEN), not the PID you started — otherwise a wedged listener blocks the next reconnect with "address already in use."
- Distinguish a dropped tunnel from an intentional shutdown. A stop-flag file lets the retry loops and the
Ctrl-C/close-window cleanup coordinate, so closing the window tears everything down while a sleep-induced drop heals silently.
The payoff: I kick off a bolt, close my laptop, and walk away. The tunnel drops, but the box stays up (detail 3 keeps it alive because the agent is working) and the bolt keeps running in the cloud. When I come back, the editor tab reconnects on its own — restarting the box first if the bolt had already finished and it idle-stopped. No re-running anything, and no bolt lost to a closed lid.
Should you build this, or use something off the shelf?
Fair question — and worth asking before you maintain your own CloudFormation. "On-demand cloud dev workspaces" is a real product category now, and if you're outfitting a team, you probably shouldn't hand-roll it:
- Coder is the closest match to this pattern, productized. Its Community Edition is open source, it provisions workspaces on EC2 in your own account via Terraform, it serves code-server, and its workspace agent dials outbound to a control plane — the same "no inbound ports" idea. The cost is that you now operate a control plane and a database. Right call for multi-user, with SSO, RBAC, and templates.
- DevPod is open source and bring-your-own-cloud, but client-only — great for an individual, less so for a shared browser portal.
- GitHub Codespaces is the lowest-friction managed option, but it runs in GitHub's cloud, not your AWS account — so the "agent uses the instance role for Bedrock" story doesn't apply.
For a single developer who wants the agent running in their own AWS account on Bedrock, a few files of CloudFormation plus a launcher is honestly competitive — and cheaper, since there's no control-plane instance or database to keep running. The moment it becomes "my team wants boxes too," that's the signal to adopt Coder rather than grow a personal script into a multi-tenant platform.
The reason to build it once yourself, even if you later graduate, is that you learn exactly where the sharp edges are — the idle-vs-agent distinction, the reparented tunnel process, the credential rotation — and those show up in every one of the managed tools too.
What I'd add next
The box is deliberately minimal. The natural extensions, roughly in the order I'd reach for them — and each one pushes further toward "run more bolts, unattended":
-
git worktreeper bolt so several agents can run parallel units of work on their own branches in the same box without stepping on each other. This is where the pattern stops being "my laptop, elsewhere" and starts being a small bolt-runner.
- A persistent
tmuxsession per repo, so a bolt survives a disconnect entirely — the tunnel dropping never even interrupts the run, it just detaches you from watching it.
- A scheduled nightly stop as a backstop to the idle timer, for the rare case where something holds CPU above the threshold after a bolt actually finished.
If you're wiring Claude Code to Bedrock in other shapes, two earlier posts pair well with this one: hosting Claude Code on Bedrock AgentCore for a managed-runtime take, and using the Claude Code Action with Bedrock for the credentials-and-CI angle.
The whole thing is four AWS primitives doing what they're good at: an instance role for credentials that outlive a long bolt, Session Manager for access without exposure, a systemd timer that can tell a working agent from an idle box, and a shell loop for ergonomics. None of it is exotic. But the combination changes what AI-DLC feels like day to day — the agent, not your laptop battery, becomes the thing that decides when a bolt is done.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article