Docker Deployment
Deploy FireBackup Enterprise using Docker and Docker Compose. This guide covers single-server deployments suitable for small to medium teams.
Prerequisites
- Docker 24.0+
- Docker Compose 2.20+
- 8 GB RAM minimum
- 100 GB storage minimum
- Valid FireBackup license key
Quick Start
1. Clone Deployment Repository
git clone https://github.com/firebackup/enterprise-deployment.git
cd enterprise-deployment
2. Configure Environment
cp .env.example .env
nano .env
3. Start Services
docker compose up -d
4. Activate License
docker exec firebackup-api firebackup license activate --key YOUR_LICENSE_KEY
5. Access Dashboard
Open https://your-domain.com in your browser.
Docker Compose Configuration
Production docker-compose.yml
version: '3.8'
services:
# API Server
api:
image: firebackup/api:latest
container_name: firebackup-api
restart: unless-stopped
ports:
- "4000:4000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/firebackup
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
- GOOGLE_CALLBACK_URL=${BASE_URL}/api/v1/auth/google/callback
- BASE_URL=${BASE_URL}
- LICENSE_KEY=${LICENSE_KEY}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- api-data:/app/data
- api-logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Web Dashboard
web:
image: firebackup/web:latest
container_name: firebackup-web
restart: unless-stopped
ports:
- "3000:80"
environment:
- VITE_API_URL=${BASE_URL}/api/v1
depends_on:
- api
# Backup Worker
backup-worker:
image: firebackup/api:latest
container_name: firebackup-worker
restart: unless-stopped
command: ["node", "dist/worker.js"]
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/firebackup
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- LICENSE_KEY=${LICENSE_KEY}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- worker-data:/app/data
- worker-logs:/app/logs
# PITR Worker
pitr-worker:
image: firebackup/api:latest
container_name: firebackup-pitr
restart: unless-stopped
command: ["node", "dist/pitr-worker.js"]
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/firebackup
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
- LICENSE_KEY=${LICENSE_KEY}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- pitr-data:/app/data
- pitr-logs:/app/logs
# PostgreSQL Database
postgres:
image: postgres:16-alpine
container_name: firebackup-postgres
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=firebackup
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis Cache
redis:
image: redis:7-alpine
container_name: firebackup-redis
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy noeviction
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Nginx Reverse Proxy
nginx:
image: nginx:alpine
container_name: firebackup-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx-logs:/var/log/nginx
depends_on:
- api
- web
volumes:
postgres-data:
redis-data:
api-data:
api-logs:
worker-data:
worker-logs:
pitr-data:
pitr-logs:
nginx-logs:
networks:
default:
name: firebackup-network
Environment File (.env)
# ===========================================
# FireBackup Enterprise Configuration
# ===========================================
# General
NODE_ENV=production
BASE_URL=https://your-domain.com
# Database
DB_PASSWORD=your-secure-database-password
# Security (generate with: openssl rand -hex 32)
JWT_SECRET=your-jwt-secret-32-bytes-minimum
ENCRYPTION_KEY=your-encryption-key-32-bytes
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
# License
LICENSE_KEY=fb_lic_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Optional: Email (for notifications)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_PASS=your-smtp-password
FROM_EMAIL=FireBackup <noreply@example.com>
# Optional: Monitoring
SENTRY_DSN=https://xxx@sentry.io/xxx
Nginx Configuration
nginx/nginx.conf
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
# Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript
application/xml application/xml+rss text/javascript;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
# Upstream servers
upstream api {
server api:4000;
keepalive 32;
}
upstream web {
server web:80;
keepalive 32;
}
# HTTP redirect to HTTPS
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL certificates
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# API routes
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
proxy_connect_timeout 75s;
# Large file uploads for backups
client_max_body_size 500M;
}
# Auth routes (stricter rate limiting)
location /api/v1/auth/ {
limit_req zone=auth burst=5 nodelay;
proxy_pass http://api;
proxy_http_version 1.1;
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;
}
# Socket.io
location /socket.io/ {
proxy_pass http://api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_read_timeout 86400;
}
# Web dashboard
location / {
proxy_pass http://web;
proxy_http_version 1.1;
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;
# SPA fallback
try_files $uri $uri/ @web;
}
location @web {
proxy_pass http://web;
}
# Health check
location /health {
access_log off;
proxy_pass http://api/health;
}
}
}
SSL Certificates
Let's Encrypt with Certbot
# Install certbot
apt-get update && apt-get install -y certbot
# Stop nginx temporarily
docker compose stop nginx
# Generate certificate
certbot certonly --standalone -d your-domain.com
# Copy certificates
mkdir -p nginx/ssl
cp /etc/letsencrypt/live/your-domain.com/fullchain.pem nginx/ssl/
cp /etc/letsencrypt/live/your-domain.com/privkey.pem nginx/ssl/
# Restart nginx
docker compose start nginx
Auto-Renewal
Add to crontab:
0 0 1 * * certbot renew --pre-hook "docker compose stop nginx" --post-hook "docker compose start nginx && cp /etc/letsencrypt/live/your-domain.com/*.pem /path/to/nginx/ssl/"
Service Management
Start All Services
docker compose up -d
Stop All Services
docker compose down
View Logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f api
docker compose logs -f backup-worker
docker compose logs -f pitr-worker
Restart a Service
docker compose restart api
docker compose restart backup-worker
Check Service Status
docker compose ps
Expected output:
NAME STATUS PORTS
firebackup-api Up (healthy) 0.0.0.0:4000->4000/tcp
firebackup-nginx Up 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
firebackup-pitr Up
firebackup-postgres Up (healthy) 5432/tcp
firebackup-redis Up (healthy) 6379/tcp
firebackup-web Up 0.0.0.0:3000->80/tcp
firebackup-worker Up
Database Management
Run Migrations
docker exec firebackup-api npx prisma migrate deploy
Create Database Backup
docker exec firebackup-postgres pg_dump -U postgres firebackup > backup.sql
Restore Database
docker exec -i firebackup-postgres psql -U postgres firebackup < backup.sql
Access PostgreSQL Shell
docker exec -it firebackup-postgres psql -U postgres firebackup
Scaling
Scale Workers
For increased backup throughput:
# docker-compose.override.yml
services:
backup-worker:
deploy:
replicas: 3
Or using CLI:
docker compose up -d --scale backup-worker=3
Multiple API Servers
For load balancing:
# docker-compose.override.yml
services:
api:
deploy:
replicas: 2
Update Nginx upstream:
upstream api {
server api:4000;
# Additional instances handled by Docker networking
keepalive 64;
}
External Database
Using External PostgreSQL
services:
api:
environment:
- DATABASE_URL=postgresql://user:password@external-postgres.example.com:5432/firebackup?sslmode=require
Remove the postgres service and its dependency.
Using External Redis
services:
api:
environment:
- REDIS_URL=redis://:password@external-redis.example.com:6379
Remove the redis service and its dependency.
Monitoring
Health Endpoints
| Endpoint | Description |
|---|---|
/health | Overall health status |
/health/db | Database connectivity |
/health/redis | Redis connectivity |
Prometheus Metrics
Enable metrics endpoint:
services:
api:
environment:
- METRICS_ENABLED=true
- METRICS_PORT=9090
ports:
- "9090:9090"
Docker Stats
docker stats firebackup-api firebackup-worker firebackup-postgres firebackup-redis
Backup & Recovery
Full System Backup
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/backups/firebackup-$DATE
mkdir -p $BACKUP_DIR
# Stop workers (prevent data inconsistency)
docker compose stop backup-worker pitr-worker
# Database backup
docker exec firebackup-postgres pg_dump -U postgres firebackup > $BACKUP_DIR/database.sql
# Redis backup
docker exec firebackup-redis redis-cli BGSAVE
sleep 5
docker cp firebackup-redis:/data/dump.rdb $BACKUP_DIR/redis.rdb
# Volume backups
docker run --rm -v firebackup_api-data:/data -v $BACKUP_DIR:/backup alpine tar czf /backup/api-data.tar.gz /data
docker run --rm -v firebackup_worker-data:/data -v $BACKUP_DIR:/backup alpine tar czf /backup/worker-data.tar.gz /data
# Config backup
cp .env $BACKUP_DIR/
cp -r nginx $BACKUP_DIR/
# Restart workers
docker compose start backup-worker pitr-worker
echo "Backup completed: $BACKUP_DIR"
Restore from Backup
#!/bin/bash
# restore.sh
BACKUP_DIR=$1
if [ -z "$BACKUP_DIR" ]; then
echo "Usage: ./restore.sh /path/to/backup"
exit 1
fi
# Stop all services
docker compose down
# Restore database
docker compose up -d postgres
sleep 10
docker exec -i firebackup-postgres psql -U postgres firebackup < $BACKUP_DIR/database.sql
# Restore Redis
docker compose up -d redis
docker cp $BACKUP_DIR/redis.rdb firebackup-redis:/data/dump.rdb
docker compose restart redis
# Restore volumes
docker run --rm -v firebackup_api-data:/data -v $BACKUP_DIR:/backup alpine tar xzf /backup/api-data.tar.gz -C /
docker run --rm -v firebackup_worker-data:/data -v $BACKUP_DIR:/backup alpine tar xzf /backup/worker-data.tar.gz -C /
# Restore config
cp $BACKUP_DIR/.env .
# Start all services
docker compose up -d
echo "Restore completed"
Troubleshooting
Container Won't Start
# Check logs
docker compose logs api
# Common issues:
# - Database not ready: Wait for postgres to be healthy
# - Missing environment variables: Check .env file
# - Port conflict: Check if ports are in use
Database Connection Failed
# Check PostgreSQL status
docker compose logs postgres
# Test connection
docker exec firebackup-api npx prisma db push --preview-feature
# Verify credentials
docker exec firebackup-postgres psql -U postgres -c "SELECT 1"
Redis Connection Failed
# Check Redis status
docker compose logs redis
# Test connection
docker exec firebackup-redis redis-cli ping
Worker Not Processing Jobs
# Check worker logs
docker compose logs backup-worker
# Check Redis queue
docker exec firebackup-redis redis-cli LLEN bull:backups:waiting
# Restart worker
docker compose restart backup-worker
SSL Certificate Issues
# Verify certificate
openssl x509 -in nginx/ssl/fullchain.pem -text -noout
# Check Nginx config
docker exec firebackup-nginx nginx -t
# View Nginx logs
docker compose logs nginx
Updating
Update to Latest Version
# Pull latest images
docker compose pull
# Restart with new images
docker compose up -d
# Run migrations
docker exec firebackup-api npx prisma migrate deploy
# Verify
docker compose ps
Rollback
# Stop current version
docker compose down
# Use specific version
sed -i 's/:latest/:v1.2.3/g' docker-compose.yml
# Start previous version
docker compose up -d
Security Checklist
- Strong database password (32+ characters)
- Unique JWT secret (32+ bytes)
- Unique encryption key (32 bytes)
- SSL certificates configured
- Firewall rules in place
- Regular security updates
- Log rotation configured
- Backup strategy implemented
Related
- System Requirements - Hardware requirements
- Kubernetes Deployment - K8s deployment
- Environment Variables - Configuration reference
- Security Hardening - Security best practices
Next: Kubernetes Deployment - Deploy on Kubernetes with Helm.