Skip to main content

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

EndpointDescription
/healthOverall health status
/health/dbDatabase connectivity
/health/redisRedis 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

Next: Kubernetes Deployment - Deploy on Kubernetes with Helm.