AWS

A starter recipe for self-hosted Flux on AWS — ECS Fargate, RDS Postgres, Secrets Manager, ALB.

A starter recipe — the smallest viable AWS shape that respects Flux’s constraints (durable Postgres, persistent bootstrap-token, sticky routing for worker connections when the server runs more than one task). Production hardening — multi-AZ workers, blue/green deploys, IAM least-privilege per task — is on top of this.

Topology

Provisioning order

Roughly in this order:

1. Networking

VPC, public + private subnets across two AZs, NAT gateway (or VPC endpoints if minimizing NAT cost), security groups. Three groups suffice: ALB (allow 443 from 0.0.0.0/0), Server tasks (allow 8000 from the ALB SG), RDS (allow 5432 from the Server and any admin bastion SG).

2. RDS Postgres

Create the instance in the private subnets. Take note of the endpoint, port, master credentials. Create the flux database and the flux user with CREATE permission — Flux will create its own tables on first connect.

3. Secrets Manager

Generate and store three secrets:

# Bootstrap token
aws secretsmanager create-secret \
  --name flux/bootstrap-token \
  --secret-string "$(python -c 'import secrets; print(secrets.token_hex(32))')"

# Encryption key
aws secretsmanager create-secret \
  --name flux/encryption-key \
  --secret-string "$(python -c 'import secrets; print(secrets.token_hex(32))')"

# DB password (or import the RDS-generated one)
aws secretsmanager create-secret \
  --name flux/database-url \
  --secret-string "postgresql://flux:PASSWORD@flux-db.xxx.rds.amazonaws.com:5432/flux"

4. ECS cluster

Create a Fargate cluster. The flux-server task definition references the secrets via the task role’s secrets: block, which injects them as env vars at task start:

"secrets": [
  { "name": "FLUX_DATABASE_URL",
    "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:flux/database-url" },
  { "name": "FLUX_WORKERS__BOOTSTRAP_TOKEN",
    "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:flux/bootstrap-token" },
  { "name": "FLUX_SECURITY__ENCRYPTION__ENCRYPTION_KEY",
    "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:flux/encryption-key" }
]

The task role needs secretsmanager:GetSecretValue on those ARNs and kms:Decrypt on the KMS key encrypting them.

The flux-worker task definition needs only the bootstrap-token and FLUX_WORKERS__SERVER_URL (or pass --server-url in the container command). Set it to the internal Service Discovery endpoint (Cloud Map) or the ALB’s internal DNS, depending on whether you want workers to traverse the ALB or hit the server tasks directly.

5. ECS Services

6. ALB

Listener on 443 with an ACM cert. Target group pointing at the server tasks on port 8000. Health check path /ready. The ALB inspects the status code, which is all you need: /ready returns 200 when the server can reach the database and 503 when it can’t, so the ALB check reflects real readiness on its own.

At desired_count > 1, enable target-group stickiness (stickiness.enabled=true, lb_cookie): a worker’s SSE stream (GET /workers/{name}/connect) and its dispatch queue live on the task it connected to, so worker traffic must keep landing there. Plain round-robin is fine for everything else, but one target group with stickiness for all traffic is the simpler shape. Also raise the ALB idle timeout (60s+) so the long-lived SSE stream isn’t severed between heartbeats.

Terminate TLS at the ALB. Flux’s flux start server has no --ssl-keyfile flag — it serves plain HTTP and relies on the ALB for transport security.

Secrets

Two distinct kinds of “secret” exist in a Flux-on-AWS deployment, and conflating them causes confusion.

The relationship: AWS Secrets Manager bootstraps Flux; once Flux is up, application workflows pull their own credentials from Flux’s secrets table. The encryption key you put in AWS Secrets Manager is the master key that makes those workflow secrets readable.

Networking notes

What can go wrong

Server task keeps replacing

Symptom. ECS shows the flux-server Service cycling tasks every few minutes; CloudWatch shows them failing the ALB health check.

Cause. Most commonly: ALB health check timeout too aggressive, or the task is failing to start because Secrets Manager retrieval is slow or IAM-blocked.

Fix. Bump the ALB health check Healthy threshold to 2 and Interval to 30s. Check the task’s startup logs in CloudWatch — AccessDeniedException from Secrets Manager points at task-role IAM.

Workers stay OFFLINE

Symptom. flux worker list (run from a bastion against the server) shows workers as OFFLINE.

Cause. Most commonly: bootstrap-token mismatch — the server and worker task definitions reference different versions of the Secrets Manager secret, or the secret was rotated and one Service wasn’t redeployed.

Fix. Confirm both task definitions reference the same secret ARN (without a version suffix, so they pick up the current version on each task start), then redeploy both Services.

Workers flap after scaling the server Service

Symptom. After raising desired_count on flux-server, workers cycle between ONLINE and OFFLINE and executions sit unclaimed longer than before.

Cause. Target-group stickiness is off, so each worker request lands on a random server task while its SSE stream and dispatch queue live on one specific task.

Fix. Enable lb_cookie stickiness on the target group, then restart the workers once. See High availability.


This recipe is derived from AWS service documentation and Flux’s deployment requirements; the author hasn’t personally validated it end-to-end on AWS as of 2026-07. Treat IAM policies, VPC topology, and ALB tuning as starting points to validate against your account’s guardrails.