AuditTrail Deployment Guide

Docker Compose Production Setup

The included docker-compose.yml defines three services: the FastAPI API, the Next.js frontend, and an nginx reverse proxy.

Build and Start

cd AuditTrailCodebase

# Set the secret key (required for production)
export AUDITTRAIL_SECRET_KEY=$(openssl rand -hex 32)

# Build and start all services
docker compose up -d --build

The stack exposes:

PortServiceDescription
80nginxPublic entry point (routes to API and frontend)
8000apiFastAPI backend (not exposed publicly behind nginx)
3000webNext.js frontend (not exposed publicly behind nginx)

In production, you should remove the direct port mappings for api and web so they are only reachable through nginx. Edit docker-compose.yml:

services:
  api:
    # Remove or comment out:
    # ports:
    #   - "8000:8000"
  web:
    # Remove or comment out:
    # ports:
    #   - "3000:3000"

Persistent Data

The API stores its SQLite database in a named Docker volume api-data, mapped to /app/data inside the container. This volume survives container rebuilds.

To inspect the volume:

docker volume inspect audittrailcodebase_api-data

Environment Variables for Production

Create a .env file in the project root or set these variables in your deployment environment:

# REQUIRED: Change from the default. Generate with: openssl rand -hex 32
AUDITTRAIL_SECRET_KEY=your-production-secret-key

# Database URL
# SQLite (default, fine for small deployments):
DATABASE_URL=sqlite+aiosqlite:///data/audittrail.db
# PostgreSQL (recommended for production):
# DATABASE_URL=postgresql+asyncpg://user:password@db-host:5432/audittrail

# Disable debug mode
AUDITTRAIL_DEBUG=false

# Lock down CORS to your actual domain
AUDITTRAIL_CORS_ORIGINS=["https://your-domain.com"]

# PII redaction (enabled by default, keep it on)
AUDITTRAIL_PII_REDACTION_ENABLED=true

# Data retention
AUDITTRAIL_RETENTION_DAYS=90

# Frontend environment
NEXT_PUBLIC_API_URL=https://your-domain.com/api
NEXT_PUBLIC_WS_URL=wss://your-domain.com/ws

Nginx Reverse Proxy Configuration

The included nginx.conf handles routing, WebSocket upgrades, rate limiting, and security headers. Key features:

  • /api/ and /ws/ requests are proxied to the FastAPI backend
  • All other requests are proxied to the Next.js frontend
  • WebSocket upgrade headers are set for /ws/ routes
  • Rate limiting: 10 requests/second per IP on API endpoints (burst of 20)
  • Security headers: X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy
  • Gzip compression enabled
  • Long timeouts for ablation endpoints (300s) and WebSocket connections (3600s)

Adding TLS/SSL

For HTTPS, add a TLS server block. Example with Let's Encrypt certificates:

server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

    # ... same location blocks as the existing server block ...
}

server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$host$request_uri;
}

Mount your certificates into the nginx container via a volume:

nginx:
  volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf:ro
    - /etc/letsencrypt:/etc/letsencrypt:ro

Custom Domain

Update the NEXT_PUBLIC_API_URL and NEXT_PUBLIC_WS_URL environment variables for the web service to match your domain:

web:
  environment:
    - NEXT_PUBLIC_API_URL=https://your-domain.com/api
    - NEXT_PUBLIC_WS_URL=wss://your-domain.com/ws

SQLite WAL Mode Considerations

SQLite is the default database for AuditTrail. In production with SQLite, note the following:

WAL (Write-Ahead Logging) mode is recommended for better concurrent read performance. SQLite enables WAL mode automatically with aiosqlite, but you can verify it:

sqlite3 data/audittrail.db "PRAGMA journal_mode;"

If it does not return wal, enable it:

sqlite3 data/audittrail.db "PRAGMA journal_mode=WAL;"

Limitations of SQLite in production:

  • File-level write locking -- only one writer at a time. Under heavy ingest load, writes queue up.
  • No connection pooling -- each request opens/closes the file.
  • Database file must be on a local filesystem (not NFS or network shares).
  • Maximum recommended concurrent users: ~10-20 for an observability dashboard.

When to switch to PostgreSQL: If you experience write contention (slow ingest under load), need concurrent multi-user access, or want JSONB queries for metadata search, set DATABASE_URL to a PostgreSQL connection string:

DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/audittrail

No code changes are required -- SQLAlchemy abstracts the database layer. Run Alembic migrations after switching:

cd apps/api
alembic upgrade head

Data Backup

SQLite Backup

Use the SQLite .backup command for a consistent snapshot (safe even while the server is running):

sqlite3 data/audittrail.db ".backup data/audittrail_backup_$(date +%Y%m%d).db"

For Docker deployments, exec into the container:

docker compose exec api sqlite3 /app/data/audittrail.db \
  ".backup /app/data/audittrail_backup.db"

Then copy the backup out:

docker compose cp api:/app/data/audittrail_backup.db ./backups/

Automated Backup Script

#!/bin/bash
# backup.sh -- Run via cron: 0 2 * * * /path/to/backup.sh
BACKUP_DIR="/path/to/backups"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

docker compose exec -T api sqlite3 /app/data/audittrail.db \
  ".backup /app/data/backup_${DATE}.db"

docker compose cp "api:/app/data/backup_${DATE}.db" "$BACKUP_DIR/"

# Remove backups older than 30 days
find "$BACKUP_DIR" -name "backup_*.db" -mtime +30 -delete

Generated Reports

PDF reports are stored at data/reports/ inside the API container. Back these up separately if needed:

docker compose cp api:/app/data/reports/ ./backups/reports/

Monitoring

Health Endpoint Polling

The API exposes GET /api/v1/health for health checks. The Docker Compose file already configures a health check that polls this endpoint every 30 seconds.

For external monitoring, poll the health endpoint and alert on:

  • status is not "ok" (database connectivity issue)
  • db_connected is false
  • rules_loaded is 0 (rule directory misconfigured)
  • Endpoint is unreachable (service down)

Example with curl:

curl -sf http://localhost:8000/api/v1/health | python3 -c "
import json, sys
data = json.load(sys.stdin)
if data['status'] != 'ok':
    print(f'ALERT: AuditTrail health degraded: {data}')
    sys.exit(1)
print('OK')
"

Instance Info

GET /api/v1/instance provides additional metrics useful for dashboards:

curl http://localhost:8000/api/v1/instance

Returns version, connected agent count, rule count, and uptime.

Container Logs

# All services
docker compose logs -f

# API only
docker compose logs -f api

# Last 100 lines
docker compose logs --tail=100 api

Security Checklist

Before deploying to production, verify each item:

Authentication and Secrets

  • Change the secret key. The default dev-secret-key-change-in-production is not safe. Generate a new one:
    openssl rand -hex 32
    
  • Set AUDITTRAIL_DEBUG=false. Debug mode exposes verbose error details.

Network Security

  • Configure CORS origins. Set AUDITTRAIL_CORS_ORIGINS to your actual domain(s) only. Do not use ["*"] in production.
  • Enable TLS/SSL. Serve all traffic over HTTPS. WebSocket connections should use wss://.
  • Remove direct port exposure. Remove the ports mappings for api and web services so they are only accessible through nginx.
  • Review nginx rate limiting. The default is 10 req/s per IP with burst of 20. Adjust for your traffic patterns.

Data Protection

  • Enable PII redaction. Set AUDITTRAIL_PII_REDACTION_ENABLED=true (enabled by default). This redacts personally identifiable information from ingested trace data.
  • Configure data retention. Set AUDITTRAIL_RETENTION_DAYS to comply with your data retention policy.
  • Secure the database file. If using SQLite, ensure the data/ directory has restrictive file permissions. The Dockerfile creates a dedicated audittrail user for this purpose.
  • Protect API keys. The secret key portion (sk-at-...) is shown only once at creation time. It is stored as an Argon2 hash.

Constitutional Rules

  • Review default rules. The rules/ directory ships with 6 default rules (bulk delete prevention, cost guard, latency SLO, PII guard, token budget, cite sources). Review and customize for your use case.
  • Mount rules as read-only. The Docker Compose file mounts ./rules:/app/rules:ro -- keep the :ro flag to prevent the application from modifying rule files.

Backup

  • Set up automated backups. See the Data Backup section above.
  • Test restore procedure. Verify you can restore from a backup by copying it back and restarting the service.