Deploy on a Cloud VM (GCP & AWS)
The Self-Hosted Deployment guide runs AxonFlow Community with Docker Compose on any Docker host. This page shows how to put that same stack on a single cloud VM — a Google Compute Engine instance or an AWS EC2 instance — and reach it securely without opening any public ports.
The pattern is identical on both clouds:
- Create one VM that installs Docker automatically at boot.
- Bring up the AxonFlow stack on it exactly as in the self-hosted guide.
- Reach the dashboards from your laptop over the cloud's built-in identity-aware tunnel — IAP on GCP, SSM Session Manager on AWS — so there is no public web surface and no SSH key to manage.
Community vs Enterprise. This guide uses AxonFlow Community (source-available, built from the public repo). If you are an Enterprise evaluation partner, you will have received cloud-specific runbooks that pull pre-built images from a registry with your license — follow those instead; the VM and secure-access mechanics below are the same.
Why a single VM
For a trial, an internal sandbox, or a small production footprint, one VM running Docker Compose is the simplest deployment that still mirrors the real runtime shape of the platform. When you outgrow it, the same compose stack maps to managed Kubernetes (GKE Autopilot / EKS) — but that is rarely where you should start.
| Concern | GCP | AWS |
|---|---|---|
| Compute (4 vCPU / 16 GB) | GCE e2-standard-4 | EC2 t3.xlarge |
| OS image | Ubuntu 22.04 LTS | Ubuntu 22.04 LTS |
| Disk | 50 GB pd-balanced | 50 GB gp3 EBS |
| Secure access (no public ports) | IAP TCP tunnel | SSM Session Manager |
Sizing note: the agent and orchestrator each want ~1 vCPU / 2 GB, plus Postgres, Redis, Prometheus, and Grafana — 4 vCPU / 16 GB is a comfortable fit (and gives headroom for the first-run source build of the two Go services). A t3.large / e2-standard-2 works for a light smoke test.
Option A — Google Compute Engine
1. Create the VM (Docker installs automatically)
export ZONE="us-central1-a" # pick a region close to you
gcloud services enable compute.googleapis.com iap.googleapis.com
# Allow IAP to reach SSH (one-time, scoped to Google's IAP range)
gcloud compute firewall-rules create allow-iap-ssh \
--direction=INGRESS --action=ALLOW --rules=tcp:22 \
--source-ranges=35.235.240.0/20
gcloud compute instances create axonflow \
--zone="$ZONE" --machine-type=e2-standard-4 \
--image-family=ubuntu-2204-lts --image-project=ubuntu-os-cloud \
--boot-disk-size=50GB --boot-disk-type=pd-balanced \
--shielded-secure-boot --shielded-vtpm --shielded-integrity-monitoring \
--metadata=startup-script='#!/bin/bash
set -e
apt-get update && apt-get install -y ca-certificates curl gnupg git
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
for u in $(ls /home); do usermod -aG docker "$u"; done
touch /var/log/docker-ready'
The Community stack builds the agent and orchestrator from source on first
up, sodocker-buildx-pluginis required (above) and the first start takes several minutes. For the most locked-down posture, add--no-address(no public IP) and configure Cloud NAT on the subnet so the VM can still reach the internet — without it the startup script cannot install Docker or pull base images.
2. Bring up the stack
gcloud compute ssh axonflow --zone "$ZONE" --tunnel-through-iap
# on the VM:
until [ -f /var/log/docker-ready ]; do sleep 5; done # wait for Docker
sudo usermod -aG docker "$USER" && newgrp docker # IAP login users aren't in docker by default
git clone https://github.com/getaxonflow/axonflow.git
cd axonflow
cp .env.example .env # add an LLM provider key (OPENAI_API_KEY / ANTHROPIC_API_KEY / ...)
docker compose up -d # first run builds the agent + orchestrator images (a few minutes)
docker compose ps # all services should report healthy
See Self-Hosted Deployment for the full .env walkthrough, service list, and verification steps.
3. Reach Grafana over IAP
The Community stack ships Grafana on port 3000 (default login admin / grafana_localdev456 — change it). There is no separate web portal in Community; the portal UI is an Enterprise feature. Forward the one port through an SSH session tunnelled over IAP (this reaches the service regardless of how the container publishes its port):
gcloud compute ssh axonflow --zone "$ZONE" --tunnel-through-iap -- -N -L 3000:localhost:3000
Open http://localhost:3000 in your browser for Grafana. Anyone with the roles/iap.tunnelResourceAccessor role can open the same forward under their own Google identity — no shared keys.
Option B — AWS EC2
1. Create an SSM-enabled role and the VM
export AWS_REGION="us-east-1" # pick a region close to you
# One-time: instance role for SSM access
aws iam create-role --role-name AxonFlowSSMRole \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ec2.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
aws iam attach-role-policy --role-name AxonFlowSSMRole \
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
aws iam create-instance-profile --instance-profile-name AxonFlowSSMProfile
aws iam add-role-to-instance-profile --instance-profile-name AxonFlowSSMProfile --role-name AxonFlowSSMRole
AMI_ID=$(aws ssm get-parameter --region "$AWS_REGION" \
--name /aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id \
--query 'Parameter.Value' --output text)
aws ec2 run-instances --region "$AWS_REGION" \
--image-id "$AMI_ID" --instance-type t3.xlarge \
--iam-instance-profile Name=AxonFlowSSMProfile \
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":50,"VolumeType":"gp3"}}]' \
--metadata-options 'HttpTokens=required,HttpEndpoint=enabled' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=axonflow}]' \
--user-data '#!/bin/bash
set -e
apt-get update && apt-get install -y ca-certificates curl gnupg git
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
usermod -aG docker ubuntu
touch /var/log/docker-ready'
No security-group inbound rule and no SSH key are configured — access is over SSM. The run-instances call uses your default VPC; if it was deleted, pass --subnet-id + --associate-public-ip-address. The instance needs outbound internet (public IP/IGW or NAT) to install Docker and build/pull images. Install the Session Manager plugin on your workstation. Find the instance ID once it is running:
INSTANCE_ID=$(aws ec2 describe-instances --region "$AWS_REGION" \
--filters "Name=tag:Name,Values=axonflow" "Name=instance-state-name,Values=running" \
--query 'Reservations[0].Instances[0].InstanceId' --output text)
2. Bring up the stack
aws ssm start-session --region "$AWS_REGION" --target "$INSTANCE_ID"
# on the instance — wait for docker BEFORE switching user so the docker group applies:
sudo bash -c 'until [ -f /var/log/docker-ready ]; do sleep 5; done'
sudo su - ubuntu
git clone https://github.com/getaxonflow/axonflow.git
cd axonflow
cp .env.example .env # add an LLM provider key
docker compose up -d # first run builds the agent + orchestrator (a few minutes)
docker compose ps
3. Reach Grafana over SSM
The Community stack ships Grafana on port 3000 (no separate portal — that is Enterprise-only). Forward the one port:
aws ssm start-session --region "$AWS_REGION" --target "$INSTANCE_ID" \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["3000"],"localPortNumber":["3000"]}'
Then open http://localhost:3000 for Grafana. Each session start is IAM-authenticated and recorded as a CloudTrail API event.
Operating and tearing down
Stop the VM when idle to pause compute billing — state persists on the disk:
# GCP
gcloud compute instances stop axonflow --zone "$ZONE"
# AWS
aws ec2 stop-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID"
Delete it entirely when done:
# GCP — also remove the IAP SSH firewall rule if it was created only for this VM
gcloud compute instances delete axonflow --zone "$ZONE"
gcloud compute firewall-rules delete allow-iap-ssh
# AWS — and the SSM role/profile if you won't reuse them
aws ec2 terminate-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID"
No public IPs, load balancers, or public-internet ingress rules were created (the GCP path adds only an IAP-range SSH rule, deleted above), so there is little else to clean up.
Next steps
- Self-Hosted Deployment — the full stack configuration, service ports, and
.envreference - Post-Deployment — what to validate once the stack is healthy
- For production, front the VM with an HTTPS load balancer, move state to a managed database (Cloud SQL / RDS), and store secrets in Secret Manager. When you need autoscaling and managed nodes, the same stack maps to GKE Autopilot or EKS.
