Skip to content

Fargate services

Confirm $CLUSTER, $GATEWAY, and $PATH_NAME are ${NAME}-fargate, and $EXEC_ROLE_ARN / private subnet IDs are set. Application step: two small apps + gateway connector
Opt-in parameter on an ECS service that names the container port for gateway traffic. Can filter PUBLIC or PRIVATE access types and set health checks.
Connect services
.

Stand up two different apps — Orders and Catalog — so each managed hostname clearly shows which service the gateway routed to. Same nginx image; different HTML baked into each task definition.

Orders

service -a · port web

Catalog

service -b · port web

One gateway · one shared ALBhost-based routing · HTTPS 443
<id-a>.ecs.<region>.on.aws<id-b>.ecs.<region>.on.aws
Terminal window
echo "CLUSTER=$CLUSTER GATEWAY=$GATEWAY PATH_NAME=$PATH_NAME"
CLUSTER=ecs-gw-lab-fargate GATEWAY=ecs-gw-lab-fargate PATH_NAME=ecs-gw-lab-fargate

AmazonECSTaskExecutionRolePolicy can write log streams, but not create the group. Create the group with your caller identity (do not use awslogs-create-group — that needs logs:CreateLogGroup on the exec role).

Terminal window
aws logs create-log-group --log-group-name "/ecs/${PATH_NAME}" 2>/dev/null || true
(no stdout — created, or already exists)
Terminal window
aws logs put-retention-policy --log-group-name "/ecs/${PATH_NAME}" --retention-in-days 7
(no stdout — success is silent)

awsvpc + named port web (must match the connector portName). CPU/memory required for Fargate. HTML is base64-encoded so the task JSON stays tidy.

Define helpers once in this shell (no AWS call yet):

Terminal window
write_app_html() {
local out="$1" title="$2" accent="$3" blurb="$4"
cat > "$out" <<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${title} · ECS Gateway lab</title>
<style>
:root { color-scheme: light dark; --accent: ${accent}; }
* { box-sizing: border-box; }
body {
margin: 0; min-height: 100vh; display: grid; place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background:
radial-gradient(1200px 600px at 10% -10%, color-mix(in oklab, var(--accent) 28%, transparent), transparent 60%),
radial-gradient(900px 500px at 100% 0%, color-mix(in oklab, var(--accent) 16%, transparent), transparent 55%),
#0f1419;
color: #e8eef5;
}
main {
width: min(36rem, calc(100% - 2rem));
padding: 2rem 2.1rem 1.75rem;
border: 1px solid color-mix(in oklab, var(--accent) 45%, #2a3542);
border-radius: 1rem;
background: color-mix(in oklab, #15202b 88%, var(--accent));
box-shadow: 0 24px 60px rgba(0,0,0,.35);
}
.eyebrow {
margin: 0 0 .65rem; font-size: .75rem; letter-spacing: .14em;
text-transform: uppercase; color: color-mix(in oklab, #c9d6e5 70%, var(--accent));
}
h1 { margin: 0 0 .75rem; font-size: clamp(1.8rem, 4vw, 2.4rem); letter-spacing: -0.03em; }
p { margin: 0 0 .9rem; line-height: 1.55; color: #c5d0dc; }
code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: .9em; color: #fff;
background: rgba(0,0,0,.28); padding: .12em .4em; border-radius: .35em;
}
.meta { margin-top: 1.25rem; font-size: .85rem; color: #9aabbc; }
</style>
</head>
<body>
<main data-app="${title}">
<p class="eyebrow">ECS Gateway lab · host-shared ALB</p>
<h1>${title}</h1>
<p>${blurb}</p>
<p class="meta">If you open the other app’s URL, you should get a different page on the <strong>same</strong> load balancer.</p>
</main>
</body>
</html>
HTML
}
register_web_task() {
local family="$1" html_file="$2"
local b64
b64=$(base64 -w0 < "$html_file")
cat > /tmp/${family}-task.json <<EOF
{
"family": "${family}",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "${EXEC_ROLE_ARN}",
"containerDefinitions": [{
"name": "web",
"image": "public.ecr.aws/nginx/nginx:stable-alpine",
"essential": true,
"entryPoint": ["sh", "-c"],
"command": ["echo '${b64}' | base64 -d > /usr/share/nginx/html/index.html && exec nginx -g 'daemon off;'"],
"portMappings": [{
"name": "web",
"containerPort": 80,
"protocol": "tcp",
"appProtocol": "http"
}],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/${PATH_NAME}",
"awslogs-region": "${AWS_REGION}",
"awslogs-stream-prefix": "web"
}
}
}]
}
EOF
aws ecs register-task-definition \
--cli-input-json "file:///tmp/${family}-task.json" \
--query 'taskDefinition.{family:family,revision:revision}'
}

Write the two pages (local files only):

Terminal window
write_app_html /tmp/${PATH_NAME}-orders.html "Orders" "#3d8bfd" \
"Checkout and order history for the shop. This page is served by <code>${PATH_NAME}-a</code> behind the Fargate-path gateway."
write_app_html /tmp/${PATH_NAME}-catalog.html "Catalog" "#3ecf8e" \
"Product listing and search. This page is served by <code>${PATH_NAME}-b</code> on a different hostname — same ALB, different route."
ls /tmp/${PATH_NAME}-orders.html /tmp/${PATH_NAME}-catalog.html
/tmp/ecs-gw-lab-fargate-orders.html
/tmp/ecs-gw-lab-fargate-catalog.html

Register Orders:

Terminal window
register_web_task "${PATH_NAME}-a" /tmp/${PATH_NAME}-orders.html
{
"family": "ecs-gw-lab-fargate-a",
"revision": 2
}

Register Catalog:

Terminal window
register_web_task "${PATH_NAME}-b" /tmp/${PATH_NAME}-catalog.html
{
"family": "ecs-gw-lab-fargate-b",
"revision": 2
}

Revision numbers increase if you re-register; that is fine.

Tasks run in private subnets with assignPublicIp=DISABLED. Omit security groups to use the VPC default (fine for this lab). First connector attachment provisions the shared ALB in the public subnets for this gateway.

Terminal window
aws ecs create-service \
--cluster "$CLUSTER" \
--service-name "${PATH_NAME}-a" \
--task-definition "${PATH_NAME}-a" \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={
subnets=[${PRIVATE_SUBNET_A},${PRIVATE_SUBNET_B}],
assignPublicIp=DISABLED
}" \
--gateway-connectors '[{
"portName": "web",
"filters": { "accessTypes": ["PUBLIC"] },
"healthCheck": { "healthCheckPath": "/" }
}]' \
--query 'service.{name:serviceName,status:status,desired:desiredCount}'
{
"name": "ecs-gw-lab-fargate-a",
"status": "ACTIVE",
"desired": 1
}

Same connector shape — platform already defined ingress on the Fargate-path gateway.

Terminal window
aws ecs create-service \
--cluster "$CLUSTER" \
--service-name "${PATH_NAME}-b" \
--task-definition "${PATH_NAME}-b" \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={
subnets=[${PRIVATE_SUBNET_A},${PRIVATE_SUBNET_B}],
assignPublicIp=DISABLED
}" \
--gateway-connectors '[{
"portName": "web",
"filters": { "accessTypes": ["PUBLIC"] },
"healthCheck": { "healthCheckPath": "/" }
}]' \
--query 'service.{name:serviceName,status:status,desired:desiredCount}'
{
"name": "ecs-gw-lab-fargate-b",
"status": "ACTIVE",
"desired": 1
}

Fargate verify