End-to-end AWS deployment automation for the Formae agent on ECS Express Mode. Provisions and configures all required infrastructure from a single script.
| Resource | Name | Notes |
|---|---|---|
| Aurora Serverless v2 | formae-db |
PostgreSQL 16.4, Data API enabled |
| Secrets Manager | formae-db-creds |
DB username + password |
| Secrets Manager | formae-config |
Rendered Pkl agent config |
| ECR repository | formae |
Private, mirrors image from GHCR |
| IAM role | formae-ecs-execution |
Image pull, logs, secret injection |
| IAM role | formae-ecs-infrastructure |
ALB, networking, auto-scaling managed by ECS |
| ECS cluster | formae |
|
| ECS Express service | formae-agent |
Fargate, ALB, 1–4 tasks |
All required tools (aws, docker, pkl, ruby, jq) are provided via the Nix dev shell:
nix develop
# or just cd into the directory if direnv is activeConfigure credentials for the target account before running any script:
export AWS_PROFILE=your-profile
# or use environment variables
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=... # if using temporary credentials| Variable | Required | Notes |
|---|---|---|
AWS_ACCOUNT_ID |
Always | 12-digit AWS account ID |
AWS_REGION |
Always | Deployment region (e.g. us-east-2) |
DB_PASSWORD |
First install | Master password for Aurora. Not needed if SKIP_DB=1 |
IMAGE_TAG |
No | Image tag to deploy. Defaults to latest |
Run the full setup from scratch:
export AWS_ACCOUNT_ID=123456789012
export AWS_REGION=us-east-2
export DB_PASSWORD=your-secure-password
bash setup.shThis runs 5 steps in order:
- Database — Creates Aurora Serverless v2 cluster
formae-dbwith Data API enabled, and stores credentials informae-db-creds - Configuration — Renders
config/formae.conf.pkl.erband stores the result in theformae-configsecret - ECR — Creates the
formaeECR repository, pulls the image from GHCR, and pushes it - IAM — Creates the
formae-ecs-executionandformae-ecs-infrastructureroles with the required policies - Deploy — Creates the ECS Express service with an ALB, Fargate tasks, and auto-scaling
On completion, a resource summary is printed with ARNs and AWS console links.
Each step can be skipped independently. Set any of these to a non-empty value:
| Flag | Skips |
|---|---|
SKIP_DB=1 |
Aurora cluster + credentials secret |
SKIP_CONFIGURE=1 |
Pkl config rendering + secret update |
SKIP_ECR=1 |
ECR repo creation + image push |
SKIP_IAM=1 |
IAM role creation |
SKIP_DEPLOY=1 |
ECS Express service creation |
Examples:
# Re-deploy only (all infrastructure already exists)
SKIP_DB=1 SKIP_CONFIGURE=1 SKIP_ECR=1 SKIP_IAM=1 bash setup.sh
# Recreate IAM roles and redeploy (DB, config and image already set)
SKIP_DB=1 SKIP_CONFIGURE=1 SKIP_ECR=1 bash setup.sh
# Set up everything except the service deployment
SKIP_DEPLOY=1 bash setup.sh| Variable | Default | Notes |
|---|---|---|
SERVICE_NAME |
formae-agent |
ECS Express service name |
ECS_CLUSTER |
formae |
ECS cluster name |
ECS_EXECUTION_ROLE_NAME |
formae-ecs-execution |
Execution IAM role name |
ECS_INFRA_ROLE_NAME |
formae-ecs-infrastructure |
Infrastructure IAM role name |
Use update-config.sh to re-render the Pkl config and apply it to the running service with zero downtime:
export AWS_ACCOUNT_ID=123456789012
export AWS_REGION=us-east-2
bash update-config.shThis runs 5 steps:
- Fetch ARNs — Resolves DB cluster ARN, DB secret ARN, config secret ARN, execution role ARN, and verifies the ECS service is active
- Render config — Re-renders
config/formae.conf.pkl.erbwith current environment variables - Update secret — Pushes the new config to the
formae-configSecrets Manager secret - Trigger update — Snapshots the current service revision, then calls
update-express-gateway-serviceto start a rolling deployment - Wait — Polls until
activeConfigurations[0].serviceRevisionArnchanges and the service status returns toACTIVE
The service stays available throughout the rolling update.
- Any time a configuration value changes (DB cluster ARN, region, etc.)
- After rotating the DB credentials secret
- To force tasks to restart and pick up a new version of the config
ECS Express does not support file mounts. The Pkl config is stored in Secrets Manager and injected as the FORMAE_CONFIG environment variable at task startup. The container command writes it to disk:
printenv FORMAE_CONFIG > /tmp/formae.conf.pkl && formae agent start --config /tmp/formae.conf.pkl| Setting | Value |
|---|---|
| Launch type | Fargate |
| Container port | 49684 |
| Health check | GET /api/v1/health |
| Min tasks | 1 |
| Max tasks | 4 |
| Auto-scaling metric | Average CPU at 60% |
| Ingress | Public ALB (HTTPS) |
formae-ecs-execution (task execution + runtime role):
AmazonECSTaskExecutionRolePolicy— ECR image pull, CloudWatch logsPowerUserAccess— infrastructure operations- Inline policy — read access to the
formae-configSecrets Manager secret
formae-ecs-infrastructure (ECS-managed infrastructure):
AmazonECSInfrastructureRoleforExpressGatewayServices— ALB, target groups, security groups, auto-scaling
Aurora Serverless v2 with the Data API (--enable-http-endpoint) allows the agent to query the database over HTTPS without VPC connectors or NAT gateways.
| Setting | Value |
|---|---|
| Engine | PostgreSQL 16.4 |
| Instance class | db.serverless |
| Min capacity | 0.5 ACU |
| Max capacity | 2.0 ACU |
| Database name | formae |
| DB user | postgres |
All .erb files under config/ are rendered with Ruby's erb. Rendered outputs are git-ignored.
| Template | Output | Purpose |
|---|---|---|
formae.conf.pkl.erb |
formae.conf.pkl |
Agent Pkl config (DB ARNs, region) |
ecs-primary-container.json.erb |
ecs-primary-container.json |
ECS container definition |
ecs-execution-policy.json.erb |
ecs-execution-policy.json |
IAM inline policy for secret access |
formae-data-api-policy.json.erb |
formae-data-api-policy.json |
IAM policy for Aurora Data API |
Render a template manually:
erb config/formae.conf.pkl.erb > config/formae.conf.pkl# Health check
curl -s -o /dev/null -w "%{http_code}" https://$SERVICE_URL/api/v1/health
# Expected: 200The service URL is printed at the end of both setup.sh and update-config.sh.