Skip to content

About

Dev EC2 boxes and Auto Scaling groups that stop when nobody is using them, with Slack start/stop/extend through Amazon Q Developer. One terraform apply.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

terraform-aws-devboxes

CI License: MIT

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 run devbox-extend. The only stop without a warning is an explicit devbox-stop.
  • From Slack: devbox-list, devbox-status, devbox-start, devbox-stop and devbox-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/aws and hashicorp/archive providers. No Docker, no build step.

Read this first: what it is and isn't

  • 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, kept log_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_ids and 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-extend before 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.

Quick start

Option 1: clone and apply

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 apply

State 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.

Option 2: use it as a module

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.

One-time Slack setup

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.

  1. 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.
  2. In the AWS console, in the account you deploy to, open Amazon Q Developer in chat applications.
  3. Under Configure a chat client, choose Slack, then Configure.
  4. On Slack's page, pick the workspace in the drop-down at the top right, then choose Allow.
  5. Back in the console, copy the Workspace ID (it starts with T) into slack.workspace_id.
  6. In Slack, open the channel, choose its name, and copy the Channel ID at the bottom of the About tab (it starts with C) into slack.channel_id.
  7. In the channel, type /invite @Amazon Q and 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 your boxes

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.

If Terraform (or CloudFormation, or CDK) manages the box

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_instance with associate_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). Add lifecycle { 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. Add lifecycle { 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.

Slack commands

@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.

How idle works

Every 5 minutes the evaluator looks at each running box:

  • CloudWatch: average CPUUtilization and NetworkIn + NetworkOut over the last 10 to 15 minutes. Below idle.cpu_percent and idle.network_kbps counts 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/0 and user@notty processes), users in who, and the busiest GPU from nvidia-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. Use devbox-extend.
  • Long jobs that idle the CPU (waiting on a download, a paused training run) look idle. Use devbox-extend, or tag the box devbox: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.

Auto Scaling groups

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 mixed in devbox-list and 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.

What it costs to run

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.

Prerequisites

  • 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.

Documentation

License

MIT

About

Dev EC2 boxes and Auto Scaling groups that stop when nobody is using them, with Slack start/stop/extend through Amazon Q Developer. One terraform apply.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages