Skip to main content

VM Deployment

Deploy Aurora on a single VM using Docker Compose.

Choose your deployment path:


Standard Deployment

This path covers every step from provisioning the VM to accessing Aurora in your browser, on any cloud provider with unrestricted internet.

1. Provision a VM

Create a VM on your cloud provider of choice (AWS EC2, GCP Compute Engine, Azure VM, DigitalOcean Droplet, Hetzner, etc.).

RequirementValue
OSUbuntu 22.04 LTS or Debian 12
CPU4 cores minimum, 8 recommended
RAM8 GB minimum, 32 GB recommended
Disk60 GB SSD
Disk Size

Aurora's Docker images, containers, and volumes require significant space.

After creation, note the VM's public/external IP address — you'll need it later.

2. SSH Into the VM

ssh -i /path/to/your-key.pem YOUR_USERNAME@YOUR_VM_IP

Most cloud providers also offer a browser-based SSH console in their web UI.

3. Install Dependencies

Run these commands one at a time (not as a single pasted block — newgrp opens a sub-shell that prevents subsequent commands from running).

Ubuntu / Debian

# Update packages
sudo apt update && sudo apt upgrade -y

# Install system tools
sudo apt install -y make git jq cloud-guest-utils

# Install Docker
curl -fsSL https://get.docker.com | sh

# Add your user to the docker group
sudo usermod -aG docker $USER

# Apply the group change (opens a new shell — run this separately)
newgrp docker

# Verify Docker works (must print v2.x.x)
docker compose version

CentOS / RHEL / Amazon Linux

sudo yum update -y
sudo yum install -y make git jq cloud-utils-growpart

curl -fsSL https://get.docker.com | sh
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker

docker compose version

If docker gives "permission denied" after newgrp, log out and back in (exit then SSH again).

Restricted Environments

If curl is blocked or the Docker convenience script fails, see Installing Docker for manual installation instructions covering Debian, Ubuntu, CentOS/RHEL, Amazon Linux on both amd64 and arm64, including fully airgapped environments.

Run Commands Individually

newgrp docker opens a new shell session. If you paste all commands at once, everything after newgrp will not execute in the current session. Run it separately, then continue with the remaining commands.

4. Clone and Initialize

git clone https://github.com/arvo-ai/aurora.git
cd aurora
make init

make init creates .env from .env.example and generates random values for POSTGRES_PASSWORD, FLASK_SECRET_KEY, and AUTH_SECRET.

5. Configure .env

nano .env

Required Changes

LLM API Key — set at least one:

OPENROUTER_API_KEY=sk-or-v1-...     # Recommended — one key, many models
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_AI_API_KEY=AIza...
OPENAI_API_KEY=sk-...

LLM Provider Mode — must match whichever key you set (see LLM Providers for all options):

LLM_PROVIDER_MODE=openrouter   # for OPENROUTER_API_KEY (default)
LLM_PROVIDER_MODE=direct # for direct provider keys (Anthropic, OpenAI, Google, etc.)

Model selection — when using LLM_PROVIDER_MODE=direct, all model env vars must use the same provider as your API key. If omitted, Aurora defaults to Anthropic models:

# Example: Google AI
MAIN_MODEL=google/gemini-3.1-pro-preview
RCA_MODEL=google/gemini-2.5-flash
SUMMARIZATION_MODEL=google/gemini-2.5-flash

# Example: Anthropic (default, no need to set explicitly)
MAIN_MODEL=anthropic/claude-sonnet-4.6
RCA_MODEL=anthropic/claude-haiku-4.5

See LLM Providers for the full list of valid model names per provider.

VM URLs — replace YOUR_VM_IP with your VM's public IP (or internal/VPN IP — see note below):

FRONTEND_URL=http://YOUR_VM_IP:3000
NEXT_PUBLIC_BACKEND_URL=http://YOUR_VM_IP:5080
NEXT_PUBLIC_WEBSOCKET_URL=ws://YOUR_VM_IP:5006
SEARXNG_URL=http://YOUR_VM_IP:8082

Leave BACKEND_URL=http://aurora-server:5080 as-is — that's for internal container-to-container communication.

Private/Internal Network

If accessing via VPN, private subnet, or reverse proxy, use that IP/hostname instead of the public IP (e.g., 10.8.0.1 for a WireGuard tunnel, 192.168.x.x for a private subnet, https://aurora.internal for a reverse proxy). BACKEND_URL always stays as the internal Docker name regardless.

Save and exit (Ctrl+X, Y, Enter in nano).

Static IPs

Most cloud providers assign ephemeral public IPs by default — they change if you stop and restart the VM. Reserve a static/elastic IP through your provider's console so you only need to configure the URLs once.

6. Build and Start

Choose one:

Option A — Build from source (recommended for most deployments):

make prod-local

Builds all Docker images from the cloned source code. Slower on first run (several minutes) but ensures you have the latest code and all connectors.

Option B — Pull prebuilt images (faster, but uses published releases):

make prod-prebuilt

Pulls prebuilt images from GHCR instead of building locally. Faster to start, but uses the last published release.

7. Get and Set the Vault Token

After the stack is running, the vault-init sidecar initializes Vault and generates a root token. Extract it and write it into .env:

# Wait ~30 seconds for vault-init to finish, then:
VAULT_TOKEN=$(docker exec aurora-vault cat /vault/init/keys.json | jq -r '.root_token') \
&& sed -i "s|^VAULT_TOKEN=.*|VAULT_TOKEN=$VAULT_TOKEN|" .env

# Verify it was written
grep VAULT_TOKEN .env

If the command fails (container not ready yet), wait and retry. Check vault-init status with:

docker logs aurora-vault-init

8. Restart to Apply Vault Token

# Use whichever command you chose in step 6
make down && make prod-local # if you built from source
make down && make prod-prebuilt # if you pulled prebuilt images

9. Open Firewall Ports

Aurora needs three ports accessible from outside the VM:

PortService
3000Frontend (Next.js)
5080Backend API (Flask)
5006WebSocket (Chatbot)

How you open these depends on your cloud provider:

AWS — Edit the instance's Security Group: add inbound rules for TCP 3000, 5080, 5006.

GCP — Create a Firewall Rule under VPC Network > Firewall: allow ingress TCP 3000, 5080, 5006.

Azure — Edit the Network Security Group attached to the VM: add inbound security rules for TCP 3000, 5080, 5006.

DigitalOcean — Create or edit a Cloud Firewall and add inbound rules for TCP 3000, 5080, 5006, then attach it to your droplet.

Any provider — Find the network firewall / security group attached to your VM and allow inbound TCP on ports 3000, 5080, and 5006.

For the source IP range, use your own IP (e.g., 1.2.3.4/32 — find it at whatismyip.com) to restrict access to just you, or 0.0.0.0/0 for public access.

Security

Setting source to 0.0.0.0/0 makes the instance accessible to anyone on the internet. Aurora has its own login system, but for a test/internal deployment, restrict to your own IP for safety. Consider enabling rate limiting (RATE_LIMITING_ENABLED=true in .env) if exposing publicly.

OS-level firewall — Most cloud VMs don't enable an OS-level firewall by default. If yours does (check with sudo ufw status or sudo firewall-cmd --state), also open the ports there:

# Ubuntu/Debian (ufw)
sudo ufw allow 3000 && sudo ufw allow 5080 && sudo ufw allow 5006

# CentOS/RHEL (firewalld)
sudo firewall-cmd --permanent --add-port=3000/tcp --add-port=5080/tcp --add-port=5006/tcp
sudo firewall-cmd --reload

10. Access Aurora

Open in your browser:

http://YOUR_VM_IP:3000

You must include the :3000 port — plain http://YOUR_VM_IP/ (port 80) will not work.

Verify Health

# From inside the VM
curl http://localhost:5080/health/liveness

# Check all containers are running
docker compose -f docker-compose.prod-local.yml ps

Ongoing Operations

# View logs
make prod-logs

# Stop everything
make down

# Restart
make down && make prod-local

# Full cleanup (removes data volumes)
make prod-local-clean

# Nuclear option (removes everything including images)
make prod-local-nuke

Deploying Code Updates

git pull
make down && make prod-local

The NEXT_PUBLIC_* environment variables are injected at container startup, not baked at build time. If you only change those values in .env, you can skip a full rebuild:

docker compose -f docker-compose.prod-local.yml up -d frontend

Secure Deployment (Air-Tight)

Use this path when the target VM has restricted or no outbound internet access (enterprise, government, private infrastructure). All Docker images are pre-built and bundled into a single tarball on a machine with internet access, then transferred to the VM. Nothing is fetched from the internet during deployment.

Prerequisites:

  • You have received the airtight bundle (aurora-airtight-<version>-<arch>.tar.gz) and its checksum file (aurora-airtight-<version>-<arch>.tar.gz.sha256)
  • The target VM meets the hardware requirements (4+ CPU, 8+ GB RAM, 60 GB SSD)
  • Docker and Docker Compose are installed on the VM (see Installing Docker for all OS/architecture combinations, including environments where curl and wget are blocked)
  • You can SSH into the VM

1. Transfer the Bundle to the VM

Move the .tar.gz and .sha256 files to the VM using whatever transfer method your organization permits:

# SCP
scp aurora-airtight-*.tar.gz aurora-airtight-*.sha256 user@VM_IP:~/

# GCS bucket (GCP)
gcloud storage cp aurora-airtight-*.tar.gz aurora-airtight-*.sha256 gs://YOUR_BUCKET/
# Then on the VM:
gcloud storage cp gs://YOUR_BUCKET/aurora-airtight-* ~/

# Azure Blob / AWS S3 / internal file server — use your org's standard method

2. Verify the Bundle Integrity

cd ~
sha256sum -c aurora-airtight-*.sha256

You should see OK. If the check fails, the file was corrupted during transfer — re-transfer it.

3. Clone the Repository

The repo contains configuration files (docker-compose.airtight.yml, Makefile, .env.example) needed to run the stack. No images are pulled during this step.

git clone https://github.com/arvo-ai/aurora.git
cd aurora
make init

4. Configure .env

nano .env

LLM API Key — set at least one:

OPENROUTER_API_KEY=sk-or-v1-...     # Recommended — one key, many models
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_AI_API_KEY=AIza...
OPENAI_API_KEY=sk-...

LLM Provider Mode — must match whichever key you set (see LLM Providers for all options):

LLM_PROVIDER_MODE=openrouter   # for OPENROUTER_API_KEY (default)
LLM_PROVIDER_MODE=direct # for direct provider keys (Anthropic, OpenAI, Google, etc.)

VM URLs — replace YOUR_VM_IP with the VM's public IP (or internal/VPN IP):

FRONTEND_URL=http://YOUR_VM_IP:3000
NEXT_PUBLIC_BACKEND_URL=http://YOUR_VM_IP:5080
NEXT_PUBLIC_WEBSOCKET_URL=ws://YOUR_VM_IP:5006
SEARXNG_URL=http://YOUR_VM_IP:8082

Leave BACKEND_URL=http://aurora-server:5080 as-is — that's for internal container-to-container communication.

Private/Internal Network

If accessing via VPN, private subnet, or reverse proxy, use that IP/hostname instead of the public IP (e.g., 10.8.0.1 for a WireGuard tunnel, 192.168.x.x for a private subnet, https://aurora.internal for a reverse proxy). BACKEND_URL always stays as the internal Docker name regardless.

Save and exit (Ctrl+X, Y, Enter in nano).

5. Load Images and Start

make prod-airtight AIRTIGHT_BUNDLE=~/aurora-airtight-<version>-<arch>.tar.gz

This loads every Docker image from the tarball into the local Docker daemon and starts the full Aurora stack. No outbound network calls are made. First run takes a few minutes while images are loaded.

On subsequent restarts (images already loaded):

make prod-airtight

6. Get and Set the Vault Token

# Wait ~30 seconds for vault-init to finish, then:
VAULT_TOKEN=$(docker exec aurora-vault cat /vault/init/keys.json | jq -r '.root_token') \
&& sed -i "s|^VAULT_TOKEN=.*|VAULT_TOKEN=$VAULT_TOKEN|" .env

grep VAULT_TOKEN .env

7. Restart to Apply Vault Token

make down && make prod-airtight

8. Open Firewall Ports

Same as the standard deployment firewall step — allow inbound TCP on ports 3000, 5080, and 5006.

9. Access Aurora

http://YOUR_VM_IP:3000

Deploying Updates (Air-Tight)

Each new Aurora release requires a fresh bundle. On the connected machine:

git pull && make package-airtight

Transfer the new tarball and checksum to the VM, then:

make down
make prod-airtight AIRTIGHT_BUNDLE=~/aurora-airtight-<version>-<arch>.tar.gz

The .env file stays on the VM and is never part of the bundle.

Creating the Air-Tight Bundle

This section is for the person building the bundle on a machine with internet access.

git clone https://github.com/arvo-ai/aurora.git && cd aurora
make package-airtight

This builds all Aurora images, pulls all third-party images, and saves everything into aurora-airtight-<version>-<arch>.tar.gz with a SHA-256 checksum. The default target architecture is linux/amd64.

To target ARM servers:

PLATFORM=linux/arm64 make package-airtight

If building on Apple Silicon for an x86 server, the default linux/amd64 cross-compiles automatically — no extra flags needed.


Troubleshooting

"no space left on device" During Build

The disk is full. Clean up and consider resizing:

docker image prune -a -f
docker builder prune -f
docker system df

If still not enough, resize the disk through your cloud provider's console and expand the partition:

sudo growpart /dev/sda 1
sudo resize2fs /dev/sda1

Vault Sealed After VM Restart

The vault-init sidecar auto-unseals on startup using keys stored in a persistent Docker volume. If it didn't work:

docker restart aurora-vault-init

"Connection Timed Out" in Browser

  1. Verify the cloud firewall / security group allows TCP 3000, 5080, 5006
  2. Verify the OS-level firewall isn't blocking traffic (sudo ufw status or sudo firewall-cmd --state)
  3. Verify you're using the correct public IP: curl -s ifconfig.me
  4. Verify the frontend is running: docker ps | grep frontend

Public IP Changed

Cloud provider ephemeral IPs change when the VM is stopped and restarted. Update .env with the new IP and recreate the frontend:

nano .env   # update FRONTEND_URL, NEXT_PUBLIC_BACKEND_URL, NEXT_PUBLIC_WEBSOCKET_URL, SEARXNG_URL
docker compose -f docker-compose.prod-local.yml up -d frontend

To avoid this, reserve a static/elastic IP through your cloud provider.

Containers Keep Restarting

Check logs for the failing container:

docker logs aurora-server --tail 50
docker logs aurora-celery_worker-1 --tail 50

Common causes: missing env vars, Vault not ready, database not initialized.