Skip to content

VPS Deployment

Deploy AegisAI on a virtual private server (VPS) for production use with TLS, reverse proxy, and hardened configuration.

Server Requirements

Resource Minimum Recommended
CPU 2 vCPU 4 vCPU
RAM 4 GB 8 GB
Disk 40 GB SSD 100 GB SSD
OS Ubuntu 22.04 LTS Ubuntu 24.04 LTS
Provider Any (AWS, DigitalOcean, Hetzner, etc.)

Step 1: Server Setup

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

# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

# Install Docker Compose plugin
sudo apt install docker-compose-plugin -y

# Configure firewall
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Step 2: Clone and Configure

cd /opt
sudo git clone https://github.com/valera7623/AegisAI.git
sudo chown -R $USER:$USER aegisai
cd aegisai

# Create production environment
cp .env.example .env

Edit .env with production values:

# Generate secrets
SECRET_KEY=$(openssl rand -hex 32)
ADMIN_API_KEY=$(openssl rand -hex 24)
WEBHOOK_SECRET=$(openssl rand -hex 24)

cat >> .env << EOF
SECRET_KEY=${SECRET_KEY}
ADMIN_API_KEY=${ADMIN_API_KEY}
WEBHOOK_SECRET=${WEBHOOK_SECRET}
ENVIRONMENT=production
SUPER_ADMIN_EMAIL=admin@yourcompany.com
SUPER_ADMIN_PASSWORD=YourStrongPassword123!
LOG_LEVEL=INFO
EOF

Step 3: Deploy Services

For co-located VPS deployment (e.g. with Traefik):

chmod +x deploy.sh
./deploy.sh

The deploy script builds app/frontend, stops running app containers before recreate, and waits for /api/v1/health/ready.

Manual alternative:

docker compose -f docker-compose.prod.yml up -d --build
curl http://localhost:8000/api/v1/health/ready

Run migrations after first deploy:

docker compose -f docker-compose.prod.yml exec app poetry run alembic upgrade head

Configure SSO callback URL in .env when using IdP login — see SSO.

Step 4: Build Frontend

cd frontend
npm install
npm run build

The production build outputs to frontend/dist/.

Step 5: Reverse Proxy (Caddy)

Install Caddy for automatic TLS:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy -y

Create /etc/caddy/Caddyfile:

api.yourcompany.com {
    reverse_proxy localhost:8000

    @websocket {
        path /api/v1/ws
    }
    reverse_proxy @websocket localhost:8000 {
        header_up Connection {>Connection}
        header_up Upgrade {>Upgrade}
    }
}

app.yourcompany.com {
    root * /opt/aegisai/frontend/dist
    file_server
    try_files {path} /index.html

    handle /api/* {
        reverse_proxy localhost:8000
    }
}
sudo systemctl reload caddy

Step 6: DNS Configuration

Record Type Value
api.yourcompany.com A <server-ip>
app.yourcompany.com A <server-ip>

Step 7: Post-Deployment

# Create first tenant
curl -X POST https://api.yourcompany.com/api/v1/tenants \
  -H "X-Admin-Key: ${ADMIN_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"name": "production-tenant"}'

# Verify frontend
curl -s -o /dev/null -w "%{http_code}" https://app.yourcompany.com

# Log in at https://app.yourcompany.com

Production Docker Compose Adjustments

Create docker-compose.override.yml to restrict port exposure:

services:
  postgres:
    ports: []  # Remove external port

  redis:
    ports: []  # Remove external port

  opa:
    ports: []  # Remove external port

  app:
    ports:
      - "127.0.0.1:8000:8000"  # Localhost only
    command: >
      sh -c "poetry run alembic upgrade head &&
             poetry run python scripts/init_super_admin.py &&
             poetry run uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4"
    restart: unless-stopped

  frontend:
    profiles: ["dev"]  # Don't start in production

Nginx Alternative

If you prefer Nginx over Caddy:

server {
    listen 443 ssl http2;
    server_name app.yourcompany.com;

    ssl_certificate /etc/letsencrypt/live/app.yourcompany.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.yourcompany.com/privkey.pem;

    root /opt/aegisai/frontend/dist;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /api/v1/ws {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }
}

Systemd Service (Optional)

For automatic restart on boot without Docker Compose restart policies:

# /etc/systemd/system/aegisai.service
[Unit]
Description=AegisAI Platform
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/aegisai
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target
sudo systemctl enable aegisai
sudo systemctl start aegisai

Updating

cd /opt/aegisai
git pull origin main
docker compose build app
docker compose exec app poetry run alembic upgrade head
docker compose up -d app

# Rebuild frontend if changed
cd frontend && npm run build

Security Hardening

Measure Implementation
TLS Caddy/Nginx with Let's Encrypt
Firewall UFW — only 22, 80, 443
DB exposure Remove port mapping, internal network only
Secrets Environment variables, not in Git
SSH Key-only auth, disable password login
Updates unattended-upgrades for security patches
Fail2ban Protect SSH and API from brute force