Stops idle EC2 dev boxes and GPU boxes, warns their owners in Slack first, and lets the team
start, stop and keep boxes alive from Slack. One terraform apply, and a tag on each box.
A g5.xlarge left running over a weekend costs about $70; left on for a month, about $730. Schedules alone stop boxes people are still using, and CPU alarms stop boxes whose owner is reading code in an SSH session. This module stops a box when nobody is using it:
EventBridge (every 5 min) ──▶ evaluator Lambda
│ boxes tagged devbox:managed=true (instances and Auto Scaling groups)
│ signals: CPU and network (CloudWatch), Session Manager sessions,
│ an SSM probe on the box (SSH sessions, logged-in users, GPU)
▼
idle 45 min ──▶ warning in Slack, mentioning the owner
15 min later, still idle ──▶ stop (an Auto Scaling group: Standby, then stop)
23:00 local ──▶ warning, then stop, used or not
08:30 on weekdays ──▶ start the boxes that opted in
Slack: @Amazon Q run devbox-start alice-gpu ──▶ Amazon Q ──▶ control Lambda ──▶ EC2
- Idle means unused: average CPU and network below thresholds, no Session Manager session, no SSH session, nobody logged in, and the GPU quiet. A missing signal counts as activity.
- Warned first: the owner is mentioned in the channel and has
warn_minutes(15 by default) to use the box or rundevbox-extend. The only stop without a warning is an explicitdevbox-stop. - From Slack:
devbox-list,devbox-status,devbox-start,devbox-stopanddevbox-extend, through Amazon Q Developer in chat applications (formerly AWS Chatbot). - A nightly stop as a backstop, and an optional weekday morning start for the boxes that ask for it.
- Auto Scaling groups too: a group "stops" by putting its instances in Standby and stopping them, so their disks survive, and starts back to its previous capacity.
- Least privilege: the functions can start, stop and probe only resources tagged
devbox:managed=true, and the Slack channel can invoke only the module's command function. - Self-contained: Python Lambda functions using only boto3, packaged by Terraform; the
hashicorp/awsandhashicorp/archiveproviders. No Docker, no build step.
- It's for a small team's personal boxes, not a dev-environment platform. If you want workspaces created from templates, per-user isolation or an IDE gateway, look at Coder, DevPod or SkyPilot instead.
- Anyone in the Slack channel can act on any managed box. Amazon Q lets channel members
choose their own IAM role, so "only your own box" can't be enforced. Amazon Q's audit log
(
/aws/chatbot/<name>-<hash>in us-east-1, keptlog_retention_days) records who ran what, and every action is announced in the channel with the owner mentioned. Use a channel for people who may stop each other's boxes. - Tags opt a box in. Anyone who can tag an instance can put it under the module. See
SECURITY.md for
allowed_idsand an SCP that locks the tag down. - Idle detection has blind spots. It sees SSH, Session Manager, logged-in users, CPU,
network and GPU. It doesn't see a VS Code Tunnel, a remote desktop (RDP, VNC, DCV), a
Jupyter server reached over an open port, or a quiet notebook kernel you mean to come back
to; these count only through their network traffic. Run
devbox-extendbefore you step away from such work. How idle works. - Linux only. The probe is a POSIX shell script; Windows boxes get metrics only.
- One region per deployment, one channel per deployment. Deploy the module once in each region your boxes run in, and give each deployment its own Slack channel: two deployments in one channel would define the same command aliases.
git clone https://github.com/ankitkr/terraform-aws-devboxes.git
cd terraform-aws-devboxes/deploy
cp terraform.tfvars.example terraform.tfvars # region, Slack IDs, time zone, emails
terraform init
terraform applyState is local unless you copy backend.tf.example to backend.tf. Authorize Slack first
(below); without the slack block, notices go to email only and there
are no commands.
module "devboxes" {
source = "github.com/ankitkr/terraform-aws-devboxes?ref=v1.0.0"
slack = { # null: notices to SNS and email only, no commands
workspace_id = "T0123ABCD"
channel_id = "C0123ABCD"
}
time_zone = "Asia/Kolkata"
nightly_stop = "23:00" # null: no nightly stop
morning_start = "08:30" # null: no morning start; weekdays only
idle = {
minutes = 45 # idle this long, then warn (null: no idle stop)
warn_minutes = 15 # then stop unless used or kept alive
}
max_extension_hours = 12
alert_emails = ["platform@example.com"]
}Every input is in docs/configuration.md.
Amazon Q has to be allowed into your Slack workspace by hand once per AWS account; Terraform can't do the OAuth step. You need to be a Slack workspace admin, or have admin approval for apps.
- In Slack, choose Automations in the left pane (or More, then Automations). If Amazon Q Developer isn't listed, choose Browse Apps Directory, find it, and choose Add.
- In the AWS console, in the account you deploy to, open Amazon Q Developer in chat applications.
- Under Configure a chat client, choose Slack, then Configure.
- On Slack's page, pick the workspace in the drop-down at the top right, then choose Allow.
- Back in the console, copy the Workspace ID (it starts with
T) intoslack.workspace_id. - In Slack, open the channel, choose its name, and copy the Channel ID at the bottom of
the About tab (it starts with
C) intoslack.channel_id. - In the channel, type
/invite @Amazon Qand choose Invite Them. A private channel needs this before Amazon Q can post to it.
Then terraform apply. Don't create a channel configuration in the console: the module creates
it, with a channel role and guardrail that allow only the dev box commands.
| Tag | Meaning |
|---|---|
devbox:managed = true |
The module may start, stop and report this instance or Auto Scaling group. Required. |
devbox:owner = U0123ABCD |
The owner's Slack member ID (profile, ⋮, Copy member ID), mentioned in notices. |
devbox:name = alice-gpu |
Its name in Slack commands. Defaults to the Name tag, else the instance ID or group name. Letters, digits, ., _ and -, up to 64. |
devbox:autostart = true |
Start it on weekday mornings, if the module stopped it. |
devbox:idle-minutes = 120 or off |
This box's idle time before a warning; off disables idle stops (the nightly stop still applies). |
On an Auto Scaling group, tag the group, and set devbox:managed with PropagateAtLaunch so
its instances carry it too: IAM lets the module touch only tagged instances.
The module stops and starts boxes behind your infrastructure code's back. Make the code ignore what that changes, or its next apply undoes it, or worse:
aws_instancewithassociate_public_ip_address = true: a stopped instance has no public IP, and the AWS provider plans to replace the instance (a new instance, the root volume gone). Addlifecycle { ignore_changes = [associate_public_ip_address] }, or let the subnet assign public IPs instead.aws_autoscaling_group: while the group is stopped its min and desired capacity are 0, and an apply would set them back and launch new instances next to the ones in Standby. Addlifecycle { ignore_changes = [min_size, desired_capacity] }.- Don't manage the box with
aws_ec2_instance_state.
Boxes need the SSM agent online (Amazon Linux and Ubuntu AMIs have it; the instance profile
needs AmazonSSMManagedInstanceCore) for the probe. Without it the module still works from
CloudWatch metrics, and says so once a day.
@Amazon Q run devbox-list every managed box and whether it's running
@Amazon Q run devbox-status alice-gpu state, lease, warning, idle time
@Amazon Q run devbox-start alice-gpu start; it stays up at least the grace period
@Amazon Q run devbox-stop alice-gpu stop now, no warning
@Amazon Q run devbox-extend alice-gpu 3 keep it up 3 more hours (up to max_extension_hours)
Amazon Q prints each reply as the function's raw JSON string, so replies are one line. Start and stop return straight away; the channel hears when the box is stopping or starting, with the owner mentioned.
Warnings also carry two buttons, Keep alive 2h (devbox-extend for 2 hours, or
max_extension_hours if lower) and Stop now (devbox-stop). Choose one, confirm, then
choose Run. The warning still lists the matching commands.
Every 5 minutes the evaluator looks at each running box:
- CloudWatch: average
CPUUtilizationandNetworkIn + NetworkOutover the last 10 to 15 minutes. Belowidle.cpu_percentandidle.network_kbpscounts as idle; no datapoints count as active. - Session Manager: any active session is activity.
- The probe, an SSM Run Command document the module installs: authenticated SSH sessions
on any port (sshd's
sshd: user@pts/0anduser@nottyprocesses), users inwho, and the busiest GPU fromnvidia-smi. Any of these is activity. It also finds remote IDE servers (vscode-server, JetBrains), which the evaluator logs when a box is judged idle, but doesn't count them: those servers keep running after the laptop closes. - GPU metrics from the CloudWatch agent (
nvidia_smi_utilization_gpu), only when the probe can't run.
A box idle for idle.minutes gets a warning; if it's still idle idle.warn_minutes later, it
stops. Activity at any tick in between withdraws the warning.
Blind spots:
- VS Code Tunnels connect outbound, so there is no SSH session and no login to see. Its
CPU may not cross the threshold while you read code. Use
devbox-extend. - Remote desktops and open ports: an RDP, VNC or DCV desktop, or Jupyter reached over an
open port rather than an SSH tunnel, is seen only through network traffic above
idle.network_kbps. A desktop left still sends little. Usedevbox-extend. - Long jobs that idle the CPU (waiting on a download, a paused training run) look idle. Use
devbox-extend, or tag the boxdevbox:idle-minutes = off. - A failing probe counts as activity three times running, then the module falls back to metrics and tells the owner.
Starting a box, from Slack, the console or the morning start, gives it a grace period of
max(idle.minutes, 30) minutes before idle warnings can begin.
A group is one box. It's idle only when every InService instance is idle.
- Stop: the module saves the group's min, max and desired capacity, lowers min to 0, moves the InService instances to Standby (decrementing desired capacity), and stops them. The disks survive. Health checks ignore Standby instances.
- Start: it starts the instances, waits for them to run (the next tick), moves them out of Standby, and restores the saved capacity.
Caveats:
- Scaling policies and scheduled actions can launch replacements while the group is "stopped". Suspend them, or don't put such a group under the module.
- Scale-in protection and warm pools aren't supported: AWS refusals are reported, and a stop
that never finishes gets a notice after 15 minutes. A group left half-way by hand shows as
mixedindevbox-listand is left alone. - Spot groups aren't supported. EC2 can't stop a group's Spot instances (they are one-time requests), nor an instance-store root volume. The module refuses such a group before touching it. If AWS refuses a stop part-way anyway, the module puts the instances back in service at the saved capacity, so the group keeps running as before; a refused return to service stops the instances still in Standby again.
For 20 boxes, about $5 to $8 a month:
- GetMetricData: $0.01 per 1,000 metrics; 20 boxes × 3 metrics × 288 runs a day is about $5 a month.
- Lambda: the evaluator runs 288 times a day for 10 to 70 seconds (it waits for the probe) at 256 MB on arm64, about $1 to $2 a month before the free tier.
- Alarms: three, $0.30 a month. DynamoDB, SNS, Run Command on EC2 and Amazon Q: cents or free.
Stopping one g5.xlarge for one night (about $9) pays for more than a month of it.
- Terraform >= 1.10 or OpenTofu, and the AWS provider >= 6.41.
- The evaluator reserves 1 concurrent execution; AWS keeps 10 unreserved, so an account limited to 10 concurrent executions must raise it first.
- Instances whose EBS volumes use a customer managed KMS key can only be started if the key policy lets the module's roles create grants. See docs/operations.md.
- How it works: the rules, state, signals and the Slack wiring.
- Configuration: inputs, outputs, deployer permissions, regions.
- Operations: status, extending, alarms, troubleshooting, removal.
- Development: layout, tests, testing against AWS.
- Security