Skip to main content

Deploy on AWS ECS Fargate

This guide deploys AxonFlow Enterprise into your own AWS account as an ECS Fargate stack using CloudFormation. It is the right path when you run the platform inside your own VPC and region and own the deployment — as opposed to the AWS Marketplace procurement flow, which uses the same template.

Enterprise. The container images are the licensed Enterprise build and are pulled from a private registry using the credentials in your welcome bundle. For a Community trial on a single machine, see Deploy on a Cloud VM instead.


What it deploys

A single CloudFormation stack provisions:

  • Agent — the policy-enforcement service (replica-scaled).
  • Orchestrator — LLM routing and policy management (a required service).
  • Customer Portal and Portal UI — the governance console (optional; set their desired counts to 0 to disable).
  • RDS PostgreSQL — the platform database.
  • Application Load Balancerinternal by default, or internet-facing.
  • EFS volume — durable storage for the audit trail.
  • Prometheus and Grafana — optional monitoring.

Architecture notes

  • The agent is replica-interchangeable. Any agent task can serve any request; scale AgentDesiredCount up or down freely, and the load balancer spreads traffic across them. There is no agent-side sticky state.
  • The orchestrator is a required service. The agent depends on it for LLM routing and policy management, so it must be running (OrchestratorDesiredCount ≥ 1) for the platform to serve traffic.

Prerequisites

RequirementDetail
VPCAn existing VPC in your target region.
Public subnets2, in 2 Availability Zones, each routing 0.0.0.0/0 to an Internet Gateway (for the load balancer).
Private subnets2, in the same 2 AZs (for the app services and RDS).
ToolingAWS CLI v2 and Docker (with buildx), authenticated to your account.
Registry credentialsA username and read:packages token from your welcome bundle.
HTTPS (optional)An ACM certificate ARN, or a domain name for the template to auto-issue one. Without it the load balancer serves HTTP only.

Choose a registry option

The stack pulls images from whatever you set in ContainerImageRegistry. There are two supported ways to make the images pullable by ECS.

Option 1 — Private registry direct (quickstart)

Fewest moving parts, ideal for an evaluation: ECS pulls the images straight from the private registry using a pull credential you store in AWS Secrets Manager.

Create the secret. Its value must be JSON of exactly this shape — a bare token string will not authenticate:

{"username":"<your-registry-username>","password":"<your-read:packages-token>"}
aws secretsmanager create-secret \
--name axonflow/registry-pull \
--secret-string '{"username":"YOUR_USER","password":"YOUR_TOKEN"}' \
--query ARN --output text

Then deploy with the private registry as ContainerImageRegistry and the secret ARN as RegistryCredentialsSecretArn. The stack grants the task execution role read access to that one secret so ECS can authenticate the pull.

A read:packages token expires on its configured schedule. When it does, image pulls fail with CannotPullContainerError in the ECS service events; rotate the secret value to recover. For production, prefer Option 2.

Copy the images once into an ECR registry in your own account and point the stack there. This is the recommended production path because:

  • Fast, same-region pulls — no cross-region or public-internet fetch on every task placement.
  • Execution-role auth that never expires — ECS pulls from ECR using the task execution role, so there is no registry token to rotate and no token-expiry surprise mid-deploy.
  • No internet egress required — with an ECR VPC endpoint, private subnets pull images with no route to the internet at all, which matters for regulated and data-sovereignty deployments.
  • Fetch decoupled from rollout — mirroring happens ahead of time, so a slow pull never blocks a stack update.

The deployment package (in the axonflow-install bundle) includes mirror-images-to-your-ecr.sh, which copies the six images from the private registry into your ECR with the multi-architecture manifest preserved:

export AXONFLOW_GHCR_USER=your-registry-username
export AXONFLOW_GHCR_TOKEN=your-read-packages-token

./mirror-images-to-your-ecr.sh \
123456789012.dkr.ecr.us-east-1.amazonaws.com/axonflow 9.9.0

Then deploy with your ECR registry as ContainerImageRegistry and leave RegistryCredentialsSecretArn empty — ECS pulls from ECR via the execution role.


Key parameters

ParameterNotes
OrganizationIDYour organization identifier (lowercase, hyphens). Required.
VpcId, PublicSubnet1/2, PrivateSubnet1/2Your network (see prerequisites). Required.
DBPasswordRDS master password. Required.
ContainerImageRegistryThe private registry (Option 1) or your ECR registry (Option 2).
RegistryCredentialsSecretArnSecrets Manager ARN of {"username","password"} for the private-registry pull; empty for ECR.
AgentImageTagGrafanaImageTagv-prefixed release image tag (e.g. v9.9.0); release images are published v-prefixed.
AgentDesiredCount, OrchestratorDesiredCountReplica counts.
CustomerPortalDesiredCount, CustomerPortalUIDesiredCountSet to 0 to disable.
DeployPrometheus, DeployGrafanaOptional monitoring.
LoadBalancerSchemeinternal (default) or internet-facing.
DomainName / CertificateArnOptional HTTPS.

Deploy

The template is larger than CloudFormation's inline body limit, so upload it to an S3 bucket in your account and deploy by URL:

REGION=us-east-1
BUCKET=my-cfn-templates # a bucket you own in $REGION
aws s3 cp cloudformation-ecs-fargate.yaml \
"s3://$BUCKET/axonflow/cloudformation-ecs-fargate.yaml"

aws cloudformation create-stack \
--region "$REGION" \
--stack-name axonflow-prod \
--template-url "https://$BUCKET.s3.amazonaws.com/axonflow/cloudformation-ecs-fargate.yaml" \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters \
ParameterKey=OrganizationID,ParameterValue=your-org \
ParameterKey=VpcId,ParameterValue=vpc-xxxxxxxx \
ParameterKey=PublicSubnet1,ParameterValue=subnet-aaaa \
ParameterKey=PublicSubnet2,ParameterValue=subnet-bbbb \
ParameterKey=PrivateSubnet1,ParameterValue=subnet-cccc \
ParameterKey=PrivateSubnet2,ParameterValue=subnet-dddd \
ParameterKey=DBPassword,ParameterValue='ChangeMe-Strong-Passw0rd!' \
ParameterKey=ContainerImageRegistry,ParameterValue=<registry> \
ParameterKey=RegistryCredentialsSecretArn,ParameterValue=<secret-arn-or-empty>

aws cloudformation wait stack-create-complete --region "$REGION" --stack-name axonflow-prod

Verify

# Stack outputs (endpoints)
aws cloudformation describe-stacks --region "$REGION" --stack-name axonflow-prod \
--query 'Stacks[0].Outputs' --output table

# ECS services should show runningCount == desiredCount
CLUSTER=$(aws cloudformation describe-stack-resources --region "$REGION" --stack-name axonflow-prod \
--query 'StackResources[?ResourceType==`AWS::ECS::Cluster`].PhysicalResourceId' --output text)
aws ecs list-services --region "$REGION" --cluster "$CLUSTER" --output table

The agent and orchestrator expose /health. With an internal load balancer these endpoints are reachable only from inside the VPC (for example over an SSM port-forward or a bastion); with LoadBalancerScheme=internet-facing they are reachable from your workstation.

If images fail to pull, inspect the ECS service events — a bad or expired pull credential shows there as CannotPullContainerError. It does not appear in the CloudFormation stack events (CloudFormation only reports that the service did not stabilize):

aws ecs describe-services --region "$REGION" --cluster "$CLUSTER" \
--services <service-arn> --query 'services[0].events[:10].message' --output table

Upgrade

Upgrade the whole stack to a single version so every service stays in lock-step; do not bump one image in isolation.

For the mirror-to-ECR path, the deployment package includes upgrade.sh, which mirrors the new version into your ECR, flips only the six image-tag parameters (keeping every other parameter unchanged), rolls the update, and re-checks that the services reach a steady state:

cp upgrade.env.example upgrade.env    # set STACK_NAME + ECR_REGISTRY
./upgrade.sh 9.9.0

For the private-registry-direct path, run update-stack with the new image tags and the same RegistryCredentialsSecretArn.


Next steps