AWS / ECS Deployment

Causet’s production deployment targets AWS ECS (Elastic Container Service). Each service runs as a separate ECS service backed by Fargate tasks. Infrastructure is managed with Terraform modules in the infra/ directory.


AWS services used

AWS ServiceCauset use
ECS (Fargate)Run all Causet service containers
ECRStore Docker images
RDS PostgreSQLTwo databases: causet (event store) + projections (read models)
MSK (or self-managed Redpanda)Kafka-compatible broker
ElastiCache RedisIR artifact cache + optional query result cache
S3IR artifact storage per release version
ALBHTTP routing to runtime (8080) and query service (8082)
Secrets ManagerDB credentials, Clerk secret key, API keys
IAMECS task roles for S3 and Secrets Manager access
VPCNetwork isolation: private subnets for services, public subnet for ALB

Architecture


ECS service definitions

Each Causet service maps to one ECS service. Recommended Fargate CPU/memory:

ECS ServiceCPUMemory
causet-runtime-service1024 (1 vCPU)2048 MB
causet-projection-worker1024 (1 vCPU)2048 MB
causet-query-service5121024 MB
causet-saas-cloud5121024 MB
causet-cloud-control-plane256512 MB
ws-gateway-go256512 MB

Task definition: causet-runtime-service

{
  "family": "causet-runtime-service",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "1024",
  "memory": "2048",
  "executionRoleArn": "arn:aws:iam::ACCOUNT:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::ACCOUNT:role/causet-runtime-task-role",
  "containerDefinitions": [
    {
      "name": "causet-runtime-service",
      "image": "ACCOUNT.dkr.ecr.REGION.amazonaws.com/causet-runtime-service:VERSION",
      "portMappings": [
        { "containerPort": 8080, "protocol": "tcp" },
        { "containerPort": 9090, "protocol": "tcp" }
      ],
      "environment": [
        { "name": "SERVER_PORT", "value": "8080" },
        { "name": "GRPC_PORT", "value": "9090" },
        { "name": "KAFKA_BOOTSTRAP_SERVERS", "value": "broker.kafka.internal:9092" },
        { "name": "S3_BUCKET", "value": "causet-artifacts-prod" },
        { "name": "CAUSET_SAAS_URL", "value": "http://causet-saas-cloud.internal:8085" }
      ],
      "secrets": [
        { "name": "DATABASE_URL", "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:causet/prod/runtime-db-url" },
        { "name": "AWS_ACCESS_KEY_ID", "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:causet/prod/s3-key-id" },
        { "name": "AWS_SECRET_ACCESS_KEY", "valueFrom": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:causet/prod/s3-secret" }
      ],
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 90
      },
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/causet-runtime-service",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}

See ecs-env-vars-reference.md in the repository for the complete environment variable list for each service.


IAM roles

ECS task execution role

The standard ecsTaskExecutionRole with AmazonECSTaskExecutionRolePolicy attached. This allows ECS to pull images from ECR and write to CloudWatch Logs.

ECS task role (application role)

Each service’s task role needs specific permissions:

causet-runtime-service:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::causet-artifacts-prod",
        "arn:aws:s3:::causet-artifacts-prod/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:causet/prod/*"
    }
  ]
}

causet-projection-worker additionally needs S3 read access for IR artifacts (same as runtime).


VPC layout

VPC: 10.0.0.0/16
  Public subnets (ALB):
    10.0.1.0/24  us-east-1a
    10.0.2.0/24  us-east-1b
  Private subnets (ECS tasks, RDS, MSK, Redis):
    10.0.10.0/24  us-east-1a
    10.0.11.0/24  us-east-1b
  • ALB resides in public subnets. ECS tasks have no public IPs.
  • RDS, MSK, and ElastiCache reside in private subnets alongside ECS tasks.
  • Use VPC endpoints for S3 to avoid data transfer through the public internet.

ALB configuration

Create two ALB listeners / target groups:

Target groupPortHealth check path
causet-runtime-tg8080/actuator/health
causet-query-tg8082/actuator/health
causet-saas-tg8085/actuator/health

Route by path or subdomain depending on your domain setup. The gRPC port (9090) on the runtime service should be exposed through an NLB (Network Load Balancer) rather than the ALB if gRPC clients connect directly.


Auto-scaling

Scale ECS services based on CPU and memory utilization:

resource "aws_appautoscaling_policy" "runtime_scale_up" {
  name               = "runtime-cpu-scale-up"
  resource_id        = "service/causet-prod/causet-runtime-service"
  scalable_dimension = "ecs:service:DesiredCount"
  service_namespace  = "ecs"
 
  target_tracking_scaling_policy_configuration {
    target_value = 70.0
    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }
    scale_in_cooldown  = 300
    scale_out_cooldown = 60
  }
}

Minimum 2 tasks for the runtime and projection worker to survive an AZ failure. The projection worker auto-scales based on Kafka consumer lag rather than CPU — use a custom metric.


Deployment strategy

Use ECS rolling updates. Set the deployment configuration:

"deploymentConfiguration": {
  "minimumHealthyPercent": 50,
  "maximumPercent": 200
}

This keeps at least half the current tasks running during a deploy, and allows 2× the desired count during the transition.

Warning: The projection worker is a Kafka consumer. During a rolling update, two versions of the worker may run briefly with the same consumer group. This is safe because the new tasks will rebalance partitions from the old tasks — no duplicate processing. Messages already committed will not be reprocessed.


Deployment checklist

Before deploying to ECS:

  1. New Docker images pushed to ECR with correct version tag
  2. IR artifacts uploaded to S3 at the new irVersion path
  3. Release created in causet-saas-cloud
  4. ECS task definitions updated with the new image tags
  5. Health checks passing on the new image in a test environment

After deploying:

  1. ECS deployment status shows COMPLETED (not stuck)
  2. All tasks show RUNNING and healthy
  3. /actuator/health returns UP on all services
  4. Submit a test intent and verify the response

See Release Checklist for the full procedure.


Terraform

Infrastructure is defined in infra/terraform/. Reference modules:

infra/terraform/
  modules/
    ecs-service/       — reusable ECS service module
    rds/               — RDS PostgreSQL
    msk/               — MSK Kafka
    elasticache/       — Redis
    alb/               — Application Load Balancer
  environments/
    prod/              — production configuration
    staging/           — staging configuration
# Plan and apply
cd infra/terraform/environments/prod
terraform init
terraform plan -out=tfplan
terraform apply tfplan