Skip to content

Ecs service worker

Bridge Network Mode and Service Connect

This module supports both awsvpc and bridge network modes for ECS worker tasks. Bridge mode is useful when running multiple services on a single EC2 instance to avoid ENI limits.

Network Mode Variables

Variable Description Default
network_mode Docker network mode (awsvpc or bridge) awsvpc
enable_service_connect Enable ECS Service Connect for service discovery false
service_connect_namespace_arn ARN of the Cloud Map namespace for Service Connect null

Bridge Mode Configuration

When using bridge mode: - Port mappings automatically use dynamic host ports (hostPort = 0) - Service Connect is required for inter-service communication - Subnet and security group configurations are not applied to tasks (they use the EC2 host's network)

Example: Bridge Mode with Service Connect

module "my_worker" {
  source = "./modules/ecs_service_worker"

  service_name = "my-worker"
  cluster_name = module.ecs_cluster.cluster_name

  # Bridge mode configuration
  network_mode                  = "bridge"
  enable_service_connect        = true
  service_connect_namespace_arn = module.ecs_cluster.namespace_arn

  # EC2 capacity provider
  capacity_provider_strategy = {
    ec2 = {
      capacity_provider = module.ecs_cluster.ec2_capacity_provider_name
      weight            = 100
    }
  }

  # Other required variables...
  container_image    = "my-worker-image:latest"
  subnet_ids         = var.private_subnet_ids
  security_group_ids = [var.security_group_id]
  env                = var.environment
}

Service Connect Benefits

ECS Service Connect provides: - Transparent service-to-service communication via Envoy proxy sidecars - Same service URLs work in both Fargate (awsvpc) and EC2 (bridge) modes - No application code changes required when switching between modes - Built-in load balancing and health checking for service mesh

Modules

Name Source Version
ecs_service terraform-aws-modules/ecs/aws//modules/service 6.2.1

Inputs

Name Description Type Default Required
additional_container_definitions Additional container definitions to add to the task definition
map(object({
create = optional(bool, true)
operating_system_family = optional(string)
tags = optional(map(string))

# Container definition
command = optional(list(string))
cpu = optional(number)
dependsOn = optional(list(object({
condition = string
containerName = string
})))
disableNetworking = optional(bool)
dnsSearchDomains = optional(list(string))
dnsServers = optional(list(string))
dockerLabels = optional(map(string))
dockerSecurityOptions = optional(list(string))
entrypoint = optional(list(string))
environment = optional(list(object({
name = string
value = string
})))
environmentFiles = optional(list(object({
type = string
value = string
})))
essential = optional(bool)
extraHosts = optional(list(object({
hostname = string
ipAddress = string
})))
firelensConfiguration = optional(object({
options = optional(map(string))
type = optional(string)
}))
healthCheck = optional(object({
command = optional(list(string), [])
interval = optional(number, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
}))
hostname = optional(string)
image = optional(string)
interactive = optional(bool)
links = optional(list(string))
linuxParameters = optional(object({
capabilities = optional(object({
add = optional(list(string))
drop = optional(list(string))
}))
devices = optional(list(object({
containerPath = optional(string)
hostPath = optional(string)
permissions = optional(list(string))
})))
initProcessEnabled = optional(bool)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}))
logConfiguration = optional(object({
logDriver = optional(string)
options = optional(map(string))
secretOptions = optional(list(object({
name = string
valueFrom = string
})))
}))
memory = optional(number)
memoryReservation = optional(number)
mountPoints = optional(list(object({
containerPath = optional(string)
readOnly = optional(bool)
sourceVolume = optional(string)
})))
name = optional(string)
portMappings = optional(list(object({
appProtocol = optional(string)
containerPort = optional(number)
containerPortRange = optional(string)
hostPort = optional(number)
name = optional(string)
protocol = optional(string)
})))
privileged = optional(bool)
pseudoTerminal = optional(bool)
readonlyRootFilesystem = optional(bool)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
})
)
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number, 30)
stopTimeout = optional(number, 120)
systemControls = optional(list(object({
namespace = optional(string)
value = optional(string)
})))
ulimits = optional(list(object({
hardLimit = number
name = string
softLimit = number
})))
user = optional(string)
versionConsistency = optional(string)
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})))

workingDirectory = optional(string)

# Cloudwatch Log Group
service = optional(string)
enable_cloudwatch_logging = optional(bool)
create_cloudwatch_log_group = optional(bool)
cloudwatch_log_group_name = optional(string)
cloudwatch_log_group_use_name_prefix = optional(bool)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_retention_in_days = optional(number)
cloudwatch_log_group_kms_key_id = optional(string)
}))
{} no
auto_discover_secrets_from_additional_containers Automatically discover and grant task execution role access to secrets used in additional_container_definitions bool true no
autoscaling_max_capacity The maximum number of tasks to run number 10 no
autoscaling_min_capacity The minimum number of tasks to run number 1 no
autoscaling_policies Map of autoscaling policies to create for the service any
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
no
capacity_provider_strategy Cluster-level strategy enforcement
map(object({
base = optional(number)
capacity_provider = string
weight = optional(number)
}))
null no
cluster_name The name of the ECS cluster string n/a yes
command The command to run in the container list(string) [] no
container_cpu The number of CPU units to reserve for the container number 256 no
container_health_check Health check configuration for the container
object({
command = optional(list(string), [])
interval = optional(number, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
})
null no
container_image The container image to use string n/a yes
container_memory The amount of memory to reserve for the container number 256 no
container_runtime_user The user to run the container as (set in container definition). string null no
cpu_architecture The CPU architecture of the container string "ARM64" no
create Whether to create the ECS service bool true no
dd_agent_apm_enabled Whether to enable Datadog APM (Application Performance Monitoring) for distributed tracing. Only applies when dd_enabled is true. bool true no
dd_agent_container_monitoring_enabled Whether to enable Datadog container-level monitoring (CPU, memory, network metrics). Only applies when dd_enabled is true. bool true no
dd_agent_metrics_enabled Whether to enable Datadog metrics collection via DogStatsD. Only applies when dd_enabled is true. bool true no
dd_api_key_parameter_name The name of the Parameter Store parameter containing the Datadog API key string "/datadog/DD_API_KEY" no
dd_apm_enabled DEPRECATED: Use dd_agent_apm_enabled instead. Kept for backward compatibility. bool true no
dd_apm_ignore_resources_string Datadog APM ignore resources string, e.g. 'GET /healthcheck string "" no
dd_enabled Master switch for Datadog monitoring. When true, enables all monitoring features (logs, APM, metrics, container monitoring) unless explicitly disabled via granular flags. When false, all monitoring is disabled regardless of granular flag settings. bool false no
dd_fluentbit_base_config Base Fluent Bit configuration file path. Defaults to built-in parse-json.conf string "/fluent-bit/configs/parse-json.conf" no
dd_fluentbit_enable_ecs_log_metadata Enable ECS log metadata in Fluent Bit. Only applies when using custom Fluent Bit config. bool true no
dd_fluentbit_s3_config S3 ARN for custom Fluent Bit configuration file (e.g., arn:aws:s3:::bucket-name/path/to/fluentbit.conf). When provided, uses init-image and downloads config from S3 before starting Fluent Bit string null no
dd_log_level The log level for the Datadog agent string "INFO" no
dd_logs_enabled Whether to enable Datadog log forwarding via Fluent Bit. Only applies when dd_enabled is true. bool true no
dd_site The Datadog site to send data to string "datadoghq.eu" no
dd_tags Map of tags to apply to the Datadog agent map(string) {} no
dependencies List of dependencies for the ECS primary container
list(object({
containerName = string
condition = string
}))
[] no
deployment_maximum_percent Upper limit (as a percentage of the service's desired_count) of the number of running tasks that can be running in a service during a deployment number 200 no
deployment_minimum_healthy_percent Lower limit (as a percentage of the service's desired_count) of the number of running tasks that must remain healthy during a deployment number 66 no
desired_count The desired number of tasks to run number null no
enable_autoscaling Whether to enable autoscaling for the ECS service bool true no
enable_deployment_rollback Whether to enable deployment rollback bool true no
enable_execute_command Whether to enable execute command for the ECS service bool true no
enable_service_connect Enable ECS Service Connect for service discovery (required for bridge mode inter-service communication) bool false no
env The environment the service is running in string n/a yes
environment_files List of S3 ARNs for environment files to load into the container
list(object({
value = string
type = string
}))
[] no
environment_variables Map of environment variables to set in the container map(string) {} no
ephemeral_storage The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate
object({
size_in_gib = number
})
null no
ignore_task_definition_changes Whether to ignore changes to the task definition bool false no
launch_type Optional ECS launch type string "FARGATE" no
mount_points List of mount points to attach to the container
list(object({
sourceVolume = string
containerPath = string
readOnly = bool
}))
[] no
network_mode Docker network mode for the task (awsvpc or bridge). Bridge mode allows more tasks per EC2 instance but requires Service Connect for service discovery. string "awsvpc" no
parameter_store_secret_names Map of Parameter Store secret names to attach to the ECS service map(string) {} no
secretsmanager_secret_names Map of Secrets Manager secret names to attach to the ECS service map(string) {} no
security_group_egress_rules Security group egress rules to add to the security group created
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
}))
{} no
security_group_ids The security groups to attach to the ECS service list(string) n/a yes
security_group_ingress_rules Security group ingress rules to add to the security group created
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
}))
{} no
service_connect_namespace_arn ARN of the Cloud Map namespace for Service Connect (required when enable_service_connect is true) string null no
service_connect_namespace_name Name of the Cloud Map namespace for Service Connect DNS resolution (e.g., 'genai-namespace'). Used to construct full DNS names like 'service.namespace'. string null no
service_discovery_id The ID of the service discovery namespace string "" no
service_name The name of the ECS service string n/a yes
service_port The port the service listens on (required for Service Connect to expose the service to other containers) number null no
subnet_ids The subnets to place the ECS service list(string) n/a yes
task_exec_additional_secret_arns Additional Secrets Manager secret ARNs to grant task execution role access to (in addition to those in secretsmanager_secret_names) list(string) [] no
task_exec_additional_ssm_param_arns Additional SSM Parameter Store ARNs to grant task execution role access to (in addition to those in parameter_store_secret_names) list(string) [] no
volume Object of volumes to attach to the container any {} no

Outputs

Name Description
tasks_execution_iam_role_arn The IAM execution role ARN for the ECS tasks
tasks_execution_iam_role_name The IAM execution role name for the ECS tasks
tasks_iam_role_arn The IAM execution role ARN for the ECS tasks. Use this to attache policies for other AWS services, e.g. access to S3 bucket.
tasks_iam_role_name The IAM execution role name for the ECS tasks. Use this to attache policies for other AWS services, e.g. access to S3 bucket.

IAM Permissions for Secrets

The module creates IAM policies with permissions ONLY for the secrets you use. No wildcards, no broad permissions.

1. Basic secrets (main container)

module "ecs_service_worker" {
  source = "./modules/ecs_service_worker"

  secretsmanager_secret_names = {
    "DATABASE_PASSWORD" = aws_secretsmanager_secret.db_password.name
    "API_KEY"           = aws_secretsmanager_secret.api_key.name
  }

  parameter_store_secret_names = {
    "REDIS_URL" = aws_ssm_parameter.redis_url.name
  }
}

The module creates IAM permissions for these secrets automatically.

2. Automatic discovery (additional containers)

additional_container_definitions = {
  datadog = {
    image = "datadog/agent:latest"
    secrets = [
      { name = "DD_API_KEY", valueFrom = "arn:aws:secretsmanager:us-east-1:123456789012:secret:datadog-key" }
    ]
  }
}

The module finds secrets in additional_container_definitions automatically. IAM permissions are added for them.

To disable: auto_discover_secrets_from_additional_containers = false

3. Manual mode (advanced)

task_exec_additional_secret_arns = [
  "arn:aws:secretsmanager:us-east-1:123456789012:secret:shared-secret"
]

task_exec_additional_ssm_param_arns = [
  "arn:aws:ssm:us-east-1:123456789012:parameter/shared-param"
]

Use this when you need secrets that are not in the container definitions.