ShopFlow on ECS: end-to-end production walkthrough

ShopFlow on ECS: end-to-end production walkthrough

This post implements a realistic workload—not a single “hello task.” ShopFlow is a simplified e-commerce checkout path:

  • Storefront (public) accepts POST /api/checkout.
  • Orders orchestrates inventory reservation and payment capture.
  • Payments and Inventory are internal-only microservices.
  • Settlement is a VPC Lambda that health-checks Orders over Cloud Map DNS (not Service Connect).
  • EFS stores append-only audit JSONL from Orders (shared, durable, multi-task safe).

You will configure Service Connect for east-west HTTP, Service Discovery for non-ECS callers, EFS for shared files, and an ALB for north-south traffic—following AWS rollout order and production guardrails.

Theory reference: ECS at production scale · Service Connect vs Discovery cheat sheet

Code and JSON artifacts: dir/shopflow-ecs/

ShopFlow architecture


1. Requirements and constraints

RequirementDesign choice
HTTPS for customersApplication Load Balancer + ACM certificate
Internal APIs not on public internetawsvpc tasks in private subnets; only Storefront registered to ALB
Low-latency service-to-service callsService Connect short names (orders, payments, inventory)
Batch settlement in VPCCloud Map FQDN orders-api.shopflow.prod.internal
Audit trail survives task restartEFS access point mounted only on Orders
Observable, safe deploysContainer + ALB health checks, circuit breaker, structured logs
Least privilegeSeparate task roles per service; EFS IAM enabled on access point

Region example: us-east-1 · Launch type: Fargate platform LATEST (Linux 1.4+) · Network mode: awsvpc


2. Architecture

                         ┌─────────────────────────────────────────┐
  Internet ──HTTPS──►    │  ALB (public) → storefront:8080         │
                         └──────────────────┬──────────────────────┘
                                            │ Service Connect mesh
                         ┌──────────────────▼──────────────────────┐
                         │  namespace: shopflow-mesh (HTTP)          │
                         │  storefront ──► orders ──► payments     │
                         │              └────► inventory            │
                         └──────────────────┬──────────────────────┘
                                            │
              ┌─────────────────────────────┼─────────────────────────────┐
              │ EFS (audit)               │ Cloud Map DNS_PRIVATE        │
              │ /mnt/efs/audit            │ orders-api.shopflow.prod...  │
              └─────────────────────────────┴──────────────┬──────────────┘
                                                           │
                                                    settlement Lambda (VPC)

Traffic rules

FromToMechanismHostname
UserStorefrontALB target groupshop.example.com
StorefrontOrdersService Connecthttp://orders:8081
OrdersPayments, InventoryService Connectpayments:8082, inventory:8083
Lambda settlementOrdersService Discoveryorders-api.shopflow.prod.internal:8081

3. Prerequisites in your AWS account

Complete these once per environment (prod, staging).

3.1 Network

  • VPC with private subnets (tasks) and public subnets (ALB only).
  • NAT gateway or VPC endpoints for ECR, CloudWatch Logs, EFS.
  • VPC DNS hostnames and DNS support enabled (required for Cloud Map).

3.2 ECR image

Build and push the sample app (dir/shopflow-ecs/Dockerfile):

export AWS_REGION=us-east-1
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export REPO=shopflow-api

aws ecr create-repository --repository-name "$REPO" --region "$AWS_REGION" 2>/dev/null || true
docker build -t "$REPO:1.0.0" ./dir/shopflow-ecs
docker tag "$REPO:1.0.0" "$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPO:1.0.0"
aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS \
  --password-stdin "$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"
docker push "$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPO:1.0.0"

(Adjust path if you cloned only dir/shopflow-ecs.)

3.3 IAM roles (summary)

RoleTrustPurpose
shopflow-ecs-executionecs-tasks.amazonaws.comPull image, write logs, EFS client mount
shopflow-*-taskecs-tasks.amazonaws.comApp AWS API calls (minimal for this sample)
shopflow-settlement-lambdalambda.amazonaws.comVPC Lambda logs + ENI

Attach AWS managed AmazonECSTaskExecutionRolePolicy to execution role; add EFS elasticfilesystem:ClientMount scoped to your access point ARN.

3.4 CloudWatch log groups

Create /ecs/shopflow/storefront, orders, payments, inventory, /ecs/shopflow/service-connect before first deploy (or let execution role create—explicit is cleaner for retention policies).


4. Storage — EFS for audit (production pattern)

Orders appends JSON lines to /mnt/efs/audit/orders-YYYY-MM-DD.jsonl (app.py).

Console — EFS

  1. Open EFS console → Create file system.
  2. VPC: same as ECS; mount targets in each AZ used by tasks.
  3. Encryption: enabled (default KMS or customer managed).
  4. Performance / throughput: start General Purpose + Bursting; move to provisioned throughput if JSONL write rate spikes.
  5. Create access point:
    • Root path: /audit
    • POSIX user/group: match container user (for python:3.12-slim, often UID 0 for simplicity in labs; use non-root + matching AP in real prod).
    • IAM authorization: ON (task role must allow mount).
  6. Security group on mount targets: allow TCP 2049 from Orders task security group only.

Task definition wiring

See task-def-orders.json: efsVolumeConfiguration with transitEncryption: ENABLED and accessPointId.

Best practice: Only Orders mounts EFS. Storefront, Payments, and Inventory stay ephemeral—smaller blast radius.


5. Service Connect namespace and port naming

Console — HTTP namespace for Service Connect

  1. ECS console → Namespaces → Create.
  2. Name: shopflow-mesh (HTTP namespace for Service Connect).
  3. Copy the namespace ARN (arn:aws:servicediscovery:...:namespace/ns-...).

Port naming (all four services)

Every task definition must name ports and set appProtocol: http:

ServicePortPort nameSC discovery name
Storefront8080storefront-8080-tcp(client only)
Orders8081orders-8081-tcporders
Payments8082payments-8082-tcppayments
Inventory8083inventory-8083-tcpinventory

Register task definitions in the console (Task definitions → Create) or register JSON revisions. Templates: task-def-storefront.json, task-def-orders.json—duplicate pattern for payments/inventory with APP_ROLE and ports.

Environment variables must match Service Connect aliases (ORDERS_HOST=orders, not orders.shopflow).


6. Service Discovery (for Lambda settlement)

Orders is also registered in a DNS private namespace for non-ECS consumers.

Console — Cloud Map private namespace

  1. Cloud Map → Create namespace.
  2. Type: DNS private.
  3. Name: shopflow.prod.internal.
  4. VPC: your ECS VPC.

Console — Cloud Map service

  1. Inside namespace, create service orders-api.
  2. DNS routing: Multivalue with low TTL if you want faster convergence.

Attach to ECS Orders service

When creating/updating orders ECS service:

  1. Expand Service discovery (optional).
  2. Namespace: shopflow.prod.internal.
  3. Service name: orders-api.

Resulting FQDN: orders-api.shopflow.prod.internal → task IPs.

Settlement Lambda (lambda_settlement.py) uses ORDERS_DNS=orders-api.shopflow.prod.internal.


7. Deploy services (order matters)

AWS recommends enabling client-server Service Connect on backends before clients (rollout guidance).

Phase A — Internal backends (no ALB)

Deploy in this order; wait for steady state between each:

  1. inventory — SC client-server, alias inventory:8083
  2. payments — SC client-server, alias payments:8082
  3. orders — SC client-server, alias orders:8081 + EFS mount + service discovery orders-api

Console — create orders service (excerpt)

  1. Clusters → shopflow-prod → Create service.
  2. Compute: Launch type Fargate, task definition shopflow-orders.
  3. Networking: private subnets, orders-sg.
  4. Service Connect → Use Service Connect:
    • Namespace: shopflow-mesh ARN
    • Add service: port name orders-8081-tcp, DNS name orders, port 8081
    • Optional: Access log configuration → JSON
  5. Service discovery: orders-api in shopflow.prod.internal.
  6. Deployment circuit breaker: enable rollback.
  7. Create service.

JSON reference: service-connect-orders.json.

Phase B — Storefront (ALB + SC client)

  1. Create ALB (internet-facing), HTTPS listener, target group IP mode port 8080, health check path /ready.
  2. Create storefront service:
  3. Security groups:
    • ALB SG → storefront SG on 8080.
    • Storefront SG → orders SG on 8081 (if not using SC proxy-only paths, still allow task-to-task; SC proxy listens on alias ports inside task—follow SC security group guidance).

Phase C — Force client redeploy if needed

If Storefront was deployed before Orders finished registering, run Update service → Force new deployment on Storefront (/etc/hosts caveat).


8. Security groups (reference)

SGInboundOutbound
alb-sg443 from 0.0.0.0/0storefront:8080
storefront-sg8080 from alb-sg443 to VPC endpoints / NAT
orders-sg8081 from storefront-sg (+ SC mesh)payments, inventory, EFS 2049
payments-sg8082 from orders-sg—
inventory-sg8083 from orders-sg—
efs-sg2049 from orders-sg—
lambda-settlement-sg—orders:8081, logs, NAT

Use separate SGs per tier—never one shared “ecs-sg” with wide ingress.


9. Verification checklist

9.1 Service Connect (from Storefront task)

ECS Exec into a Storefront task:

cat /etc/hosts | egrep 'orders|payments|inventory'
curl -sS http://orders:8081/health

9.2 Checkout path (public)

curl -sS -X POST "https://shop.example.com/api/checkout" \
  -H "Content-Type: application/json" \
  -d '{"sku":"SKU-100","quantity":2,"orderId":"ord-demo-001"}' | jq .

Expect nested order with inventory.reserved and payment.charged.

9.3 EFS audit

From Orders task (or EC2 bastion with EFS mount):

ls -la /mnt/efs/audit/
tail -n 3 /mnt/efs/audit/orders-$(date -u +%F).jsonl

9.4 Service Discovery (Lambda)

Deploy lambda_settlement.py in VPC private subnets with ORDERS_DNS=orders-api.shopflow.prod.internal. Invoke test event—should return Orders /health body.

9.5 Observability

  • ECS → cluster → Metrics / Service Connect namespace metrics.
  • CloudWatch Logs → /ecs/shopflow/*.
  • Enable Container Insights on cluster for production (ECS CloudWatch).

10. CI/CD and operations

PracticeShopFlow application
Immutable tagsPush shopflow-api:1.0.1, update task definition revision, rolling deploy
Circuit breakerEnabled on all services
Minimum healthy 100% / max 200%Standard for small task counts; tune for faster rollouts
SecretsMove payment API keys to Secrets Manager; inject via task definition secrets
AutoscalingTarget tracking on ALB RequestCountPerTarget for Storefront; CPU for Orders
EFS backupsAWS Backup plan on file system; lifecycle to IA for old audit files
Multi-AZdesiredCount >= 2 per service across subnets

CodePipeline sketch: Source → CodeBuild (docker build/push) → ECS deploy action with imagedefinitions.json per service—four deploy stages or matrix for storefront, orders, payments, inventory (same image, different task defs).


11. What we deliberately did not do (and what to add next)

TopicShopFlow scopeProduction next step
Service Connect TLSOff in walkthroughEnable PCA + infrastructure role (TLS guide)
Per-task EBSNot usedAdd for ephemeral cache workloads via configuredAtLaunch
App Mesh / third-party meshNot neededSC covers L7 east-west for ECS-only
DynamoDB / AuroraMocked in appPersist orders in DynamoDB with idempotency keys on orderId
WAFNot shownAttach AWS WAF to ALB

12. Artifact index

FileDescription
dir/shopflow-ecs/app.pyFlask + gunicorn multi-role application
dir/shopflow-ecs/task-def-orders.jsonOrders + EFS + named port
dir/shopflow-ecs/task-def-storefront.jsonStorefront + /ready health
dir/shopflow-ecs/service-connect-orders.jsonSC server config
dir/shopflow-ecs/service-connect-storefront-client.jsonSC client config
dir/shopflow-ecs/lambda_settlement.pyCloud Map DNS consumer

Further reading

← ECS production theory · Service Connect vs Discovery