Running a Simple Test Service on AWS ECS Fargate


by Thomas Tran



I recently needed a small ECS service that would just run a bash loop inside a container. The goal was pretty simple, but I ran into a few ECS details that weren’t immediately obvious. I figured I’d write them down in case they’re useful to someone else who needs a quick throwaway ECS service for testing.

I was working on some code that automates forcing an ECS service to redeploy so it picks up new values for certain environment variables. To test that end to end, I needed a container that would continuously print a secret value to the logs. After rotating the secret, I could then check the logs and confirm that the new task picked up the new value.

The container itself is about as simple as it gets. It’s just a bash one-liner running in a loop and printing an environment variable every 30 seconds.

Before getting into the actual setup, there are three ECS concepts that caused me some confusion at first:

  1. Cluster: An ECS cluster is basically a logical grouping for your services and tasks. It doesn’t actually run anything by itself. Think of it as a namespace that contains the things you’re running.

  2. Task Definition: This is the configuration for the container. It specifies things like the image, command, CPU, memory, and environment variables. You don’t really “run” a task definition directly. Instead, ECS creates revisions of the task definition, and your service runs one of those revisions.

  3. Service: This is what keeps the container running. A service points to a particular task definition revision and maintains the desired number of running tasks. If a task stops unexpectedly, the service will start another one.

Step 1: Create a Cluster

Go to the ECS console and click Create Cluster.

Give the cluster a name. I used test-cluser for this example. For the infrastructure, select AWS Fargate. Fargate takes care of the underlying compute for you, so you don’t have to manage EC2 instances yourself. For a small test service like this, it’s the simplest option.

I left the other settings at their defaults and created the cluster. It took about 30 seconds.

Step 2: Create a Task Definition

This is where most of the configuration happens.

Go to Task Definitions and click Create new task definition.

Task definition family name. I used test-definition. ECS uses this as the base name and creates a new revision whenever you create another version of the configuration.

Launch type. Select AWS Fargate.

CPU and memory. This container isn’t doing much, so there’s no reason to give it a lot of resources. I used 0.25 vCPU and 0.5 GB of memory.

Task execution role. This is the IAM role ECS uses for things like pulling the container image and retrieving secrets from AWS Secrets Manager. If you don’t already have one, ECS can create the default ecsTaskExecutionRole.

Since my container needed to read a secret from Secrets Manager, I also made sure the role had secretsmanager:GetSecretValue permission for the secret ARN. I’ll get into that more below.

Configuring the Container

The task definition can contain one or more containers. I only needed one.

Container name. I called mine secret-test.

Image URI. I used the public Amazon Linux image:

public.ecr.aws/amazonlinux/amazonlinux:2023

I went with the regular 2023 image instead of 2023-minimal because I wanted curl available for some HTTP testing later. The minimal image doesn’t include it. For a little test container like this, I wasn’t concerned about the difference in image size.

Docker configuration - Command. This is the command the container runs when it starts. One slightly confusing thing about the ECS console is that the command isn’t entered like a normal shell command. It expects the individual command arguments to be comma-separated.

Here’s what I used:

/bin/bash,-c,while true; do echo "=== $(date) ==="; echo "Client ID: $KC_CLIENT_ID - Secret value: $CLIENT_SECRET"; echo "---"; sleep 30; done

The /bin/bash,-c portion tells the container to run bash and treat the next argument as the command to execute. The rest is just an infinite loop that prints the date, client ID, and secret value, then waits 30 seconds before doing it again.

The comma-separated format was one of the things that initially tripped me up. If you’re used to a Dockerfile where CMD looks something like ["/bin/bash", "-c", "..."], the ECS console is basically asking you to enter those array elements separated by commas, without spaces around them.

Environment Variables

Under the container’s environment variables, I added these two:

Key Value Type Value
CLIENT_SECRET ValueFrom The ARN of my Secrets Manager secret
KC_CLIENT_ID Value oidc-client-secret-rotation

The difference between Value and ValueFrom is important.

Value is just a regular string that ECS passes to the container.

ValueFrom tells ECS to get the value from another service, in this case AWS Secrets Manager. When the task starts, ECS retrieves the secret and makes the plaintext value available to the container as an environment variable.

That means the actual secret isn’t baked into the container image or stored directly in the task definition. The task definition contains the ARN, and the value is retrieved when the task starts.

For ValueFrom to work, the task execution role needs secretsmanager:GetSecretValue. If the secret uses a customer-managed KMS key instead of the default AWS-managed key, the role also needs kms:Decrypt.

I missed that permission the first time. The result wasn’t especially helpful: the task just kept failing to start, and the service events didn’t give me a particularly clear explanation. Once I added the required permission to the execution role, it started working.

Logging

Under the logging settings, enable CloudWatch Logs. ECS can create the log group for you.

This is important for this particular test because the container is writing everything to stdout. If CloudWatch logging isn’t configured, there’s nowhere useful to look for that output.

Once everything is configured, create the task definition. You should see it listed as revision 1.

Step 3: Create a Service

Go back to your cluster and click Create under the Services section.

Launch type. Select Fargate again.

Task definition. Select the task definition you just created. It should use the latest revision.

Service name. I used kc-secret-rotation-test-svc.

Desired tasks. Set this to 1. There’s no need to run more than one copy of this container.

Networking. Select a VPC, subnet, and security group.

For this test, the networking setup doesn’t need to be complicated. The important part is that the task has outbound internet access so it can pull the image from the public ECR registry.

A public subnet with an internet gateway works. A private subnet with a NAT gateway works too. A private subnet without a NAT gateway won’t work because the task won’t be able to reach the public ECR registry to pull the image.

If you’re just trying to get the test running and aren’t sure about the networking setup, a public subnet with a public IP is the simplest place to start. You can tighten the setup later if needed.

Create the service. After a minute or two, you should see a task under the Tasks tab with a status of RUNNING.

Step 4: Check the Logs

Click the running task and open the Logs tab. You should see something along these lines:

=== Thu Feb 26 14:19:28 UTC 2026 ===
Client ID: oidc-client-secret-rotation - Secret value: Upm9Eb4a8PefEuuCq6Ei3ZiggqrePP9C
---

=== Thu Feb 26 14:19:58 UTC 2026 ===
Client ID: oidc-client-secret-rotation - Secret value: Upm9Eb4a8PefEuuCq6Ei3ZiggqrePP9C
---

At this point, the test service is doing what we need. The container stays running and prints the environment variable every 30 seconds.

If you rotate the secret and then force a new deployment, the new task should pick up the new secret value. You can confirm that by watching the logs.

Updating the Task Definition

You’ll probably need to change something while you’re setting this up. Maybe the command needs fixing, or you want to add another environment variable.

The workflow is:

  1. Go to Task Definitions and select your task definition.
  2. Click Create new revision.
  3. Make your changes.
  4. Click Create. This creates the next revision.
  5. Go back to your service and click Update.
  6. Select the new task definition revision.
  7. Click Update.

ECS will then perform a rolling deployment. It starts a task using the new revision and eventually stops the old task.

For a service with just one task, the whole thing should take roughly a minute.

One easy thing to miss is that creating a new task definition revision does not automatically change the task definition used by an existing service. You still have to update the service to point to the new revision.

That behavior makes sense for production, but it’s easy to forget when you’re just iterating on a test container.

Forcing a New Deployment

Sometimes you don’t need to change the task definition at all. You just want ECS to restart the task.

That’s useful for this particular test because changing a secret in Secrets Manager doesn’t automatically change the value inside an already-running container.

From the service page, click Update, select Force new deployment, and then click Update.

ECS will replace the existing task with a new one. When the new task starts, ECS resolves the ValueFrom environment variables again, so the container gets the current value from Secrets Manager.

This is also what my Lambda function does programmatically when it rotates the secret. It calls the ECS UpdateService API with forceNewDeployment: true.

In other words, the Lambda is doing the same thing as checking that box in the console.

And that’s it! You’ve got yourself a running ECS cluster.