Skip to main content

Overview

The ECS Cluster resource deploys containerised applications on AWS ECS with a multi-service architecture. A single cluster hosts one or more services that share a load balancer. Each service declares its own capacity provider ("FARGATE", "FARGATE_SPOT" or "EC2"), containers, CPU/memory, scaling rules and alarms.

Resource Class

EcsCluster is the default export from the module. The deep lib/... path is a published subpath of the package and is the only import that resolves for this construct, because the package root barrel re-exports networking, storage, CDN, messaging, organisation and the EC2 compute construct, but not the ECS or Lambda constructs.
The scaffolded fjall/<app>/infrastructure.ts that the CLI maintains never imports a construct class directly. It imports App and the *Factory symbols from the package root, and the factory builds this construct for you. Reach for the class directly only when you are writing infrastructure by hand.

Basic Usage

Single Service

Multi-Service with Routing

Worker Cluster (No Load Balancer)

Cluster Props

Per-service alarms and the task-stop churn alarm only materialise when alertsTopic is set. The running-tasks alarm additionally requires containerInsights: true, because RunningTaskCount exists only in the ECS/ContainerInsights namespace. Without insights that alarm is skipped rather than synthesised into permanent INSUFFICIENT_DATA.

Cluster Configuration

Custom Domains

A custom domain needs a hosted zone that already exists. Fjall never creates a hosted zone inside an application stack: such a zone would be undelegated, so ACM DNS validation hangs until CloudFormation rolls back, and destroying the stack would delete the zone. Name the zone source one of three ways.
A bare cluster.domain with no zone source throws at synth: Cluster 'ApiCluster': domain 'api.example.com' names no hosted zone source. Bare CDK synth carries no injected binding, so declare domainConfig.managedDomain or domainConfig.hostedZone for any deploy that does not run through the Fjall CLI against a managed domain.
domainConfig.domainName takes precedence over cluster.domain, so set the name once in domainConfig and drop cluster.domain entirely. The domain must sit inside the zone, checked on a dot boundary, otherwise synth fails. Other domainConfig fields:

Service Props

Each entry in the services array carries these properties.

Routing Configuration

Container Configuration

Each entry in a service’s containers array carries these properties. The first container with a port is the primary container and receives load balancer traffic. Every other container acts as a sidecar.
Without a healthCheck and without an ALB target group, ECS treats a container as healthy the moment it is RUNNING. Post-start exits then stay invisible to the deployment circuit breaker. Declare a health check for any service that has no target group.
dependsOn is resolved at synth time against the container names in the same service, so a typo fails the synth rather than the deploy.

Capacity Providers

FARGATE

Serverless containers with no instances to manage.

FARGATE_SPOT

Up to 70% cheaper than standard Fargate. Tasks may be interrupted.

EC2

Run containers on EC2 instances you control. Task sizing moves to ec2Config: service-level cpu always throws on EC2 capacity, and service-level memoryLimitMiB throws unless it exactly mirrors ec2Config.memoryLimitMiB.

EC2 Capacity Configuration

Slot Semantics

Every CloudFormation identity surface for the ASG (logical-ID prefixes, physical service name, launch-template name) derives from the slot anchor, never from the hardware config. Changing instanceType, memoryLimitMiB or any other property updates the ASG in place instead of replacing it. Services that declare the same slot share one ASG and must agree on every property field, checked at synth. Give a service its own slot when it needs different hardware, and always when it declares a persistentDataVolume, since a single-attach EBS volume implies a singleton service.
reuseOnScaleIn: true returns scaled-in instances to the warm pool in a Stopped state, which can strand ECS tasks DRAINING indefinitely. The default is false, which terminates on scale-in.

Auto-Scaling

Omit scalingType to disable auto-scaling entirely. No scalable target is registered and minCapacity / maxCapacity have no effect.

CPU-Based Scaling

Memory-Based Scaling

Queue-Depth Scaling

ScalingType.QUEUE drives a step-scaling policy on SQS backlog. It is the only mode that wakes a service from desiredCount: 0, because CPU and memory target-tracking publish no datapoint at zero running tasks.
Three synth-time guards keep the shapes honest:
  • desiredCount: 0 with ScalingType.CPU or ScalingType.MEMORY throws, because nothing could ever wake the service.
  • ScalingType.QUEUE with no queueScaling.queues throws.
  • queueScaling set on any other scaling mode throws.

Secrets and Environment Variables

Environment Variables

SSM Parameter Store Secrets

Secrets Manager Imports

id is required by SecretImport, and you supply exactly one of name or arn. Use name for a secret in the deploy account and region: Fjall resolves it to a complete ARN at deploy time. Use arn for a cross-account or cross-region secret the deploy identity cannot DescribeSecret. field is optional and pulls a single JSON key out of the secret rather than the whole value.

Multi-Container Tasks

The first container with a port is the primary container. Additional containers run as sidecars.

Building From a Dockerfile

Set docker to build the service image instead of pulling a pre-built URI. The two are mutually exclusive: docker builds, image pulls.
When target is set, the content-hash image tag becomes <service>-<target>-sha-<12 hex>. buildArgs are public: they are baked into the image and recorded in docker history and provenance, which is what makes them the only channel that reaches a Vite client bundle. buildSecrets are references resolved just in time and mounted by BuildKit, so the value never lands in a layer or the manifest. Put credentials in buildSecrets, never in buildArgs.

Connections

Services declare the resources they reach. The construct creates security group rules for network resources and IAM grants for storage resources.
For resources in a peered application, use remoteConnections. Each spec resolves at synth time into ${PREFIX}_HOST and ${PREFIX}_PORT environment variables merged into every container in the service.

Alarms

Alarms are created only when the cluster carries an alertsTopic. Each service then gets CPU, memory and running-task alarms, plus 5xx error-rate and p99 response-time alarms when it sits behind the ALB.
Disable the p99 alarm for services dominated by deliberately long-lived responses such as SSE streams, which inflate ALB TargetResponseTime without any request being slow.

Methods

Get Load Balancer

Get Service

Get All Services

Get Cluster

Get URL

Complete Example

Best Practices

  1. Pick FARGATE for production workloads that need task-level isolation.
  2. Pick FARGATE_SPOT for batch processing and fault-tolerant workloads.
  3. Pick EC2 when you need specific hardware, and size the container through ec2Config.memoryLimitMiB.
  4. Scale production services from at least 2 tasks, and use ScalingType.QUEUE for anything that should idle at zero.
  5. Put sensitive values in secrets or secretsImport, never in environment or docker.buildArgs.
  6. Declare routing whenever a cluster runs more than one service with a port.
  7. Declare a container healthCheck for services with no ALB target group, so the circuit breaker can see post-start exits.
  8. Set alertsTopic on every production cluster, and containerInsights: true when you want the running-tasks alarm.
  9. Give each hardware profile its own slot so instance-type changes update the ASG in place.

Next Steps

Compute Factory

Provision an ECS cluster through the higher-level compute factory pattern.

Deployment Safety

Tune the circuit breaker, schema gate and task-stop watchdog.

Application Load Balancer

Configure the ALB that fronts your cluster services.

ECR Repository

Store the container images your services pull at deploy time.