ECS Task Definitions Have Containers with Read-Only Root Filesystems
Overview
This check verifies that all containers in your Amazon ECS task definitions have read-only root filesystems enabled. When a container's root filesystem is read-only, it cannot modify its own system files at runtime, which significantly reduces the attack surface.
Risk
A writable root filesystem creates serious security vulnerabilities:
- Malware installation: Attackers can download and install malicious software
- Binary tampering: System binaries can be modified to create backdoors
- Persistence: Attackers can modify files to maintain access after restarts
- Data exfiltration: Sensitive files like logs or secrets could be accessed or modified
Making the root filesystem read-only prevents these attacks by ensuring containers run in an immutable state.
Remediation Steps
Prerequisites
You need:
- Access to the AWS Console with permissions to modify ECS task definitions
- Knowledge of which directories your application needs to write to (commonly
/tmp,/var/run, or application-specific paths)
CLI and IaC tool setup
For command-line remediation:
- AWS CLI v2 installed and configured
- Appropriate IAM permissions for
ecs:RegisterTaskDefinitionandecs:DescribeTaskDefinition
For infrastructure as code:
- Terraform 1.0+ with AWS provider 5.0+
- Or AWS CloudFormation access
AWS Console Method
- Open the Amazon ECS console at https://console.aws.amazon.com/ecs/
- In the left navigation, click Task definitions
- Select the task definition family you need to update
- Click Create new revision
- Scroll down to the Container definitions section
- Click on the container name to expand its settings
- Under Storage and Logging, find Read only root file system
- Toggle this setting to On
- If your application needs to write to specific directories:
- Scroll to the Storage section at the task level
- Add a volume (e.g., name it
tmp-volume) - Back in the container settings, under Mount points, add a mount:
- Source volume:
tmp-volume - Container path:
/tmp - Read only: Leave unchecked
- Source volume:
- Repeat steps 6-9 for each container in the task definition
- Click Create to register the new revision
- Update any services using this task definition to use the new revision
AWS CLI
Step 1: Get the current task definition
aws ecs describe-task-definition \
--task-definition <your-task-family> \
--region us-east-1 \
--query 'taskDefinition' > task-def.json
Step 2: Modify the container definitions
Edit task-def.json to add "readonlyRootFilesystem": true to each container, and add volume mounts for directories that need write access:
{
"containerDefinitions": [
{
"name": "app-container",
"image": "your-image:tag",
"readonlyRootFilesystem": true,
"mountPoints": [
{
"sourceVolume": "tmp-volume",
"containerPath": "/tmp",
"readOnly": false
}
]
}
],
"volumes": [
{
"name": "tmp-volume"
}
]
}
Step 3: Remove read-only fields before registering
The describe output includes fields that cannot be included when registering. Remove these fields:
taskDefinitionArnrevisionstatusrequiresAttributescompatibilitiesregisteredAtregisteredBy
Step 4: Register the new task definition
aws ecs register-task-definition \
--cli-input-json file://task-def.json \
--region us-east-1
Step 5: Update services (if applicable)
aws ecs update-service \
--cluster <your-cluster> \
--service <your-service> \
--task-definition <your-task-family> \
--region us-east-1
CloudFormation
AWSTemplateFormatVersion: '2010-09-09'
Description: ECS Task Definition with read-only root filesystem
Parameters:
TaskFamily:
Type: String
Description: The family name for the task definition
Default: my-secure-task
ContainerImage:
Type: String
Description: The container image to use
Default: nginx:latest
Resources:
ECSTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref TaskFamily
RequiresCompatibilities:
- FARGATE
NetworkMode: awsvpc
Cpu: '256'
Memory: '512'
ContainerDefinitions:
- Name: app-container
Image: !Ref ContainerImage
ReadonlyRootFilesystem: true
Essential: true
PortMappings:
- ContainerPort: 80
Protocol: tcp
MountPoints:
- SourceVolume: tmp-volume
ContainerPath: /tmp
ReadOnly: false
- SourceVolume: var-run-volume
ContainerPath: /var/run
ReadOnly: false
Volumes:
- Name: tmp-volume
- Name: var-run-volume
Outputs:
TaskDefinitionArn:
Description: The ARN of the task definition
Value: !Ref ECSTaskDefinition
Deploy with:
aws cloudformation deploy \
--template-file template.yaml \
--stack-name ecs-secure-task \
--parameter-overrides TaskFamily=my-secure-task ContainerImage=nginx:latest \
--region us-east-1
Terraform
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
variable "task_family" {
description = "The family name for the task definition"
type = string
default = "my-secure-task"
}
variable "container_image" {
description = "The container image to use"
type = string
default = "nginx:latest"
}
resource "aws_ecs_task_definition" "secure_task" {
family = var.task_family
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
container_definitions = jsonencode([
{
name = "app-container"
image = var.container_image
readonlyRootFilesystem = true
essential = true
portMappings = [
{
containerPort = 80
protocol = "tcp"
}
]
mountPoints = [
{
sourceVolume = "tmp-volume"
containerPath = "/tmp"
readOnly = false
},
{
sourceVolume = "var-run-volume"
containerPath = "/var/run"
readOnly = false
}
]
}
])
volume {
name = "tmp-volume"
}
volume {
name = "var-run-volume"
}
}
output "task_definition_arn" {
description = "The ARN of the task definition"
value = aws_ecs_task_definition.secure_task.arn
}
Apply with:
terraform init
terraform apply
Verification
After updating your task definition:
- Go to ECS > Task definitions in the AWS Console
- Select your task definition and click on the latest revision
- Expand each container definition
- Confirm that Read only root file system shows as Yes or true
CLI verification
aws ecs describe-task-definition \
--task-definition <your-task-family> \
--region us-east-1 \
--query 'taskDefinition.containerDefinitions[*].{Name:name,ReadOnlyRootFilesystem:readonlyRootFilesystem}'
Expected output shows true for all containers:
[
{
"Name": "app-container",
"ReadOnlyRootFilesystem": true
}
]
Additional Resources
- Amazon ECS Task Definition Parameters
- AWS Config Rule: ecs-containers-readonly-access
- AWS Security Hub ECS Controls
- Container Security Best Practices
Notes
-
Application compatibility: Some applications expect to write to the root filesystem. Test thoroughly in a non-production environment before applying this change to production workloads.
-
Common writable paths: Applications often need write access to:
/tmp- Temporary files/var/run- Runtime data (PID files, sockets)/var/log- Log files (consider using CloudWatch Logs instead)- Application-specific data directories
-
Layered security: For maximum protection, combine read-only root filesystems with:
- Running containers as non-root users
- Dropping unnecessary Linux capabilities
- Using immutable container images from trusted registries
-
Existing services: After registering a new task definition revision, you must update any running services to use the new revision. The old tasks will continue running until replaced.