ECS Security: IAM Policies and Network Security
When you run containerized workloads on Amazon ECS, security becomes a shared responsibility between AWS and you. Two critical pillars define how you protect your containers: IAM policies that control who can do what, and network security that governs how traffic reaches your tasks. This tutorial covers both aspects in depth, with practical code examples and best practices you can apply immediately.
Understanding IAM for ECS
π Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →ECS uses several distinct IAM roles and policies. Misconfiguring them is a common source of security gaps. Let's break down each type, why it matters, and how to write the corresponding policies.
Task Execution Role
The task execution role is assumed by the ECS agent (or Fargate infrastructure) during the task lifecycle. It grants permissions needed to pull container images from Amazon ECR, write logs to CloudWatch, and access other AWS resources required to set up the container. This role never gives permissions to your application code.
Why it matters: If the execution role lacks permissions, your task will fail to start, and youβll see errors like "CannotPullContainerError". On the other hand, granting too many permissions here exposes the underlying infrastructure unnecessarily.
Example policy attached to the execution role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
This policy lets ECS authenticate with ECR, fetch container image layers, and send logs to CloudWatch.
For Fargate, itβs also common to add ec2:DescribeTags or ssm:GetParameters if you
inject secrets from Systems Manager Parameter Store.
Task Role
The task role is assumed directly by the container application inside the task. It grants permissions your application needs to interact with AWS services β for example, reading objects from S3, querying DynamoDB, or publishing messages to SNS. This role must follow the principle of least privilege.
Example task role policy granting read access to a specific S3 bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
}
]
}
Attach this policy to the task role, and your container can safely read data from my-app-bucket
without touching other buckets.
Service-Linked Role and Classic ECS Service Role
AWS provides a service-linked role named AWSServiceRoleForECS.
It automatically appears in your account when needed and allows ECS to manage resources on your behalf β
like creating ENIs, updating load balancer targets, or scaling tasks. You don't need to create or attach it manually.
For classic load balancers (CLB) and older ECS features, you may still see references to an
ECS service role (ecsServiceRole). In modern architectures, the service-linked role
covers almost everything, and the manual service role is rarely required.
Why it matters: Rely on the service-linked role to avoid missing permissions that would
cause service creation failures.
IAM Policies for ECS Actions (Developer & CI/CD Permissions)
Developers or CI/CD pipelines need permissions to deploy, update, and monitor ECS services.
Granting full ecs:* is dangerous. Instead, scope actions to specific clusters and services
using resource ARNs and condition keys.
Example IAM policy for a developer deploying to a production cluster:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:DescribeTaskDefinition",
"ecs:ListTaskDefinitions"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecs:CreateService",
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DeleteService",
"ecs:RunTask",
"ecs:StopTask",
"ecs:DescribeTasks"
],
"Resource": [
"arn:aws:ecs:us-east-1:123456789012:cluster/production-cluster",
"arn:aws:ecs:us-east-1:123456789012:service/production-cluster/*"
],
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
This limits the user to actions only on the production cluster, prevents them from touching other clusters,
and uses a condition to restrict the region. Always combine resource restrictions with conditions like
ecs:Cluster, aws:RequestedRegion, or aws:PrincipalTag for tighter control.
Network Security for ECS
IAM secures who can deploy and what containers can access. Network security defines how traffic reaches your tasks and how tasks communicate with each other and the outside world. A strong network posture minimizes exposure and limits blast radius.
VPC and Subnet Strategy
Place your ECS tasks in private subnets (those without a direct route to an Internet Gateway). Public-facing services should sit behind an Application Load Balancer (ALB) or Network Load Balancer (NLB) deployed in public subnets. This way, your containers never receive unsolicited traffic directly from the internet.
For Fargate tasks, the awsvpc network mode is required. It provides each task a dedicated
elastic network interface (ENI) and its own private IP address, enabling per-task security group rules.
For EC2 launch type, awsvpc is strongly recommended for the same isolation benefits.
Security Groups for Containers
Security groups act as stateful virtual firewalls. Define minimal ingress (inbound) and egress (outbound) rules for every ECS task. A typical pattern: allow inbound traffic only from the ALB security group on the container port, and restrict outbound traffic to necessary destinations.
AWS CLI example to create a task security group and allow traffic from an ALB:
# Create security group in your VPC
aws ec2 create-security-group --group-name ecs-task-sg \
--description "Security group for ECS tasks" \
--vpc-id vpc-0abc123def456
# Add ingress rule: allow ALB security group on port 8080
aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef \
--protocol tcp --port 8080 \
--source-group sg-alb-00123456789
CloudFormation snippet for the same setup:
TaskSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: ECS task security group
VpcId: !Ref VpcId
SecurityGroupIngress:
- SourceSecurityGroupId: !Ref AlbSecurityGroupId
IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SecurityGroupEgress:
- CidrIp: 0.0.0.0/0
IpProtocol: tcp
FromPort: 443
ToPort: 443
Description: Allow HTTPS outbound to internet
The egress rule here limits outbound traffic to HTTPS (port 443) only, preventing data exfiltration over other protocols. Always apply the principle of least privilege to both ingress and egress.
Network ACLs for Defense-in-Depth
Network ACLs (NACLs) are stateless and evaluate both inbound and outbound traffic separately. While security groups are sufficient for most use cases, NACLs add an extra layer of subnet-level protection. For example, you can explicitly deny inbound traffic from the internet to your private subnets, even if a security group accidentally becomes too open.
Example NACL entry blocking all inbound traffic from 0.0.0.0/0:
PrivateSubnetNetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VpcId
Tags:
- Key: Name
Value: private-subnet-nacl
DenyInternetInbound:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetNetworkAcl
RuleNumber: 100
Protocol: -1 # all protocols
RuleAction: deny
CidrBlock: 0.0.0.0/0
Egress: false # applies to inbound traffic
Combine this with an allow rule for the VPC CIDR range to permit internal communication. NACLs are evaluated in order by rule number, so put deny rules first.
VPC Endpoints and AWS PrivateLink
By default, tasks in private subnets need a NAT Gateway or proxy to reach AWS services like ECR, CloudWatch, or S3 over the internet. This not only incurs costs but also exposes traffic. Instead, use VPC endpoints to route requests through the AWS backbone privately.
- Gateway endpoints β for S3 and DynamoDB, free and easy to add.
- Interface endpoints (PrivateLink) β for ECR, CloudWatch Logs, Secrets Manager, and many others.
Example CloudFormation for an interface endpoint to ECR:
EcrApiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: com.amazonaws.us-east-1.ecr.api
VpcId: !Ref VpcId
PrivateDnsEnabled: true
SecurityGroupIds: [!Ref EndpointSecurityGroupId]
SubnetIds: [!Ref PrivateSubnet1, !Ref PrivateSubnet2]
EcrDkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: com.amazonaws.us-east-1.ecr.dkr
VpcId: !Ref VpcId
PrivateDnsEnabled: true
SecurityGroupIds: [!Ref EndpointSecurityGroupId]
SubnetIds: [!Ref PrivateSubnet1, !Ref PrivateSubnet2]
With PrivateDnsEnabled: true, DNS resolution for ECR inside the VPC points to the endpoint
automatically β no code changes needed. Do the same for com.amazonaws.region.logs and
com.amazonaws.region.secretsmanager. This keeps all container traffic off the public internet.
Best Practices for ECS Security
- Separate execution and task roles β never mix infrastructure permissions with application permissions.
- Apply least privilege to IAM policies β restrict actions to specific clusters, services, and regions using resource ARNs and conditions.
- Use
awsvpcnetwork mode β provides per-task security groups and fine-grained network isolation. - Run tasks in private subnets β expose only through a load balancer in public subnets when necessary.
- Enforce strict security group rules β allow only required ports and sources; restrict egress to known destinations.
- Add Network ACLs as a second layer β deny unwanted traffic at the subnet boundary.
- Deploy VPC endpoints β keep ECR, CloudWatch, and other AWS service traffic within the AWS network.
- Enable VPC Flow Logs β capture network traffic metadata for analysis and intrusion detection.
- Encrypt EBS volumes (EC2 launch type) and enable ECS logging to monitor container output.
- Regularly audit IAM roles using IAM Access Analyzer and AWS Config rules.
Conclusion
Securing ECS requires a layered approach that combines identity and network controls. By carefully crafting IAM policies β separating execution roles from task roles, and scoping developer permissions β you prevent unauthorized actions. On the network side, placing tasks in private subnets, enforcing strict security group and NACL rules, and routing traffic through VPC endpoints drastically reduces the attack surface. Start with these patterns, iterate with automated audits, and youβll build a robust container security posture that evolves with your workloads.