Skip to content

Ecs service

Datadog Monitoring Configuration

This module provides comprehensive, granular control over Datadog monitoring with independent configuration for logs, APM, metrics, and container monitoring.

Quick Start

Enable all monitoring (backward compatible):

module "my_service" {
  source = "./modules/ecs_service"

  dd_enabled = true  # Enables logs, APM, metrics, and container monitoring
  # ... other variables
}

Granular Control

Control each monitoring feature independently by setting dd_enabled = true and then selectively disabling features:

Logs only (no agent):

dd_enabled                            = true
dd_agent_apm_enabled                  = false
dd_agent_metrics_enabled              = false
dd_agent_container_monitoring_enabled = false
# Result: Only Fluent Bit sidecar (logs to Datadog), no agent container

APM + Logs:

dd_enabled                            = true
dd_agent_apm_enabled                  = true
dd_agent_metrics_enabled              = false
dd_agent_container_monitoring_enabled = false
# Result: Fluent Bit + Datadog agent (APM traces on port 8126)

Metrics only (no logs):

dd_enabled                            = true
dd_logs_enabled                       = false
dd_agent_apm_enabled                  = false
dd_agent_metrics_enabled              = true
dd_agent_container_monitoring_enabled = false
# Result: Only Datadog agent (DogStatsD on port 8125), logs go to CloudWatch

Full observability:

dd_enabled = true
# All features enabled by default (logs, APM, metrics, container monitoring)

Architecture

When monitoring is enabled, the module deploys sidecar containers:

Fluent Bit (dd_logs_enabled = true): - Collects and forwards logs to Datadog - Supports custom configuration via S3 (dd_fluentbit_s3_config) - Resource allocation: 256 CPU / 256 MB - Independent of the Datadog agent

Datadog Agent (enabled when any agent feature is true): - APM (dd_agent_apm_enabled): Distributed tracing on port 8126 - Metrics (dd_agent_metrics_enabled): DogStatsD on port 8125 - Container Monitoring (dd_agent_container_monitoring_enabled): Container-level metrics - Resource allocation: 256 CPU / 256 MB - Only deployed if at least one feature is enabled

Configuration Variables

Variable Description Default
dd_enabled Master switch. When false, all monitoring disabled. When true, enables all features unless explicitly disabled. false
dd_logs_enabled Enable log forwarding via Fluent Bit true
dd_agent_apm_enabled Enable APM (Application Performance Monitoring) true
dd_agent_metrics_enabled Enable metrics collection via DogStatsD true
dd_agent_container_monitoring_enabled Enable container-level monitoring true
dd_fluentbit_s3_config S3 ARN for custom Fluent Bit config null
dd_site Datadog site (e.g., datadoghq.eu) "datadoghq.eu"
dd_api_key_parameter_name SSM parameter with Datadog API key "/datadog/DD_API_KEY"

Resource Sizing

The module automatically adjusts task CPU/memory: - Base: Application container resources (container_cpu + container_memory) - +256 CPU / +256 MB if Fluent Bit enabled - +256 CPU / +256 MB if Datadog agent enabled - Selects smallest valid Fargate CPU/memory combination

Bridge Network Mode and Service Connect

This module supports both awsvpc and bridge network modes for ECS 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) - ALB target groups use instance target type instead of ip - 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_service" {
  source = "./modules/ecs_service"

  service_name = "my-service"
  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-image:latest"
  service_port          = 8080
  subnet_ids            = var.private_subnet_ids
  security_group_ids    = [var.security_group_id]
  vpc_id                = var.vpc_id
  env                   = var.environment
  alb_listener          = var.alb_listener_arn
  alb_security_group_id = var.alb_security_group_id
  alb_dns_name          = var.alb_dns_name
  alb_zone_id           = var.alb_zone_id
  hosted_zone_name      = var.hosted_zone_name
}

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))
# enable_execute_command = optional(bool, false) Set in standalone variable
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
alb_dns_name The DNS name of the ALB. Only needed if create_record is true string n/a yes
alb_listener The ARN of the ALB listener string n/a yes
alb_security_group_id The security group ID of the ALB string n/a yes
alb_zone_id The Route 53 zone ID of the ALB. Only needed if create_record is true string n/a yes
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
create_listener_rule Whether to create a listener rule for the service bool true no
create_record Whether to create a Route 53 record for the 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_controller Configuration block for deployment controller configuration
object({
type = optional(string)
})
null 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 Desired count for service. If not set the autoscaling_min_capacity is used number null no
enable_autoscaling Whether to enable autoscaling for the ECS service bool false 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
entry_point The entry point for the container list(string) [] 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
extra_host_headers Extra host headers to route to the service list(string) [] no
extra_target_groups_count How many extra target groups for this service should be provisioned. Useful for blue/green deployments via CodeDeploy number 0 no
force_new_deployment Whether to force a new deployment of the ECS service bool false no
healthcheck_matcher The matcher to use for the healthcheck string "200" no
healthcheck_path The path to use for the healthcheck string "/health" no
hosted_zone_name The name of the Route 53 hosted zone. Only needed if create_record is true string n/a yes
hosts The hosts to route to the service list(string) [] no
ignore_task_definition_changes Whether to ignore changes to the task definition bool false no
ingress_container_name the container name in the task that the ALB will route to. This container has to have a port-mapping with var.ingress_port. If not set, it will default to the service_port string null no
ingress_port the port in the task that the ALB will route to. This can be any port on any of the task's containers. If not set, it will default to the service_port number null no
launch_type Optional ECS launch type string null no
mount_points List of mount points to attach to the container
list(object({
sourceVolume = string
containerPath = string
}))
[] 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
operating_system_family Operating system family for the task definition and all containers string "LINUX" no
parameter_store_secret_names Map of Parameter Store secret names to attach to the ECS service map(string) {} no
port_mappings List of port mappings to attach to the container
list(object({
name = string
containerPort = number
hostPort = number
protocol = string
}))
null 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 service string "" no
service_name The name of the ECS service string n/a yes
service_port The port the service listens on number 80 no
subnet_ids The subnets to place the ECS service list(string) n/a yes
target_group_protocol_version The protocol to use for the target group string "HTTP1" no
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
tasks_iam_role_statements A map of IAM policy statements for custom permission usage
list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
}))
null no
volume Map of volumes to attach to the ECS service any {} no
vpc_id The VPC ID string n/a yes

Outputs

Name Description
ecs_service_arn The arn of the ECS service
ecs_service_name The name of the ECS service
security_group_ids The security group IDs associated with the ECS service
service_security_group_id The security group ID of the ECS service
service_target_group_arn The arn of the created ALB target group
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" {
  source = "./modules/ecs_service"

  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.