Stop Paying for Idle AWS ECS Services: Automate Start/Stop Scheduling

Today in this blog post I’ll explain what steps I took to shut down my testing environment outside of business hours.
When I was designing the solution I wanted to make a reusable, single Lambda Function, for multiple services, multiple actions (start/stop), and multiple clusters - with that, I ended up using a Lambda Function that gets triggered by an EventBridge Rule submitted to an SNS topic.
Through this, we can control which services we start or stop, for which cluster, and the count of tasks running within the service.
Enough talk, let's get a bit technical, shall we?
So, first things first. We need to create an SNS topic that'll forward the EventBridge's messages to the Lambda. The topic needs to be standard type, and it's on you whether you want to use encryption for it or not.

Once that has been done, we can proceed to create the Lambda Function. The code for it you can find it below:
import json
import boto3
import datetime
ecs_client = boto3.client('ecs')
def lambda_handler(event, context):
try:
print("Received event: " + json.dumps(event, indent=2))
# Extract the SNS message
for record in event['Records']:
sns_message = json.loads(record['Sns']['Message'])
cluster_name = sns_message.get('cluster_name')
service_name = sns_message.get('service_name')
action = sns_message.get('action')
if not cluster_name or not service_name or action not in ['start', 'stop']:
print("Invalid message format")
return
desired_count = 1 if action == 'start' else 0
ecs_client.update_service(
cluster=cluster_name,
service=service_name,
desiredCount=desired_count
)
timestamp = datetime.datetime.utcnow().isoformat()
print(f"{timestamp} - Successfully {action}ed service {service_name} in cluster {cluster_name}")
except Exception as e:
timestamp = datetime.datetime.utcnow().isoformat()
print(f"{timestamp} - Error: {str(e)}")
You can choose whatever options you want for the function, the only crucial part is that the Runtime needs to be Python 3.13.
Once the function has been created we need to add permissions to its role.. below you can find the permissions that it needs to interact with your ECS cluster.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ECS_Scoped",
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"ecs:DescribeServices"
],
"Resource": "arn:aws:ecs:<region>:<account_id>:service/<cluster_name>/<service_name>"
}
]
}
And, of course, the permissions to ingest the events from the SNS topic. This one is added through Resource-based policy statements.

The only 3 important steps here are, to select AWS service, then for Source ARN add the ARN of the topic that we've created earlier, and for Action choose lambda:InvokeFunction.
Then, once that's done we can go back to the SNS topic, and we create a new subscription.
The only last step that we need to do here is go to EventBridge, and create a new Rule - you can name it and configure it as you feel like however, the important thing here is, when you're configuring the event pattern to select Custom pattern (JSON editor). Here we'll add the "payload" that'll be sent to the SNS topic and eventually to the Lambda. And, the Rule type needs to be Schedule.
You can choose whatever suits your needs, either "A fine-grained schedule that runs at a specific time, such as 8:00 a.m. PST on the first Monday of every month", or "A schedule that runs at a regular rate, such as every 10 minutes.". But if you plan to schedule it at a certain time, the first one would work for you.
Then for Target types we select AWS Service -> SNS Topic and select the actual topic. Once that has been done we can continue to the Additional settings section where for Configure target input we select Input Transformer and for which we select Constant (JSON text).
Then we need to add the following payload and adjust as you need:
{
"cluster_name": "<cluster_name>",
"service_name": "<service_name>",
"action": "<start/stop>"
}
And we're done! If you followed all the steps that I did it should work although I have to admit, I haven't deployed the SNS topic using encryption just because. If you do, you need to cater for it within the key policy.
As always, thank you for reading, and good luck experimenting.