Production Deployment

This guide covers deploying TameFlare to production. TameFlare has three services: the control plane (Next.js), the Gateway v2 (Go proxy), and optionally the legacy v1 Gateway.

Quick reference

| Component | Default port | Required? | |---|---|---| | Control plane (apps/web) | 3000 | Yes (dashboard + v1 API) | | Gateway v2 (apps/gateway-v2) | 9443 (internal API), 10001+ (agent proxy ports) | Yes (proxy enforcement) | | Gateway v1 (apps/gateway) | 8443 | Optional (legacy execution mode) | | Docs (apps/docs) | 3001 | Optional |


Docker Compose (recommended)

The included docker-compose.yml runs the control plane and gateway v2:

git clone https://github.com/tameflare/tameflare.git
cd agent-firewall
cp apps/web/.env.example apps/web/.env.local
# Edit .env.local with your values (see Environment Variables below)
docker compose up -d

The control plane is available at http://localhost:3000 and the gateway v2 internal API at http://localhost:9443.

To also run the legacy v1 gateway:

docker compose --profile legacy up -d

Building images separately

# Control plane
docker build -t TameFlare-web -f apps/web/Dockerfile .
 
# Gateway v2 (proxy)
docker build -t TameFlare-gateway-v2 -f apps/gateway-v2/Dockerfile apps/gateway-v2/
 
# Gateway v1 (legacy)
docker build -t TameFlare-gateway -f apps/gateway/Dockerfile apps/gateway/

Gateway v2 Docker environment

| Variable | Description | Default | |---|---|---| | AAF_GATEWAY_PORT | Internal API port | 9443 | | AAF_ENFORCEMENT_LEVEL | monitor, soft_enforce, or full_enforce | monitor | | AAF_DB_PATH | SQLite database path | /data/gateway.db |

The gateway v2 container exposes ports 9443 (internal API) and 10001-10050 (agent proxy ports). Mount a volume at /data for persistent SQLite storage.


Environment variables

Required

| Variable | Description | Example | |---|---|---| | SIGNING_KEY_PRIVATE | ES256 private key (PEM, base64-encoded) for signing decision tokens | Generate with openssl ecparam -genkey -name prime256v1 | | SIGNING_KEY_PUBLIC | ES256 public key (PEM, base64-encoded) for verifying tokens | Derived from private key |

Recommended

| Variable | Description | Default | |---|---|---| | SETTINGS_ENCRYPTION_KEY | 32-byte hex key for AES-256-GCM encryption of secrets at rest | None (secrets stored in plaintext) | | DASHBOARD_PASSWORD | Legacy shared password for dashboard access | None (use email/password auth instead) | | MAINTENANCE_SECRET | Bearer token for the cleanup endpoint | None | | GATEWAY_URL | URL of the Gateway service | http://localhost:8443 |

Optional

| Variable | Description | Default | |---|---|---| | ORG_ID | Override auto-detected organization ID | Auto-detected from DB | | NEXT_PUBLIC_DOCS_URL | URL of the docs site | http://localhost:3001/docs | | NEXT_PUBLIC_APP_URL | Public URL of the control plane | http://localhost:3000 | | AUDIT_RETENTION_DAYS | Auto-purge audit events older than N days | None (keep forever) | | TURSO_DATABASE_URL | Turso libSQL URL (omit for local SQLite) | libsql://your-db.turso.io | | TURSO_AUTH_TOKEN | Auth token for Turso database | None | | DEFAULT_USER_ROLE | Default role for new users | viewer |

Generating the encryption key

# Generate a random 32-byte hex key
openssl rand -hex 32

Set this as SETTINGS_ENCRYPTION_KEY in your environment. Without it, Slack tokens, GitHub PATs, and other integration secrets are stored in plaintext in the database.

Generating decision token keys

# Generate ES256 private key
openssl ecparam -genkey -name prime256v1 -noout -out private.pem
 
# Extract public key
openssl ec -in private.pem -pubout -out public.pem
 
# Set as env vars (inline the PEM content)
export SIGNING_KEY_PRIVATE=$(cat private.pem | base64 -w0)
export SIGNING_KEY_PUBLIC=$(cat public.pem | base64 -w0)

Database: SQLite vs Turso

TameFlare uses libSQL (SQLite-compatible) via Drizzle ORM. You have two options:

SQLite (local file)

Best for: single-server deployments, development, small teams.

When both TURSO_DATABASE_URL and TURSO_AUTH_TOKEN are omitted, TameFlare uses a local SQLite file at apps/web/local.db. No configuration needed.

  • Zero configuration
  • Data stored in a single file on disk
  • Back up by copying the file
  • No network latency

Turso (hosted libSQL)

Best for: multi-region, serverless, or managed deployments.

TURSO_DATABASE_URL=libsql://your-database-name.turso.io
TURSO_AUTH_TOKEN=your-turso-auth-token
  • Free tier: 9 GB storage, 25M row reads/month
  • Edge replicas for low-latency reads
  • Automatic backups
  • Create a database at turso.tech

Switching between them

  1. Update TURSO_DATABASE_URL (and TURSO_AUTH_TOKEN) in your environment
  2. Run pnpm db:push from apps/web/ to apply the schema
  3. Restart the control plane

Data is not automatically migrated between SQLite and Turso. Export your config first via the dashboard export feature if needed.


TLS / HTTPS

TameFlare does not terminate TLS itself. Use a reverse proxy in front of the control plane:

Nginx

server {
    listen 443 ssl;
    server_name TameFlare.yourcompany.com;
 
    ssl_certificate /etc/ssl/certs/TameFlare.crt;
    ssl_certificate_key /etc/ssl/private/TameFlare.key;
 
    location / {
        proxy_pass http://localhost:3000;
        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 /gateway/ {
        proxy_pass http://localhost:8443/;
    }
}

Caddy (automatic HTTPS)

TameFlare.yourcompany.com {
    reverse_proxy localhost:3000
}

gateway.yourcompany.com {
    reverse_proxy localhost:8443
}

Cloud platforms

Most cloud platforms provide TLS termination automatically. Below are quick-start guides for popular options.

Fly.io

# Install flyctl, then from the project root:
fly launch --name TameFlare-control-plane --dockerfile apps/web/Dockerfile
fly secrets set DATABASE_URL=file:local.db SETTINGS_ENCRYPTION_KEY=$(openssl rand -hex 32)
 
# Deploy the Gateway as a separate app
cd apps/gateway
fly launch --name TameFlare-gateway --dockerfile Dockerfile
fly secrets set CONTROL_PLANE_URL=https://TameFlare-control-plane.fly.dev

Fly.io provides automatic TLS, persistent volumes (for SQLite), and multi-region support. Use fly volumes create for SQLite persistence.

Railway

  1. Connect your GitHub repo at railway.app
  2. Create two services: one for apps/web (root directory: apps/web), one for apps/gateway (root directory: apps/gateway)
  3. Set environment variables in the Railway dashboard
  4. Railway auto-detects the Dockerfile and deploys

Railway provides automatic TLS and a free tier. Use Turso instead of SQLite for Railway since the filesystem is ephemeral.

AWS (ECS + Fargate)

  1. Build and push Docker images to ECR
  2. Create an ECS cluster with two Fargate services (control plane + gateway)
  3. Use an Application Load Balancer (ALB) with ACM certificate for TLS
  4. Store the SQLite file on an EFS volume, or use Turso for managed storage
  5. Set environment variables via ECS task definition secrets (reference from AWS Secrets Manager)
# Build and push
docker build -t TameFlare-web -f apps/web/Dockerfile .
docker tag TameFlare-web:latest <account>.dkr.ecr.<region>.amazonaws.com/TameFlare-web:latest
docker push <account>.dkr.ecr.<region>.amazonaws.com/TameFlare-web:latest

For AWS, Turso is recommended over local SQLite to avoid EFS latency and simplify scaling.


Docker on a VPS

# On your server
git clone https://github.com/tameflare/tameflare.git
cd agent-firewall
cp apps/web/.env.example apps/web/.env.local
# Edit .env.local
docker compose up -d
 
# Optional: set up systemd service for auto-restart
# Optional: set up Caddy or Nginx for TLS

Approval channels

TameFlare supports two approval modes:

Dashboard-only (default)

Approvals appear on the Approvals page in the dashboard. Admins and members can approve or deny directly from the UI. No external services required.

This is the default mode — it works out of the box with no configuration.

Slack integration

For teams that want approval notifications in Slack:

  1. Create a Slack app at api.slack.com/apps
  2. Add the chat:write and chat:read bot scopes
  3. Install the app to your workspace
  4. Copy the Bot Token and Signing Secret to Settings > Integrations in the dashboard
  5. Set the channel ID where approval messages should be posted

Slack approval messages include approve/deny buttons that update the dashboard in real time.

Both modes work simultaneously — Slack notifications are additive, not a replacement for dashboard approvals.


Maintenance

Database cleanup

Run the cleanup endpoint periodically to purge expired data:

curl -X POST https://your-TameFlare-instance/api/maintenance/cleanup \
  -H "Authorization: Bearer $MAINTENANCE_SECRET"

This removes expired nonces, sessions, and optionally old audit events (controlled by AUDIT_RETENTION_DAYS).

Recommended: Run daily via cron or a scheduled task.

Backups

  • SQLite: Copy the .db file. Consider a cron job: cp local.db backups/TameFlare-$(date +%Y%m%d).db
  • Turso: Automatic backups included. Use turso db shell for manual exports.
  • Config: Use the dashboard Export feature (GET /api/dashboard/export) to back up policies and agents as JSON.

Health monitoring

The GET /api/health endpoint returns the status of the database and gateway:

curl https://your-TameFlare-instance/api/health

Returns 200 when healthy, 503 when any check fails. Wire this into your uptime monitoring (e.g., UptimeRobot, Pingdom, or a simple cron check).


Performance & architecture

Architecture overview

Agent → Control Plane (Next.js) → Policy Engine → Decision
                                       ↓
                                  Gateway (Go) → External Tool (GitHub, Webhook, etc.)
  • Control plane — Node.js / Next.js. Handles API requests, policy evaluation, token signing, audit logging, and the dashboard UI. Single process, single SQLite file.
  • Gateway — Go binary. Receives execute requests from the control plane, verifies decision tokens, and calls external APIs. Stateless — can be scaled horizontally.
  • Database — SQLite (local) or Turso (hosted libSQL). All state lives here: agents, policies, action requests, decisions, audit events, sessions.

Latency

| Operation | Typical latency | Notes | |---|---|---| | Policy evaluation | 1–5ms | In-memory, no DB calls during evaluation | | POST /api/v1/actions (full request) | 10–30ms | Auth + policy eval + DB writes + token signing | | Gateway execution | 50–500ms | Depends on external API (GitHub, webhook target) | | Token signing (ES256) | <1ms | ECDSA P-256 via jose library |

Scaling

Single server (recommended for most teams):

  • SQLite handles thousands of writes/second
  • A single Node.js process handles ~500 concurrent action requests
  • The Gateway is stateless and adds minimal overhead

Horizontal scaling:

  • Control plane: Use Turso (hosted libSQL) instead of local SQLite to share state across instances. Put a load balancer in front.
  • Gateway: Deploy multiple instances behind a load balancer. Each instance is stateless — it verifies tokens against the control plane on every request.
  • Rate limiting: Currently in-memory per process. For multi-instance deployments, rate limits reset independently per instance. A Redis-backed rate limiter is on the roadmap.

Resource requirements

| Component | CPU | Memory | Disk | |---|---|---|---| | Control plane | 1 vCPU | 512MB | 1GB+ (depends on audit retention) | | Gateway | 0.5 vCPU | 128MB | Minimal (stateless) |

These are minimums. For production with >10 agents and >1000 actions/day, use 2 vCPU / 1GB for the control plane.

Serverless deployment notes

TameFlare's control plane is a standard Next.js application and can run on serverless platforms (Vercel, Netlify, AWS Lambda) with caveats:

  • Database: Local SQLite does not work in serverless (ephemeral filesystem). Use Turso (hosted libSQL) instead.
  • Rate limiting: The in-memory sliding window resets on each cold start. For consistent rate limiting across invocations, an external store (Redis) would be needed — not yet implemented.
  • Signing keys: Set SIGNING_KEY_PRIVATE and SIGNING_KEY_PUBLIC explicitly. Auto-generated keys change on each cold start, invalidating outstanding tokens.
  • Gateway: The Go gateway is a long-running process and cannot run on serverless. Deploy it on a VM, container, or managed service (Fly.io, Railway, ECS).
  • WebSocket/SSE: Not applicable — TameFlare uses polling and webhooks, not persistent connections.

Recommendation: For simplicity and reliability, deploy TameFlare on a single VPS or container (Docker Compose). Serverless adds complexity without significant benefit for most TameFlare workloads.