Introduction to Fargate Security
AWS Fargate is a serverless compute engine for containers that works with both Amazon ECS and Amazon EKS. When you run containers on Fargate, AWS manages the underlying infrastructure—you don't see or control the EC2 instances. This abstraction brings tremendous operational benefits, but it also shifts the security model in important ways. Security in Fargate revolves around two primary pillars: IAM Policies (who and what your containers can access) and Network Security (how your containers communicate with the world and each other). Understanding both is essential to running secure, production-grade container workloads.
Why Fargate Security Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →With Fargate, you relinquish direct control over the host OS and instance-level firewall rules. This means traditional host-based security approaches no longer apply. Instead, security becomes entirely identity-based and network-based. A misconfigured IAM role can grant a compromised container access to your entire AWS account. An improperly configured security group can expose your internal services to the internet. Given that containers often run microservices handling sensitive data, the blast radius of a single vulnerability can be catastrophic. Properly configuring IAM policies and network controls is not optional—it is the foundation of your defense-in-depth strategy on Fargate.
IAM Policies for Fargate: The Two-Role Model
Fargate uses two distinct IAM roles per task, and confusing them is a common source of security issues:
- Task Execution Role: Grants permissions to the Fargate infrastructure itself to set up your container. This includes pulling images from ECR, writing logs to CloudWatch, and retrieving secrets from Secrets Manager or SSM Parameter Store.
- Task Role: Grants permissions to the application code running inside the container. This is the role your code assumes when making AWS API calls via the SDK or CLI.
Separating these two concerns follows the principle of least privilege and limits the damage if application code is compromised.
Task Execution Role Example
Below is a minimal but complete task execution role policy. It allows Fargate to pull images from ECR and write logs to CloudWatch. Notice that it does not grant access to sensitive data stores like DynamoDB or S3—that belongs to the task role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
Attach this policy to a role named something like fargate-task-execution-role. You can also add statements for Secrets Manager if your tasks reference sensitive environment variables stored there.
Task Role Example: Least Privilege for Application Code
Your application code likely needs to interact with other AWS services. Here is an example task role policy for a container that reads from an S3 bucket and writes to a DynamoDB table. Notice the use of specific resource ARNs rather than wildcards:
{
"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/*"
]
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/MyTable"
}
]
}
By scoping permissions to exact resources, you ensure that even if an attacker gains code execution within the container, they cannot enumerate or access other buckets or tables in your account.
Attaching Roles in a Task Definition
In your ECS task definition JSON, you reference both roles. Here's a snippet showing how:
{
"family": "my-app-task",
"networkMode": "awsvpc",
"executionRoleArn": "arn:aws:iam::123456789012:role/fargate-task-execution-role",
"taskRoleArn": "arn:aws:iam::123456789012:role/fargate-task-role",
"containerDefinitions": [
{
"name": "my-app-container",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest",
"essential": true,
"memory": 512,
"cpu": 256,
"environment": [
{
"name": "BUCKET_NAME",
"value": "my-app-bucket"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-app",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
The executionRoleArn handles setup, and taskRoleArn is what your application uses at runtime. Always verify that sensitive environment variables and secrets are not accessible via the task execution role alone if they are meant for application use.
Using Secrets Manager with Fargate
When injecting secrets, you must grant the task execution role permission to retrieve the secret, and then reference it in the container definition. Here is an extended task execution role policy snippet and the corresponding container definition update:
// Additional statement for task execution role:
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"kms:Decrypt"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app-secret-*",
"arn:aws:kms:us-east-1:123456789012:key/your-kms-key-id"
]
}
// Container definition secrets block:
"secrets": [
{
"name": "DATABASE_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:my-app-secret-db"
}
]
This ensures secrets are injected as environment variables at container startup without hardcoding them in the task definition. The task execution role must have decrypt permission on the KMS key if the secret is encrypted with a customer-managed key.
Network Security for Fargate Tasks
Fargate tasks always run in the awsvpc network mode. Each task gets its own elastic network interface (ENI) with a private IP address. This model provides powerful isolation and fine-grained network control, but it requires deliberate configuration.
VPC, Subnets, and Security Groups
Your Fargate tasks must run inside a VPC. You specify which subnets and security groups to associate with each task. The fundamental decision is whether to place tasks in public or private subnets:
- Public subnets: Have a route to an Internet Gateway. Tasks can directly access the internet and can be directly reachable from the internet if assigned a public IP. Only use this for internet-facing services behind a load balancer.
- Private subnets: No direct route to the internet. Tasks must use a NAT Gateway or VPC endpoints to reach external services. This is the recommended configuration for most workloads, especially backend services.
Security Group Configuration
Security groups act as a stateful virtual firewall for each ENI. Here is how to set up a typical three-tier architecture using CloudFormation snippet to demonstrate the security group rules:
# ALB Security Group (public facing)
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTP and HTTPS from anywhere
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
# Fargate Service Security Group
FargateServiceSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow traffic from ALB only
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
DestinationSecurityGroupId: !Ref DatabaseSG
# Database Security Group
DatabaseSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow MySQL from Fargate service only
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref FargateServiceSG
This configuration ensures that the ALB accepts internet traffic on ports 80/443, the Fargate service only accepts traffic from the ALB on port 8080, and the database only accepts traffic from the Fargate service on port 3306. No other ingress paths exist.
Network ACLs as a Secondary Boundary
Network ACLs (NACLs) are stateless and act at the subnet level. While security groups are the primary control, NACLs can provide an additional layer of defense. For example, you might configure a NACL on your private subnet to explicitly deny outbound traffic to known malicious IP ranges or restrict ephemeral port ranges:
# Example NACL for a private subnet
PrivateSubnetNACL:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref VPCId
Tags:
- Key: Name
Value: private-subnet-nacl
# Inbound rule: Allow only from VPC CIDR
PrivateSubnetNACLInbound:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetNACL
RuleNumber: 100
Protocol: -1
RuleAction: allow
CidrBlock: 10.0.0.0/16
PortRange:
From: 0
To: 65535
# Outbound rule: Allow only to VPC CIDR and specific required endpoints
PrivateSubnetNACLOutbound:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetNACL
RuleNumber: 100
Protocol: -1
RuleAction: allow
CidrBlock: 10.0.0.0/16
PortRange:
From: 0
To: 65535
# Explicit deny for all other traffic (default rule)
# Additional explicit rules can block specific CIDRs
Remember that NACLs are stateless—you must allow both inbound and outbound ephemeral ports for return traffic. Security groups handle this automatically, which is why they are preferred as the primary mechanism.
VPC Endpoints for AWS Services
Fargate tasks in private subnets need access to AWS APIs (ECR, CloudWatch, S3, etc.). Without a NAT Gateway, they cannot reach the public endpoints. VPC endpoints bring AWS services directly into your VPC over the AWS backbone, eliminating internet exposure entirely. Here is how to create endpoints for ECR and CloudWatch (required for Fargate to function) and S3:
# VPC Endpoints for Fargate essential services
ECREndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: com.amazonaws.us-east-1.ecr.api
VpcEndpointType: Interface
SubnetIds: !Ref PrivateSubnetIds
SecurityGroupIds: [!Ref VpcEndpointSG]
PrivateDnsEnabled: true
ECRDockerEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: com.amazonaws.us-east-1.ecr.dkr
VpcEndpointType: Interface
SubnetIds: !Ref PrivateSubnetIds
SecurityGroupIds: [!Ref VpcEndpointSG]
PrivateDnsEnabled: true
CloudWatchEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: com.amazonaws.us-east-1.logs
VpcEndpointType: Interface
SubnetIds: !Ref PrivateSubnetIds
SecurityGroupIds: [!Ref VpcEndpointSG]
PrivateDnsEnabled: true
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPCId
ServiceName: com.amazonaws.us-east-1.s3