Skip to main content
Deploying TrailBase to production requires careful consideration of security, reliability, and performance. This guide covers best practices and essential configuration for production environments.

Production Checklist

Before deploying TrailBase to production, ensure you’ve completed these essential steps:
1

Set a public URL

Configure the public URL for OAuth redirects and email links:
trail --public-url https://yourdomain.com run
Or via environment variable:
export PUBLIC_URL=https://yourdomain.com
trail run
2

Bind to the correct address

For production behind a reverse proxy:
trail run --address 0.0.0.0:4000
For direct internet exposure, bind to localhost and use a reverse proxy:
trail run --address localhost:4000
3

Configure CORS

Restrict CORS origins to your domains:
trail run --cors-allowed-origins "https://yourdomain.com,https://www.yourdomain.com"
4

Set up SMTP for emails

Configure email delivery for auth verification and password resets. Edit traildepot/config.textproto:
email: {
  smtp_host: "smtp.example.com"
  smtp_port: 587
  smtp_encryption: STARTTLS
  smtp_username: "[email protected]"
  smtp_password: "<REDACTED>"  # Actual password goes in secrets.textproto
  sender_address: "[email protected]"
  sender_name: "Your App"
}
5

Review authentication settings

Adjust token TTLs in config.textproto:
auth: {
  # Auth token expires after 1 hour (in seconds)
  auth_token_ttl_sec: 3600
  
  # Refresh token expires after 30 days
  refresh_token_ttl_sec: 2592000
}
6

Set up SSL/TLS

Use a reverse proxy (nginx, Caddy, Traefik) for SSL/TLS termination. Never expose TrailBase directly to the internet without HTTPS.
7

Configure backups

Implement automated backups of the data directory (see Backup Strategies below).
8

Set up monitoring

Implement health checks and monitoring (see Monitoring section below).
Never disable or weaken security features for convenience. Always use HTTPS, strong passwords, and proper CORS settings in production.

Security Best Practices

SSL/TLS Configuration

Always run TrailBase behind a reverse proxy with SSL/TLS:
server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/ssl/certs/yourdomain.crt;
    ssl_certificate_key /etc/ssl/private/yourdomain.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://localhost:4000;
        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;
        
        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

File Permissions

Secure the data directory with proper permissions:
# Set ownership to the TrailBase user
chown -R trailbase:trailbase /var/lib/trailbase

# Restrict access to owner only
chmod 700 /var/lib/trailbase

# Protect sensitive files
chmod 600 /var/lib/trailbase/secrets.textproto
chmod 600 /var/lib/trailbase/*.db

Secrets Management

TrailBase separates configuration into two files:
  • config.textproto - Non-sensitive configuration
  • secrets.textproto - Sensitive values (OAuth secrets, SMTP passwords)
Secrets marked with [(secret) = true] in the protobuf schema are automatically redacted to <REDACTED> in config.textproto and stored in secrets.textproto.
You can also override any configuration value using environment variables prefixed with TRAIL_, for example: TRAIL_EMAIL_SMTP_PASSWORD.

Admin Access

For production, consider separating admin UI access:
trail run \
  --address 0.0.0.0:4000 \
  --admin-address 127.0.0.1:4001
This binds:
  • Public API to 0.0.0.0:4000 (accessible externally)
  • Admin UI to 127.0.0.1:4001 (localhost only)
Access the admin UI through an SSH tunnel:
ssh -L 4001:localhost:4001 user@yourserver
# Then browse to http://localhost:4001/_/admin

Rate Limiting

Implement rate limiting at the reverse proxy level:
http {
    limit_req_zone $binary_remote_addr zone=trailbase:10m rate=10r/s;
    
    server {
        location / {
            limit_req zone=trailbase burst=20 nodelay;
            proxy_pass http://localhost:4000;
        }
    }
}

Firewall Configuration

# Only allow HTTP/HTTPS and SSH
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw default deny incoming
sudo ufw enable

Backup Strategies

Regular backups are essential for production deployments.

Full Data Directory Backup

The simplest approach is backing up the entire data directory:
#!/bin/bash
# backup-trailbase.sh

DATA_DIR="/var/lib/trailbase/traildepot"
BACKUP_DIR="/backups/trailbase"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Create tarball of data directory
tar -czf "$BACKUP_DIR/trailbase_$TIMESTAMP.tar.gz" -C "$(dirname $DATA_DIR)" "$(basename $DATA_DIR)"

# Keep only last 7 days of backups
find "$BACKUP_DIR" -name "trailbase_*.tar.gz" -mtime +7 -delete

echo "Backup completed: trailbase_$TIMESTAMP.tar.gz"
Schedule with cron:
# Run backup daily at 2 AM
0 2 * * * /usr/local/bin/backup-trailbase.sh

SQLite Backup

For online backups without stopping the server, use SQLite’s backup API:
#!/bin/bash
# sqlite-backup.sh

DATA_DIR="/var/lib/trailbase/traildepot"
BACKUP_DIR="/backups/trailbase/sqlite"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR/$TIMESTAMP"

# Backup main database
sqlite3 "$DATA_DIR/main.db" ".backup '$BACKUP_DIR/$TIMESTAMP/main.db'"

# Backup logs database
sqlite3 "$DATA_DIR/logs.db" ".backup '$BACKUP_DIR/$TIMESTAMP/logs.db'"

# Copy configuration files
cp "$DATA_DIR/config.textproto" "$BACKUP_DIR/$TIMESTAMP/"
cp "$DATA_DIR/secrets.textproto" "$BACKUP_DIR/$TIMESTAMP/"

echo "SQLite backup completed: $TIMESTAMP"

Cloud Backup

Sync backups to cloud storage:
#!/bin/bash
aws s3 sync /backups/trailbase s3://your-bucket/trailbase/ \
  --storage-class STANDARD_IA \
  --exclude "*" \
  --include "*.tar.gz"

Restore from Backup

To restore from a backup:
# Stop TrailBase
sudo systemctl stop trailbase

# Extract backup
tar -xzf trailbase_20240115_020000.tar.gz -C /var/lib/trailbase/

# Restore permissions
chown -R trailbase:trailbase /var/lib/trailbase/traildepot
chmod 700 /var/lib/trailbase/traildepot

# Start TrailBase
sudo systemctl start trailbase
Always test your backup and restore procedures before you need them. Perform regular restore drills to ensure backups are valid.

Monitoring

Health Check Endpoint

TrailBase provides a health check endpoint:
curl http://localhost:4000/api/healthcheck
Returns 200 OK if the server is healthy.

Uptime Monitoring

Use external monitoring services:
  • UptimeRobot: Simple HTTP monitoring
  • Pingdom: Advanced monitoring with alerting
  • Better Uptime: Modern uptime monitoring
  • StatusCake: HTTP and performance monitoring
Example configuration:
  • Monitor URL: https://yourdomain.com/api/healthcheck
  • Check interval: 60 seconds
  • Alert after: 2 consecutive failures

Log Monitoring

TrailBase stores access and error logs in the logs.db SQLite database:
-- Query recent errors
SELECT * FROM _logs 
WHERE level = 'ERROR' 
ORDER BY timestamp DESC 
LIMIT 100;

-- Query access patterns
SELECT 
  COUNT(*) as requests,
  path,
  method
FROM _logs
WHERE timestamp > datetime('now', '-1 hour')
GROUP BY path, method
ORDER BY requests DESC;
For stdout logging (e.g., in Docker):
trail run --stderr-logging

Log Retention

Configure log retention in config.textproto:
server: {
  # Keep logs for 7 days (in seconds)
  logs_retention_sec: 604800
}

Resource Monitoring

Monitor system resources:
# CPU and memory usage
top -b -n 1 | grep trail

# Database size
du -sh /var/lib/trailbase/traildepot/*.db

# Disk usage
df -h /var/lib/trailbase
For automated monitoring, use:
  • Prometheus + Grafana: Metrics and dashboards
  • Netdata: Real-time performance monitoring
  • Datadog: Full-stack monitoring

Performance Metrics

Key metrics to monitor:
  • Request latency: P50, P95, P99 response times
  • Request rate: Requests per second
  • Error rate: 4xx and 5xx responses
  • Database size: Growth rate
  • CPU usage: Average and peak
  • Memory usage: RSS and growth
  • Disk I/O: Read/write operations

Scaling

Vertical Scaling

For most workloads, vertical scaling is the simplest approach:
  • Small: 1 CPU, 512 MB RAM - Up to 100 req/s
  • Medium: 2 CPU, 2 GB RAM - Up to 500 req/s
  • Large: 4 CPU, 4 GB RAM - Up to 2000 req/s
  • X-Large: 8 CPU, 8 GB RAM - Up to 5000+ req/s
TrailBase is extremely efficient. SQLite’s sub-millisecond latencies mean most applications won’t need horizontal scaling.

Horizontal Scaling (Read Replicas)

For read-heavy workloads:
1

Primary instance

Run a single write instance:
trail --data-dir /var/lib/trailbase/primary run
2

Create read replicas

Periodically copy the database to read-only instances:
# On primary
sqlite3 /var/lib/trailbase/primary/main.db ".backup '/tmp/main.db'"

# Transfer to replica
scp /tmp/main.db replica:/var/lib/trailbase/replica/main.db
3

Route traffic

Use a load balancer to route:
  • Writes → Primary instance
  • Reads → Read replicas (round-robin)
SQLite does not have built-in replication. For true high-availability and write scaling, consider commercial solutions like Turso or LiteFS.

CDN for Static Assets

If serving static files:
trail run --public-dir /var/www/static
Place a CDN (CloudFlare, Fastly, CloudFront) in front to:
  • Cache static assets globally
  • Reduce load on TrailBase
  • Improve latency for users worldwide

Database Optimization

Optimize SQLite performance:
-- Analyze query patterns
ANALYZE;

-- Rebuild indexes
REINDEX;

-- Vacuum to reclaim space
VACUUM;
Run maintenance periodically:
#!/bin/bash
sqlite3 /var/lib/trailbase/traildepot/main.db "ANALYZE; VACUUM;"

Systemd Service

Run TrailBase as a systemd service:
/etc/systemd/system/trailbase.service
[Unit]
Description=TrailBase Server
After=network.target

[Service]
Type=simple
User=trailbase
Group=trailbase
WorkingDirectory=/var/lib/trailbase
Environment="DATA_DIR=/var/lib/trailbase/traildepot"
Environment="PUBLIC_URL=https://yourdomain.com"
ExecStart=/usr/local/bin/trail run --address 0.0.0.0:4000
Restart=always
RestartSec=10

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/trailbase

[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable trailbase
sudo systemctl start trailbase
sudo systemctl status trailbase

Troubleshooting

High CPU Usage

Check for:
  • Expensive queries (add indexes)
  • Too many realtime subscriptions
  • Insufficient runtime-threads for WASM workloads

Database Locked

SQLite allows one writer at a time:
  • Enable WAL mode (enabled by default in TrailBase)
  • Reduce write transaction duration
  • Consider read replicas for read-heavy loads

Memory Leaks

Monitor memory over time:
watch -n 60 'ps aux | grep trail'
If memory grows unbounded, file a bug report with:
  • TrailBase version
  • Memory growth rate
  • Request patterns
  • Configuration

Next Steps

Configuration

Detailed configuration options and environment variables

Self-Hosting

Learn about different deployment strategies

Build docs developers (and LLMs) love