Aira

Self-Hosted Deployment

Deploy Aira on your own infrastructure with Docker.

Overview

Aira runs as three Docker containers plus PostgreSQL and Redis. You provide your own AI provider keys, and all data stays on your infrastructure.

ServiceImagePortRole
APIghcr.io/aira-proof/backend8000Backend, consensus engine, receipts
Frontendghcr.io/aira-proof/frontend3000Dashboard
Docsghcr.io/aira-proof/docs3001Documentation
PostgreSQLpostgres:16-alpine5432Database
Redisredis:7-alpine6379Cache + job queue

A reverse proxy (Caddy, Nginx, or Traefik) sits in front for HTTPS.

Prerequisites

  • Docker Engine 24+ and Docker Compose v2
  • A GHCR access token (provided by Aira — contact customers@softure-ug.de)
  • At least 2 AI provider API keys (OpenAI, Anthropic, or Google)
  • 4 GB RAM minimum, 8 GB recommended

Step 1: Authenticate with the container registry

Aira images are private. Use the access token provided to you:

echo $AIRA_REGISTRY_TOKEN | docker login ghcr.io -u aira-proof --password-stdin

Step 2: Create the project directory

mkdir -p /opt/aira
cd /opt/aira

Step 3: Create docker-compose.yml

services:
  api:
    image: ghcr.io/aira-proof/backend:latest
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
    restart: unless-stopped
    env_file: .env
    environment:
      - DATABASE_URL=postgresql+asyncpg://aira:${DB_PASS}@db:5432/aira
      - REDIS_URL=redis://redis:6379/0
      - ENVIRONMENT=production
      - DEPLOYMENT_MODE=selfhosted
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"]
      interval: 15s
      timeout: 10s
      retries: 3
      start_period: 10s
    ports:
      - "8000:8000"

  migrate:
    image: ghcr.io/aira-proof/backend:latest
    command: alembic upgrade head
    env_file: .env
    environment:
      - DATABASE_URL=postgresql+asyncpg://aira:${DB_PASS}@db:5432/aira
    depends_on:
      db:
        condition: service_healthy
    restart: "no"

  frontend:
    image: ghcr.io/aira-proof/frontend:latest
    restart: unless-stopped
    environment:
      - NEXT_PUBLIC_API_URL=${API_URL:-http://localhost:8000}
      - AUTH_SECRET=${AUTH_SECRET}
      - AUTH_TRUST_HOST=true
    ports:
      - "3000:3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/"]
      interval: 15s
      timeout: 5s
      retries: 3

  docs:
    image: ghcr.io/aira-proof/docs:latest
    restart: unless-stopped
    ports:
      - "3001:3000"

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${DB_PASS}
      - POSTGRES_DB=aira
      - POSTGRES_USER=aira
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U aira"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  pgdata:
  redisdata:

Step 4: Configure environment variables

Create a .env file:

# Database (choose a strong password)
DB_PASS=your-strong-database-password

# Security — generate with: openssl rand -hex 32
SECRET_KEY=your-64-char-random-secret
AUTH_SECRET=your-random-auth-secret

# Signing key — generate with:
# python3 -c "from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey; print(Ed25519PrivateKey.generate().private_bytes_raw().hex())"
SIGNING_PRIVATE_KEY_HEX=

# License key (required for self-hosted — contact customers@softure-ug.de)
AIRA_LICENSE_KEY=your-license-key

# AI Provider Keys — at least 2 required for multi-model consensus
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_API_KEY=AIza...

# Frontend URL (your domain — used for email verification links and CORS)
FRONTEND_URL=https://app.yourdomain.com

# API URL (your API domain)
API_URL=https://api.yourdomain.com

# CORS — add your frontend domain
CORS_ALLOWED_ORIGINS=https://app.yourdomain.com

# Optional: Email (for user verification, password reset)
RESEND_API_KEY=
EMAIL_FROM=Aira <noreply@yourdomain.com>

# Optional: OAuth (for social login omit for email/password only)
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=

Required variables

VariableHow to generateDescription
DB_PASSChoose a strong passwordPostgreSQL password
SECRET_KEYopenssl rand -hex 32JWT signing + encryption key
AUTH_SECRETopenssl rand -hex 32NextAuth session encryption
AIRA_LICENSE_KEYProvided by Aira (contact customers@softure-ug.de)Required for self-hosted deployments
DEPLOYMENT_MODESet to selfhostedEnables self-hosted mode (set automatically in docker-compose)
FRONTEND_URLYour frontend domain (e.g. https://app.yourdomain.com)Used for email verification links and CORS
API_URLYour API domain (e.g. https://api.yourdomain.com)Used by the frontend to reach the backend
CORS_ALLOWED_ORIGINSComma-separated list of allowed originsMust include your frontend domain
2 of: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEYFrom each provider's dashboardMulti-model consensus requires at least 2 providers

Optional variables

VariableDefaultDescription
SIGNING_PRIVATE_KEY_HEXAuto-generatedEd25519 key for receipt signing. Set for persistence across container restarts
RESEND_API_KEYFor sending verification and password reset emails (alternative to SMTP)
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORDSMTP email configuration (alternative to Resend)
EMAIL_FROMAira <customers@softure-ug.de>Email sender address
ADMIN_API_KEYSecures the /metrics endpoint
RATE_LIMIT_PER_MINUTE60Rate limit for authenticated requests
PUBLIC_RATE_LIMIT_PER_MINUTE30Rate limit for public requests
AUTH_GOOGLE_ID/SECRETGoogle OAuth (leave empty to disable — credentials auth works without it)
AUTH_GITHUB_ID/SECRETGitHub OAuth (leave empty to disable)
AUTH_GITLAB_ID/SECRETGitLab OAuth (leave empty to disable)

OAuth is optional. Email/password authentication works without any OAuth provider configured. OAuth providers are only needed if you want social login buttons on the login page.

Email is required for registration. User registration requires email verification. Configure either Resend (RESEND_API_KEY) or SMTP (SMTP_HOST, SMTP_USER, SMTP_PASSWORD) for email delivery to work.

Variables set automatically

These are configured in docker-compose.yml — do not override:

VariableValueWhy
DEPLOYMENT_MODEselfhostedRemoves cloud-only features, sets unlimited usage
AUTH_TRUST_HOSTtrueRequired when running behind a proxy or non-standard hostname
DATABASE_URLInternal Docker URLConnects API to the Postgres container
REDIS_URLInternal Docker URLConnects API to the Redis container

Step 5: Deploy

cd /opt/aira

# Start infrastructure
docker compose up -d db redis

# Wait for database to be ready
sleep 5

# Run database migrations
docker compose run --rm migrate

# Start all services
docker compose up -d

Step 6: Verify

# API health
curl http://localhost:8000/health
# Expected: {"status":"healthy","checks":{"database":"ok"}}

# Frontend (should redirect to login)
curl -o /dev/null -w "%{http_code}" http://localhost:3000/dashboard/cases/new
# Expected: 307

# Docs
curl -o /dev/null -w "%{http_code}" http://localhost:3001/
# Expected: 200

Reverse proxy

For HTTPS, place a reverse proxy in front. Aira ships a Caddyfile template for self-hosted deployments at deploy/Caddyfile.selfhosted. It uses environment variables for domain names (Caddy supports {$ENV_VAR} syntax):

{$API_DOMAIN:api.localhost} {
    reverse_proxy aira-api:8000
    encode gzip

    header {
        Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "strict-origin-when-cross-origin"
    }
}

{$FRONTEND_DOMAIN:app.localhost} {
    reverse_proxy aira-frontend:3000
    encode gzip
}

{$DOCS_DOMAIN:docs.localhost} {
    reverse_proxy aira-docs:3000
    encode gzip
}

Set API_DOMAIN, FRONTEND_DOMAIN, and DOCS_DOMAIN environment variables for Caddy, or replace the placeholders with your actual domains.

Updating to a new version

cd /opt/aira

# Pull latest images
docker compose pull

# Run any new migrations
docker compose run --rm migrate

# Restart services
docker compose up -d

Self-hosted vs Cloud

FeatureCloudSelf-hosted
AI provider keysAira-managed or Provider CredentialsProvider Credentials only — you provide all keys
Case limits25/month (free tier)Unlimited
Usage displayShows plan limitsShows "Unlimited"
EmailManaged by AiraConfigure your own Resend or SMTP
Data residencyEU (Frankfurt)Your infrastructure
UpdatesAutomaticPull latest images manually
SupportCommunity (free) or dedicated (enterprise)Included in license

Troubleshooting

API won't start

docker logs aira-api-1 --tail 30

Common causes:

  • Missing or incorrect DB_PASS — check the password matches between .env and what Postgres was initialized with
  • Database not ready — wait for the healthcheck or restart with docker compose restart api
  • Missing AI provider keys — need at least 2 of OpenAI, Anthropic, Google

Frontend accessible without login

Verify AUTH_SECRET is set in .env and that AUTH_TRUST_HOST=true is in the frontend environment section of docker-compose.yml.

Migrations fail

# Check current migration version
docker compose exec db psql -U aira -c "SELECT version_num FROM alembic_version;"

# Re-run migrations
docker compose run --rm migrate

Database password mismatch

If you changed DB_PASS after the database was first created, the old password is stored in the Postgres data volume. Either:

  • Reset the volume: docker compose down && docker volume rm aira_pgdata && docker compose up -d
  • Or change the password inside Postgres: docker compose exec db psql -U aira -c "ALTER USER aira PASSWORD 'new-password';"

Container can't pull images

Verify GHCR authentication:

docker pull ghcr.io/aira-proof/backend:latest

If it fails, re-authenticate with your provided token.

Support

For self-hosted deployment support, contact customers@softure-ug.de.

On this page