---
cuppy_hub: aws
cuppy_path: guides/ecs/express-mode.md
cuppy_section: docs
cuppy_doc: 342J-XT4E
cuppy_doc_uuid: 36bbcee0-472a-46dc-9fbe-ed2f0200d13a
cuppy_permalink: https://cuppy.io/aws/d/342J-XT4E
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/ecs/express-mode
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 575664d7d92964b155d3bf9c10c3ce80635ed5854e6f95646a1af3c12543ff6c
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Deploy a web service with ECS Express Mode

**Give Express Mode a container image, a task execution role and an infrastructure role, and it builds the whole service:**

- a Fargate service with canary deployments
- an HTTPS Application Load Balancer with an ACM certificate
- target group, security groups and auto scaling
- logs and a deployment alarm

Decide the service name, cluster, subnets and tags *before* the call: they can't be changed later. It is AWS's recommended path for workloads that would have used App Runner, which is closed to new customers.

## Inputs

| Input | Notes |
|---|---|
| Container image | An ECR (or other registry) image. App Runner source-code services need a build step that produces an image first |
| Task execution role ARN | As for any Fargate task: image pull, logs, secrets (`reference/ecs/roles-and-networking.md`) |
| Infrastructure role ARN | Lets ECS create and manage the ALB, target group, security groups and scaling on your behalf. Its required policy isn't on the fetched page (UNVERIFIED) |
| Task role ARN (optional) | Your application's AWS permissions |

```
aws ecs create-express-gateway-service \
  --primary-container '{"image":"<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>","containerPort":8080}' \
  --execution-role-arn <task execution role ARN> \
  --infrastructure-role-arn <infrastructure role ARN> \
  --cpu 512 --memory 1024 \
  --scaling-target '{"minTaskCount":2}'
```

The flag names were checked against the `CreateExpressGatewayService` API model in botocore 1.43.115. The image goes inside `--primary-container` (`image`, `containerPort`, plus optional `command`, `environment`, `secrets`, `awsLogsConfiguration`, `repositoryCredentials`). Only `--infrastructure-role-arn` is required by the model. `--cpu` and `--memory` are strings. `--scaling-target` also takes `maxTaskCount`, `autoScalingMetric` and `autoScalingTargetValue`. `--network-configuration`, `--health-check-path` and `--task-role-arn` are the other overrides.

## What it creates

- The `default` cluster (if missing), with Fargate capacity providers, a task definition, and a REPLICA service using **canary** deployments.
- An **ALB** with an HTTPS:443 listener, host-header rules and an ACM certificate. One ALB is **shared by up to 25 Express services** in the same VPC.
- A target group: HTTP, `ip` targets, health check path `/` every 30 s.
- Security groups for the service and the load balancer.
- Auto scaling: target tracking on CPU at 60%, between 1 and 20 tasks.
- A CloudWatch log group, an alarm for faulty deployments, and service-linked roles.

**Defaults:** 1 vCPU (1024), 2048 MB, container port **80**, desired count 1, X86_64 Linux on Fargate.

## Decide before you create

| Setting | Changeable later? |
|---|---|
| Service name, cluster, tags | **No**, only at creation |
| Deployment strategy (canary) | No |
| Load balancer configuration | No |
| Subnets | Public subnets give an **internet-facing** ALB; private subnets an **internal** one. **The first Express service in a VPC fixes the ALB's subnets**, so later services must use the same AZs |
| Default container name | Don't rename it: later Express updates can break |
| CPU, memory, scaling, security groups | Overridable at creation (`--cpu`, `--memory`, `--scaling-target`, `--network-configuration`) |

## Common mismatches

- **The app listens on 8080 but the default port is 80.** Health checks fail, and the deployment alarm rolls back. Set the container port, or make the app listen on 80 (port override flag UNVERIFIED).
- **The health check is on `/`.** An app that redirects or requires auth on `/` fails it. Serve a cheap 200 there, or change the target group's health path (`reference/elb/health-checks-and-timeouts.md`).
- **A private-subnet service has no route for image pulls.** The usual Fargate rules apply (`patterns/ecs-to-ecr.md`).

## Migrating from App Runner (AWS's procedure)

1. Build and push an image (if App Runner built from source).
2. Create the Express service from that image.
3. Add your custom domain to the ALB's host-header rule and certificate.
4. Shift traffic with Route 53 weighted records: 90/10 → 75/25 → 50/50 → 25/75 → 100/0.
5. Keep App Runner for 24–48 hours for rollback, then `aws apprunner delete-service --service-arn <arn>`.

Update and delete procedures for Express services weren't on the fetched page. The result is a normal ECS service, so `guides/ecs/run-fargate-task.md` and `reference/ecs/errors.md` apply to troubleshooting.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
