Skip to main content
Source

This page is generated from skills/ecs-build/SKILL.md. Edit the source, not this page.

ECS Build

Generate complete, production-ready Amazon ECS infrastructure projects in Terraform. All generated code is terraform apply-ready with zero manual fixups.

All generated Terraform code MUST follow the conventions from these companion skills:

  • terraform-skill -- resource block ordering, variable conventions, file organization, version constraints

Scope: Terraform only. For CDK-based ECS generation, this skill does not apply -- point the user at the AWS CDK ecs-patterns constructs and the aws-containers skill in aws/agent-toolkit-for-aws. ECS Anywhere (EXTERNAL launch type) is not generated by this skill -- it has no capacity providers, no service load balancing, and no awsvpc mode; advise rolling-update-only guidance and route deployment questions to ecs-devops.

When to Use

  • Generating ECS Terraform code from requirements (new project)
  • Scaffolding an ECS cluster with Fargate, EC2 (ASG), or Managed Instances capacity providers
  • Wiring the ECS infrastructure IAM role for Managed Instances, EBS task volumes, or Service Connect TLS
  • Generating services with native ROLLING / BLUE_GREEN / LINEAR / CANARY deployment configuration
  • Building task definitions with correct execution-role/task-role separation and secrets injection
  • Generating private/air-gapped ECS networking (VPC endpoints, no NAT)
  • Adding Application Auto Scaling (target tracking, step scaling, SQS backlog-per-task) to a service
  • Migrating an existing service from launch_type to a capacity provider strategy
  • Generating an Express service (simple stateless HTTP API) via the upstream express-service submodule

Don't Use

  • Designing the workload or choosing a launch type/capacity model from requirements (use ecs-architect -- come here once the design is settled)
  • EKS or Kubernetes builds of any kind (use eks-build)
  • Generic Terraform authoring, review, or debugging with no ECS resource being built (use terraform-skill)
  • CI/CD pipelines, release-strategy selection, rollback operations, or deployment debugging (use ecs-devops -- this skill only renders the deployment_configuration blocks the chosen strategy needs)
  • GPU/ML workload design and GPU launch-type choice (use ecs-genai -- this skill can still render the Terraform that design specifies)
  • Deep security review, IAM hardening strategy, or compliance posture (use ecs-security)
  • Observability stack selection -- monitoring, tracing, alerting design (use ecs-observability)
  • ECR repository creation and lifecycle policies -- out of scope; this skill assumes the registry and repositories already exist (see the aws_ecr_repository and aws_ecr_lifecycle_policy Terraform resources)

Internet Search Requirements

This skill generates code from scratch every time. Always search the internet for:

  • Latest versions of terraform-aws-modules/ecs, the AWS provider, and any companion modules before generating -- never use hardcoded versions from cached knowledge. See references/version-matrix.md for the lookup protocol.
  • ECS feature freshness -- deployment strategies, Managed Instances, and Service Connect evolved rapidly through 2025-2026. Check the ECS document history and AWS What's New before repeating any support matrix.
  • Negative claims ("X does not support Y") -- these go stale fastest. Re-verify against live docs and phrase them "as of ".

The references in this skill cover the most common scenarios. For anything beyond that, research first, then adapt.

Capacity Model Decision Matrix

Facts verified 2026-07-10 against https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ManagedInstances.html and https://docs.aws.amazon.com/AmazonECS/latest/developerguide/fargate-tasks-services.html

All three models are consumed through capacity providers in generated code. FARGATE_SPOT is a capacity provider, never a launchType -- the launch-type enum is EC2 | FARGATE | EXTERNAL | MANAGED_INSTANCES.

FactorFargate (+ FARGATE_SPOT)EC2 (ASG capacity provider)Managed Instances (MI)
Who manages instancesNo instances (serverless)You (AMI, agent, patching, ASG)AWS (provisioning, scaling, patching)
OSAmazon Linux 2 kernel (Linux), Windows Server 2019/2022¹Any ECS-optimized or custom AMI (incl. Windows¹)Bottlerocket only, Linux containers only (AWS-owned AMI)
GPUsNo -- gpu is invalid in Fargate task definitionsYes -- ECS GPU-optimized AMI ships NVIDIA drivers pre-installed; the right choice when the customer needs driver/CUDA version controlYes -- drivers/CUDA pre-installed; incl. fractional-GPU G6f/Gr6f types; agentless GPU metrics via Container Insights enhanced observability
Instance lifetimeN/AYours to manage (instance refresh, max lifetime)Drain starts day 14, terminate by day 21 -- services are unaffected (tasks are drained and replaced gracefully; start-before-stop requires the default maximumPercent 200, and event windows can begin draining earlier than day 14)
Task densityOne task per isolated environmentMultiple tasks per instance (binpack/spread)Multiple tasks per instance by default
Privileged / capabilitiesprivileged invalid; only CAP_SYS_PTRACE addableFull Linux capabilities availableOptional privileged capabilities (CAP_NET_ADMIN, CAP_SYS_ADMIN, CAP_BPF, CAP_PERFMON)
SOCI lazy image loadingYes -- Linux platform version 1.4.0 onlyNoNo
Long-running tasks (>14 days)YesYesNot suitable for individual tasks that must run >14 days uninterrupted; services are unaffected (tasks are drained and replaced gracefully)
SSH / custom AMIN/AYesNo SSH, no custom AMI -- ECS Exec only
Terraform surfacedefault_capacity_provider_strategy onlyASG + launch template + aws_ecs_capacity_provider (managed scaling/draining)aws_ecs_capacity_provider with managed_instances_provider + infrastructure role + instance profile

¹ Windows containers are supported by Fargate and EC2 but are out of this skill's generation scope -- Linux containers only. Windows builds need per-feature verification (Service Connect, SOCI, etc. are Linux-only as of 2026-07-10); research live before generating.

For full wiring, constraints, and migration paths see references/capacity-provider-guide.md.

Strategy mixing self-check: a cluster may contain all three provider types, but a single capacity provider strategy may contain only ONE type -- MI providers, ASG providers, or Fargate/Fargate Spot providers, never mixed (Critical Rule 2).

Express services (distinct generation path): for simple stateless HTTP(S) APIs, default to an ECS Express service instead of hand-wiring the full stack. Express delegates ALB, ACM certificate, autoscaling, and CloudWatch resource creation to ECS itself via an infrastructure role -- those resources are not in Terraform state (no plan/destroy visibility over the delegated resources -- a governance trade-off to surface to the user). Constraints: a single traffic-serving Main container exposing one port (sidecars are permitted), Fargate-only, HTTP(S) workloads, built-in canary deployments. Generate via the upstream modules/express-service submodule (in terraform-aws-modules/ecs since v7.2.0). Note: AWS App Runner closed to new customers 2026-04-30, and AWS recommends Express as the migration target (as of 2026-07-10; see Express services and App Runner availability change; date per the App Runner product page). Details in references/service-and-deployment.md.

Workflow

Step 1: Gather Requirements

Accept requirements interactively or via a requirements YAML. Validate coverage: cluster, capacity model, services (count, LB, deployment strategy), task definitions (CPU/mem, secrets, logging), networking (public/private/air-gapped), autoscaling, tagging. Prompt for gaps. If the user has not settled the capacity model, route to ecs-architect first.

Minimal requirements-YAML shape (all keys optional except project and services; gaps are prompted):

project: orders-api
capacity_model: fargate # fargate | ec2 | managed_instances (may coexist per service)
networking: private # public | private | air_gapped
services:
- name: orders
image: <account>.dkr.ecr.<region>.amazonaws.com/orders:1.4.2 # never :latest
cpu: 512 # task-level; Fargate valid combos only
memory: 1024
port: 8080
load_balancer: alb # alb | nlb | none
deployment: rolling # rolling | blue_green | linear | canary
autoscaling: { min: 2, max: 10, target_cpu: 60 }
express: false # set true for simple stateless HTTP(S) APIs (Express path)
logging: { mode: non-blocking } # blocking | non-blocking (Critical Rule 4)
secrets: # injected via execution role
DB_PASSWORD: arn:aws:secretsmanager:...:secret:orders/db
tags: { environment: prod, team: payments }

Step 2: Select Capacity Model(s)

Use the decision matrix. Multiple models can coexist in one cluster (separate services), but never in one strategy.

Express path: if Step 2 selects Express, skip Steps 5-8 -- capacity providers, load balancing, autoscaling, and CloudWatch are ECS-managed. Generate via modules/express-service and jump to Step 9.

Step 3: Create Project Structure

projects/<project-name>/
├── design/ # Architecture docs (ecs-architect skill, if used)
└── code/ # Terraform code (this skill)

Step 4: Generate Scaffold

Generate root Terraform files under projects/<project-name>/code/ following terraform-skill conventions. Base the cluster and services on terraform-aws-modules/ecs (cluster, service, container-definition submodules) unless the user requires raw resources. Before generating, search the internet for the latest module and provider versions and pin them (Critical Rule 9, references/version-matrix.md).

Step 5: Generate Capacity Providers

Per references/capacity-provider-guide.md. For Managed Instances follow the full wiring recipe -- infrastructure role, instance profile, and the two-step-apply network caveat (Critical Rule 3).

Step 6: Generate Task Definitions and Services

Per references/task-definition-guide.md and references/service-and-deployment.md. Apply baseline defaults from references/baseline-defaults.md: explicit awslogs mode, circuit breaker on rolling services, execution/task role separation. Task volumes: bind mounts, EFS, EBS, or S3 Files (Fargate/MI only) -- see the volume sections in references/task-definition-guide.md. EC2 services also need ordered_placement_strategy/placement_constraints -- see references/capacity-provider-guide.md. Per-instance agents on MI go through ECS Managed Daemons, not per-task sidecars (references/service-and-deployment.md).

Step 7: Generate Networking

Per references/networking-security.md. For private/air-gapped: full VPC endpoint set, scoped by launch type (Critical Rule 8).

Step 8: Generate Autoscaling

Per references/autoscaling.md -- aws_appautoscaling_target + policies; SQS workers get backlog-per-task, not raw queue depth.

Step 9: README and Validate

Generate README with deploy commands, then run scripts/validate_project.sh <project-dir> and fix every FAIL. Exit codes: 0 = clean; 1 = FAILs to fix; 2 = fail-grade checks were skipped (e.g. no terraform binary) -- run terraform init and re-validate before handoff.

Critical Build Rules

Non-negotiable. Each rule states the failure mode it prevents. The validator (scripts/validate_project.sh) mechanically enforces the greppable ones.

1. FARGATE_SPOT Is a Capacity Provider, Never a launchType

The launch-type enum is EC2 | FARGATE | EXTERNAL | MANAGED_INSTANCES. Writing launch_type = "FARGATE_SPOT" fails at apply. Spot is requested via capacity_provider_strategy { capacity_provider = "FARGATE_SPOT" ... }. Also: launch_type and capacity_provider_strategy are mutually exclusive on a service -- setting both is rejected.

2. One Capacity Provider TYPE per Strategy

A strategy may reference only MI providers, only ASG providers, or only FARGATE/FARGATE_SPOT -- never a mix of types ("a capacity provider strategy can't have a mix of different capacity provider types" -- https://docs.aws.amazon.com/AmazonECS/latest/developerguide/capacity-launch-type-comparison.html). Within the Fargate type, FARGATE + FARGATE_SPOT together is the standard base/weight overflow pattern, and only one capacity provider in a strategy may have a base defined at all (CapacityProviderStrategyItem API).

3. Managed Instances Needs the Full Wiring (and a Two-Step Apply)

aws_ecs_capacity_provider.managed_instances_provider requires: top-level cluster set, infrastructure_role_arn (role trusted by ecs.amazonaws.com with AmazonECSInfrastructureRolePolicyForManagedInstances), and instance_launch_template with ec2_instance_profile_arn and network_configuration.subnets. Missing any piece fails create. The upstream managed-instances example documents a network-timing caveat requiring a two-step apply -- see references/capacity-provider-guide.md. Task definitions for MI must include MANAGED_INSTANCES in requires_compatibilities.

4. Set awslogs mode Explicitly

On 2025-06-25 ECS flipped the default log driver mode from blocking to non-blocking, controlled per-account by the defaultLogDriverMode account setting. Code that omits mode behaves differently across accounts. Always emit an explicit "mode": "non-blocking" (with max-buffer-size) or "blocking" per the user's delivery-vs-availability requirement (references/task-definition-guide.md).

5. Execution Role ≠ Task Role; Trust Uses Account-Wide SourceArn

Execution role = agent (ECR pull, awslogs, secrets fetch); task role = application code. Merging them over-grants the app. Both trust ecs-tasks.amazonaws.com; scope the trust with aws:SourceAccount + aws:SourceArn -- but the SourceArn MUST be the account-wide wildcard arn:aws:ecs:<region>:<account>:*. Scoping to a specific cluster is documented as not currently supported and breaks credential vending (references/networking-security.md).

6. Native Deployment Strategies, Not CodeDeploy

House rule for generation: this skill generates blue/green, linear, and canary through deployment_configuration.strategy (ROLLING default | BLUE_GREEN | LINEAR | CANARY) under the native ECS controller, and does not generate the CodeDeploy deployment controller for new services. The CodeDeploy controller remains AWS-supported for existing services -- it is not deprecated. LINEAR/CANARY require ALB, NLB, or Service Connect; headless BLUE_GREEN -- task replacement without managed traffic shifting -- is valid without a load balancer, but confirm intent before generating. The circuit breaker is rolling-only -- never emit deployment_circuit_breaker alongside a blue/green-family strategy (references/service-and-deployment.md).

7. Service Connect, Never App Mesh

App Mesh reaches end of support 2026-09-30. Generate service_connect_configuration for service-to-service traffic; plain Cloud Map (service_registries) only for non-ECS consumers that must resolve the names.

8. Full VPC Endpoint Set for Private Networking, Scoped by Launch Type

Private/air-gapped baseline: ecr.api, ecr.dkr, S3 gateway, logs; add ecs, ecs-agent, ecs-telemetry for the EC2 launch type; ssmmessages for ECS Exec (not ssm -- that one is for Parameter Store secrets); secretsmanager/ssm/kms per secrets usage. The S3 gateway endpoint is the most-missed piece -- ECR layers live in S3 and pulls hang without it.

9. Version Pinning -- Always Search Internet

Never rely on module defaults or cached versions. Look up terraform-aws-modules/ecs and provider versions before every generation and pin them (references/version-matrix.md).

10. Respect Fargate Parameter Exclusions

Fargate task definitions reject gpu, privileged, links, placementConstraints, dockerSecurityOptions, and more; the only addable Linux capability is CAP_SYS_PTRACE; task-level CPU/memory must be one of the fixed valid combinations. Emitting excluded parameters fails registration or validation (references/task-definition-guide.md).

11. Homogeneous ASGs for Managed Scaling

Mixed-instance ASGs with ECS managed scaling are supported but constrained: instance weights are not supported, and the binpack estimate protects on the smallest type -- tasks bigger than the smallest instance never trigger scale-out and sit in PROVISIONING. Generate one homogeneous ASG + capacity provider per instance-size class (references/capacity-provider-guide.md).

12. ECS Exec Conflicts with readonlyRootFilesystem

The container-definition submodule defaults readonlyRootFilesystem = true; ECS Exec's SSM agent needs a writable filesystem. When the user wants both, surface the conflict and relax the setting only on the containers that need Exec. ECS Exec in private subnets also needs the ssmmessages endpoint (Rule 8).

13. Launch Type → Capacity Provider Is a One-Way Migration

Moving a service from launch_type to a capacity_provider_strategy is an in-place UpdateService; the reverse -- strategy back to a launch type -- is not supported, except by passing an empty capacityProviderStrategy to revert to the service's original launch type (capacity/launch-type mutability). Failure mode: a "temporary" migration that can only be undone by recreating the service.

Output Structure

projects/<project-name>/code/
├── main.tf # Cluster + capacity providers + services
├── locals.tf, data.tf, providers.tf, variables.tf, outputs.tf, versions.tf
├── task-definitions.tf # Or per-service *.tf files for larger projects
├── networking.tf # VPC endpoints, security groups (if generated here)
├── iam.tf # Execution/task/infrastructure roles
├── autoscaling.tf # appautoscaling targets + policies
├── configs/
│ └── backend.hcl
└── validation-checklist.md

Deployment

cd projects/<project-name>/code
terraform init -backend-config=configs/backend.hcl
terraform plan && terraform apply
# Managed Instances only: if the first apply fails on capacity-provider networking,
# re-run terraform apply (two-step caveat — see capacity-provider-guide.md)

Completion Checklist

Every item must be done before handoff. Express projects: mark circuit-breaker/strategy/endpoint items N/A -- the validator warns rather than FAILs on them.

  • Internet search for latest module/provider versions (not from cached knowledge)
  • Capacity model(s) selected and confirmed; no mixed-type strategies
  • No launch_type = "FARGATE_SPOT" anywhere; launch_type and capacity_provider_strategy never on the same service
  • MI: infrastructure role + instance profile + subnets wired; MANAGED_INSTANCES in requires_compatibilities
  • Every awslogs block has an explicit mode
  • Execution role and task role are separate; trust policies use account-wide aws:SourceArn wildcard (no cluster-scoped ARN)
  • Blue/green-family services have ALB/NLB/Service Connect and NO circuit breaker; rolling services HAVE circuit breaker + rollback
  • No App Mesh resources; Service Connect for service-to-service
  • Private networking has the full endpoint set for its launch types
  • No Fargate-excluded parameters in FARGATE task definitions
  • All versions explicitly pinned
  • Autoscaled services: raw aws_ecs_service has lifecycle { ignore_changes = [desired_count] }; module path relies on the submodule's built-in ignore (module desired_count input changes never apply post-create -- adjust autoscaling min/max instead)
  • terraform fmt -check passes
  • scripts/validate_project.sh exits 0 (1 = FAILs to fix; 2 = fail-grade checks skipped, e.g. no terraform binary -- terraform init and re-validate)
  • validation-checklist.md generated

References

Read these as needed based on the task at hand:

  • Baseline Defaults -- Read when generating any cluster, task definition, or service. The opinionated defaults applied to every project.
  • Capacity Provider Guide -- Read when wiring Fargate/FARGATE_SPOT strategies, EC2 ASG providers, or Managed Instances (full MI recipe incl. infrastructure role and two-step apply), or migrating launch type to capacity provider.
  • Service and Deployment -- Read when generating services: deployment strategies, bake time, lifecycle hooks, circuit breaker vs alarms, Service Connect, load balancer hygiene.
  • Task Definition Guide -- Read when generating task definitions: roles, secrets (:json-key:: gotcha), awslogs mode, runtime_platform/Graviton, ephemeral storage, EBS/EFS/S3 Files task volumes, Fargate exclusions.
  • Networking and Security -- Read when generating VPC endpoints, security groups, trust policies, or ECS Exec plumbing; includes launch-type-scoped security notes (GuardDuty, SOCI).
  • Autoscaling -- Read when adding Application Auto Scaling: target-tracking metrics, high-resolution variants, SQS backlog-per-task.
  • Version Matrix -- Read when looking up authoritative version sources. A lookup protocol, not a version list.

Sources


This skill is provided as sample code for educational and demonstration purposes only. Verify point-in-time capability claims against the linked AWS documentation before acting on them. See the project's README and LICENSE for full terms.