Running development and test servers 24/7 can be unnecessarily expensive. According to AWS pricing, an m5.large instance in US-East-1 costs $0.096 per hour - that's $2.30 daily or $70 monthly per instance. By implementing a simple scheduling solution, you could potentially cut these costs by 60-70% by stopping instances during non-working hours.
AWS provides several built-in options for instance scheduling:
# Using AWS Instance Scheduler
{
"Description": "Start/stop EC2 instances",
"StartConfigurations": [
{
"Instance": "i-1234567890abcdef0",
"StartTime": "09:00",
"Timezone": "America/New_York"
}
],
"StopConfigurations": [
{
"Instance": "i-1234567890abcdef0",
"StopTime": "18:00",
"Timezone": "America/New_York"
}
]
}
For more control, create a Lambda function triggered by CloudWatch Events:
import boto3
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
# Start instances at 8 AM UTC
if event['detail']['time'] == '8:00':
response = ec2.start_instances(
InstanceIds=['i-1234567890abcdef0']
)
print(f"Started instances: {response}")
# Stop instances at 8 PM UTC
elif event['detail']['time'] == '20:00':
response = ec2.stop_instances(
InstanceIds=['i-1234567890abcdef0']
)
print(f"Stopped instances: {response}")
return {
'statusCode': 200,
'body': 'Instance scheduling completed'
}
For managing multiple instances dynamically, use instance tags:
import boto3
from datetime import datetime
def lambda_handler(event, context):
ec2 = boto3.client('ec2')
current_hour = datetime.now().hour
# Find instances with appropriate tags
instances = ec2.describe_instances(
Filters=[
{'Name': 'tag:Schedule', 'Values': ['business-hours']},
{'Name': 'instance-state-name', 'Values': ['running', 'stopped']}
]
).get('Reservations', [])
instance_ids = [i['InstanceId'] for r in instances for i in r['Instances']]
if current_hour == 8: # 8 AM
ec2.start_instances(InstanceIds=instance_ids)
elif current_hour == 20: # 8 PM
ec2.stop_instances(InstanceIds=instance_ids)
To verify your savings, use AWS Cost Explorer with the following filters:
- Filter by service: EC2
- Group by: Usage Type
- Time period: Compare before/after implementation
Remember to test your scheduling solution thoroughly before relying on it in production environments. Consider implementing notifications (via SNS) to alert you if instances fail to start/stop as expected.
Running EC2 instances 24/7 for development and testing environments can lead to unnecessary costs. By implementing automated start/stop schedules, you can potentially reduce your AWS bill by 50-70% for non-production instances. I've personally saved over $3,000 annually using this approach across 15 development instances.
AWS provides several native options for time-based instance management:
- AWS Instance Scheduler: A CloudFormation template that deploys the complete solution
- AWS Lambda + EventBridge: The most flexible and cost-effective approach
- Third-party tools: Like AWS Systems Manager Maintenance Windows
Here's a complete implementation using Python and AWS CDK:
import boto3
import os
ec2 = boto3.client('ec2')
def lambda_handler(event, context):
# Get instances with specific tag
response = ec2.describe_instances(Filters=[
{
'Name': 'tag:AutoStartStop',
'Values': ['true']
}
])
instances = []
for reservation in response['Reservations']:
for instance in reservation['Instances']:
instances.append(instance['InstanceId'])
# Determine action based on event
if 'start' in event['detail-type'].lower():
ec2.start_instances(InstanceIds=instances)
print(f"Started instances: {instances}")
elif 'stop' in event['detail-type'].lower():
ec2.stop_instances(InstanceIds=instances)
print(f"Stopped instances: {instances}")
Create two EventBridge rules to trigger the Lambda function:
Resources:
StartRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0 8 ? * MON-FRI *)" # 8 AM weekdays
State: ENABLED
Targets:
- Arn: !GetAtt InstanceScheduler.Arn
Input: '{"action":"start"}'
StopRule:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: "cron(0 18 ? * MON-FRI *)" # 6 PM weekdays
State: ENABLED
Targets:
- Arn: !GetAtt InstanceScheduler.Arn
Input: '{"action":"stop"}'
For more complex scheduling needs, consider:
- Different schedules for different instance types (dev vs test)
- Excluding instances during critical periods
- Adding delay between instance operations to avoid API throttling
- Integration with Slack/Teams for notifications
Set up CloudWatch Alarms to monitor:
aws cloudwatch put-metric-alarm \
--alarm-name "EC2-Scheduler-Failures" \
--alarm-description "Alarm when scheduler Lambda fails" \
--metric-name "Errors" \
--namespace "AWS/Lambda" \
--statistic "Sum" \
--period 300 \
--threshold 1 \
--comparison-operator "GreaterThanOrEqualToThreshold" \
--evaluation-periods 1 \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:MyNotificationTopic" \
--dimensions "Name=FunctionName,Value=InstanceScheduler"
Use AWS Cost Explorer to measure savings. Focus on these metrics:
- Instance running hours reduction
- EC2 cost savings by instance type
- Lambda invocation costs (typically under $0.01/month)