← Back to DevBytes

ECS Security: IAM Policies and Network Security

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.

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

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.

πŸš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started β€” $23.99/mo
← Back to all articles