Deploy a Temporal Worker to Amazon ECS with Fargate.
This repository is the companion example for Deploying Temporal Workers to Amazon ECS. It provides a minimal, working setup you can extend for your own workloads.
Temporal Workers long-poll the Temporal Server — they don't receive inbound HTTP traffic. This means:
- No load balancer. The ECS service has no
load_balancerblock. - No port mappings. The container makes only outbound connections (gRPC to Temporal Cloud, HTTPS to AWS).
- Egress-only security group. Zero ingress rules.
- Health checks via a sidecar HTTP endpoint on
:8080, not an ALB target group.
temporal-ecs/
├── app/ # Worker application
│ ├── Dockerfile # Production container image
│ ├── pyproject.toml # Python dependencies
│ └── worker/
│ ├── __main__.py # Worker entrypoint (health server, graceful shutdown)
│ ├── workflows.py # Sample workflow
│ └── activities.py # Sample activity
├── terraform/ # Infrastructure as Code
│ ├── main.tf # Provider, ECS cluster, CloudWatch
│ ├── ecs.tf # Task definition, service, security group
│ ├── iam.tf # Execution role + task role
│ ├── autoscaling.tf # CPU/memory scaling policies
│ ├── variables.tf # Configuration variables
│ ├── outputs.tf # Cluster/service/log group outputs
│ └── terraform.tfvars
├── push.sh # Build and push image to ECR
├── deploy.sh # Trigger ECS redeployment (after terraform apply)
└── README.md
- AWS account with a VPC, private subnets, and a NAT Gateway
- Temporal Cloud Namespace with mTLS certificates
- ECR repository
- Terraform >= 1.5
- Docker
- AWS CLI v2
aws ssm put-parameter --name "/prod/temporal/host" \
--value "your-ns.a2dd6.tmprl.cloud:7233" --type SecureString
aws ssm put-parameter --name "/prod/temporal/namespace" \
--value "your-ns.a2dd6" --type SecureString
aws ssm put-parameter --name "/prod/temporal/tls-cert" \
--value "$(cat client.pem)" --type SecureString
aws ssm put-parameter --name "/prod/temporal/tls-key" \
--value "$(cat client.key)" --type SecureStringexport AWS_ACCOUNT_ID="123456789012"
export ECR_REPO="my-ecr-repo"
# Create the ECR repo if it doesn't exist
aws ecr create-repository --repository-name "$ECR_REPO" --region us-west-2 || true
./push.shcd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your VPC, subnet, and ECR details
terraform init
terraform plan
terraform apply# Check the service is running
aws ecs describe-services \
--cluster temporal-ecs-cluster \
--services temporal-ecs-worker
# Tail logs
aws logs tail /aws/ecs/temporal-ecs-worker --followRun a local Temporal server with the Temporal CLI:
temporal server start-devThen start the Worker locally:
cd app
uv run -m workerThe Worker connects to localhost:7233 by default when no TLS credentials are provided.
After making changes to the Worker code:
export AWS_ACCOUNT_ID="123456789012"
export ECR_REPO="my-ecr-repo"
export CLUSTER_NAME="temporal-ecs-cluster"
export SERVICE_NAME="temporal-ecs-worker"
./push.sh # Build and push new image
./deploy.sh # Trigger ECS to pull the new image| Decision | Rationale |
|---|---|
| Fargate Spot (80/20) | Workers are stateless — Temporal retries Activities on Spot reclamation. 50–70% cost savings. |
| No load balancer | Workers poll outbound; nothing connects inbound. |
| SSM for secrets | mTLS certs are injected at task startup via ECS-native SSM integration. |
| CPU target at 25% | I/O-bound Workers show low CPU, even under load. Aggressive scale-out prevents backlogs. |
| min_capacity = 2 | Availability during deployments and Spot reclamations. |
| Health check on :8080 | Lightweight HTTP endpoint — ECS replaces the task if it stops responding. |
This repo deploys a minimal say-hello Workflow. To adapt it for your workload:
- Add your Workflows and Activities to
app/worker/ - Register them in
app/worker/__main__.py - Add task role permissions in
terraform/iam.tffor the AWS services your Activities use (S3, Athena, SQS, etc.) - Adjust CPU/memory in
terraform/variables.tfto match your workload profile - Rebuild and deploy with
./deploy.sh