Deploy on AWS (EKS)
End-to-end walkthrough to deploy Ensemble on Amazon EKS with Terraform, the Helm chart, ALB ingress, and ACM TLS.
This walkthrough deploys the platform on Amazon EKS. It assumes you have read
Prerequisites and have a PostgreSQL database and a
Temporal endpoint ready. Commands use a profile named ensemble; adjust to your
setup.
1. Set your environment
export AWS_PROFILE=ensemble
export AWS_REGION=us-west-2
export CLUSTER_NAME=workflows-prod
export K8S_VERSION=1.31
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export IMAGE_REGISTRY=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
export DOMAIN=workflows.example.com2. Create the EKS cluster
Create the cluster with an OIDC provider (required for IRSA) and nodes in private subnets with a single NAT gateway for a stable outbound IP.
eksctl create cluster \
--name $CLUSTER_NAME \
--region $AWS_REGION \
--version $K8S_VERSION \
--nodegroup-name workflows-nodes \
--node-type t3.large \
--nodes 2 --nodes-min 1 --nodes-max 4 \
--managed --with-oidc \
--node-private-networking \
--vpc-nat-mode Single
aws eks update-kubeconfig --name $CLUSTER_NAME --region $AWS_REGION
kubectl get nodesCapture the OIDC provider ARN for Terraform:
OIDC_URL=$(aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION \
--query "cluster.identity.oidc.issuer" --output text)
export OIDC_PROVIDER_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_URL#https://}"
echo $OIDC_PROVIDER_ARNkubectl and helm act on your kubeconfig's current-context, which persists
across shells. If it is on a GKE cluster, AWS commands fail trying to use gcloud
auth. Run aws eks update-kubeconfig ... (above) to point at EKS, and verify with
kubectl config current-context.
3. Install the cluster add-ons
You need the AWS Load Balancer Controller (for ALB ingress) and the External Secrets Operator (to sync secrets from Secrets Manager).
# AWS Load Balancer Controller (see AWS docs for the IAM policy)
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
-n kube-system --set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=false \
--set serviceAccount.name=aws-load-balancer-controller
# External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets \
-n external-secrets --create-namespace4. Provision supporting resources with Terraform
This creates the buckets, KMS key, IAM workloads role (IRSA), Secrets Manager secret, Cognito, and scheduler.
cd infrastructure/terraform
terraform init
terraform apply -var-file=environments/prod.tfvars \
-var="eks_oidc_provider_arn=$OIDC_PROVIDER_ARN" \
-var="eks_cluster_name=$CLUSTER_NAME"
terraform output helm_config_values
terraform output service_account_annotation
terraform output app_secrets_nameKeep the helm_config_values and service_account_annotation outputs; you will
paste them into your Helm values.
5. Populate the secret
Terraform created app-secrets with empty placeholders. Write the real values
(database URL, Temporal key, LLM keys). Do this out-of-band, never in Git:
aws secretsmanager put-secret-value \
--secret-id workflows-prod/app-secrets \
--region $AWS_REGION \
--secret-string '{
"PG_BASE_URL": "postgresql://user:pass@your-db-host:5432/workflows?sslmode=require",
"TEMPORAL_API_KEY": "...",
"OPENAI_API_KEY": "...",
"ANTHROPIC_API_KEY": "..."
}'6. Make images available
Either pull directly from the Marketplace registry you are entitled to, or mirror
the four images (server, web, worker, migration) into your own ECR and set
global.imageRegistry accordingly. To mirror into ECR, enable the ECR module
(enable_ecr = true) or create the repositories, then push the tagged images.
7. Request a TLS certificate
aws acm request-certificate --domain-name $DOMAIN \
--validation-method DNS --region $AWS_REGION
# Complete DNS validation, then capture the ARN:
export CERT_ARN=$(aws acm list-certificates --region $AWS_REGION \
--query "CertificateSummaryList[?DomainName=='$DOMAIN'].CertificateArn" --output text)8. Create the SecretStore and deploy
Create a SecretStore pointing at Secrets Manager (an example ships in the chart's
examples/secret-store-aws.yaml), then deploy. A minimal values overlay:
# my-values.yaml
global:
imageRegistry: "123456789012.dkr.ecr.us-west-2.amazonaws.com/"
serviceAccount:
create: true
name: workflows-sa
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/workflows-prod-workloads-role
externalSecrets:
enabled: true
secretStoreRef: { name: aws-secrets-manager, kind: SecretStore }
remoteRef: workflows-prod/app-secrets
# attach the synced secret to each component
# Attach the synced secret and set non-secret config per component.
# Fill each `config` from the Terraform helm_config_values output plus the
# Configuration reference; keys are omitted here for brevity.
server:
envFrom:
- secretRef: { name: app-secrets }
config: {} # server env vars
worker:
envFrom:
- secretRef: { name: app-secrets }
config: {} # worker env vars (a subset of the server's)
web:
config: {} # auth + URLs (no secret needed)
migrations:
envFrom:
- secretRef: { name: app-secrets }
ingress:
enabled: true
className: alb
hosts:
- host: workflows.example.com
paths:
- { path: /api, pathType: Prefix, service: server }
- { path: /, pathType: Prefix, service: web }
tls: { enabled: true, certificateArn: "REPLACE_WITH_CERT_ARN" }Fill *.config from the helm_config_values Terraform output plus the
Configuration reference. Then:
helm upgrade --install workflows infrastructure/helm/workflows \
-f infrastructure/helm/workflows/eks-values.yaml \
-f my-values.yaml \
-n workflows --create-namespace \
--set global.imageRegistry="$IMAGE_REGISTRY/" \
--set ingress.tls.certificateArn="$CERT_ARN"The migration job runs first (as a pre-upgrade hook); then the workloads roll out.
9. Configure DNS
Point your domain at the ALB:
kubectl get ingress -n workflows # note the ADDRESS (ALB hostname)Create a Route 53 alias (or CNAME) from $DOMAIN to that ALB hostname.
10. Verify
kubectl get pods -n workflows
kubectl logs -f -l app.kubernetes.io/component=server -n workflows
curl -sSf https://$DOMAIN/api/health # server health via the ingressAll pods should be Running, the migration job Completed, and the health
endpoint should return success. If not, see Troubleshooting.