Deploy a web service with ECS Express Mode
Run a container as a public or internal HTTPS web service with one ECS Express Mode call — what it creates (Fargate service, shared ALB with ACM certificate, target group, security groups, auto scaling, logs, alarm), the three inputs, the defaults to override, what can never be changed afterwards, and how it replaces App Runner.
markdownguidev4342J-XT4Estage: buildtrust: officialservice: ecsinterface: cliECS Express Modecreate-express-gateway-serviceCreateExpressGatewayServiceApp Runner replacementinfrastructure roleexpress gateway serviceshared ALB 25 servicescanary deployment ECSExpress Mode defaultsmigrate from App Runner
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
defaultcluster (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,
iptargets, 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)
- Build and push an image (if App Runner built from source).
- Create the Express service from that image.
- Add your custom domain to the ALB's host-header rule and certificate.
- Shift traffic with Route 53 weighted records: 90/10 → 75/25 → 50/50 → 25/75 → 100/0.
- 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.