Docker Deployment Guide
Last Updated: 2026-01-21
Audience: Developers and system administrators deploying ARC // OS with Docker
Prerequisites
Required Software
- Docker 20.10+ (Installation Guide)
- Docker Compose 2.0+ (Installation Guide)
- Git (for cloning the repository)
- Basic command-line knowledge
Linux Requirements
For Linux deployments, ensure:
- Kernel version 5.10+ (for Docker)
- cgroup v2 support (most modern distributions)
- At least 2GB RAM (4GB+ recommended)
- At least 10GB free disk space (for images, volumes, and data)
- Network access (for pulling images and updates)
Verify Installation
# Check Docker version
docker --version
# Should show: Docker version 20.10.x or higher
# Check Docker Compose version
docker compose version
# Should show: Docker Compose version 2.x.x
# Verify Docker is running
docker ps
# Should show running containers (or empty list if none running)
Quick Start
# 1. Clone repository
git clone <repository-url>
cd arcos
# 2. Configure environment
cp server/.env.example server/.env
cp web/.env.example web/.env
# Edit .env files with your settings
# 3. Start services
docker compose up --build -d
# 4. Initialize database
docker compose exec server pnpm prisma migrate deploy
docker compose exec server pnpm prisma generate
# 5. Verify
curl http://localhost:3001/health
Docker Compose Services
Services Overview
- postgres - PostgreSQL database
- server - Backend API (Fastify)
- web - Frontend (React + Vite, served via Nginx)
Service Configuration
PostgreSQL:
- Port: 5432 (internal)
- Data persistence:
postgres_datavolume - Auto-initialization: Database created on first run
Backend:
- Port: 3001 (internal)
- Depends on: postgres
- Environment: Production mode
- Health check:
/healthendpoint
Frontend:
- Port: 3000 (internal)
- Depends on: server
- Served via: Nginx
- Static files: Built React app
Environment Configuration
Backend Environment Variables
See server/.env.example for all available variables.
Required:
DATABASE_URL- PostgreSQL connection stringJWT_SECRET- Strong random secret (min 32 chars)NODE_ENV=productionCORS_ORIGIN- Your frontend domain
Optional:
FILE_STORAGE_ADAPTER- 'disk' or 's3' (default: 'disk')FILE_UPLOAD_DIR- Upload directory path (default: './uploads' for local, '/app/uploads' for Docker)RATE_LIMIT_MAX- API rate limit- File upload settings (MAX_FILE_SIZE, ALLOWED_IMAGE_TYPES, etc.)
EMAIL_PROVIDER- Email service provider ('mock' for test deployment, saves emails toserver/emails/directory)
Frontend Environment Variables
Required:
VITE_API_URL- Backend API URL
Common Operations
View Logs
# All services
docker compose logs -f
# Specific service
docker compose logs -f server
docker compose logs -f web
docker compose logs -f postgres
Restart Services
# All services
docker compose restart
# Specific service
docker compose restart server
Stop Services
# Stop (keeps containers)
docker compose stop
# Stop and remove containers
docker compose down
# Stop and remove volumes (⚠️ deletes data)
docker compose down -v
Update Application
# Pull latest code
git pull
# Rebuild and restart
docker compose up --build -d
# Run migrations if needed
docker compose exec server pnpm prisma migrate deploy
docker compose exec server pnpm prisma generate
Database Operations
# Run migrations
docker compose exec server pnpm prisma migrate deploy
# Generate Prisma Client
docker compose exec server pnpm prisma generate
# Seed database
docker compose exec server pnpm prisma db seed
# Access database shell
docker compose exec postgres psql -U arcos arcos
# Backup database
docker compose exec postgres pg_dump -U arcos arcos > backup.sql
# Restore database
docker compose exec -T postgres psql -U arcos arcos < backup.sql
Production Considerations
Resource Limits
Add to docker-compose.yml:
services:
server:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
Volume Management
Database data:
- Volume:
postgres_data(named Docker volume) - Mount Path:
/var/lib/postgresql/data(inside container) - Location: Managed by Docker (typically
/var/lib/docker/volumes/arcos_postgres_data/_data) - Persistence: Volume persists across container restarts and updates
- Backup: Regular pg_dump backups (see Backup & Recovery section)
- Size: Grows with database content (monitor regularly)
File uploads:
- Volume:
uploads_data(named Docker volume) - Mount Path:
/app/uploads(inside container) - Location: Managed by Docker (typically
/var/lib/docker/volumes/arcos_uploads_data/_data) - Configuration: Set
FILE_UPLOAD_DIR=/app/uploadsin Docker environment - Permissions: Files created with default Node.js process permissions (typically uid 1000)
- Persistence: Volume persists across container restarts
- Backup: Regular tar backups (see Backup & Recovery section)
- Size: Grows with user uploads (monitor regularly)
Volume Backup Strategy:
-
Automated Backups:
- Database: Daily pg_dump backups via Docker Compose backup service
- Uploads: Daily tar.gz backups via Docker Compose backup service
- Backups stored in
backups_dataDocker volume - Retention: Configurable via
BACKUP_RETENTION_DAYS(default: 30 days) - Old backups automatically cleaned by backup script
-
Backup Service:
- Runs as separate Docker Compose service (
backup) - Has access to database and upload volumes
- Writes backups to
backups_datavolume - Can be run manually:
docker compose run --rm backup - Automated via cron/systemd timer on host
- Runs as separate Docker Compose service (
-
Backup Retention:
- Configurable retention period (default: 30 days)
- Automatic cleanup of old backups
- Backups stored in volume, can be copied to host for external storage
-
Backup Verification:
- Test restore procedures monthly
- Verify backup integrity
- Monitor backup volume size
- Document restore process
Network Configuration
Docker Network:
- Docker Compose creates a default bridge network
- Services communicate via service names (
db,server,web) - Internal communication only (not exposed to host by default)
Port Mapping:
- Database:
5432:5432(exposed to host for direct access if needed) - Backend API:
3001:3001(exposed to host) - Frontend:
3000:3000(exposed to host)
Firewall Setup (Linux):
For production, configure firewall to only allow necessary ports:
# Using ufw (Ubuntu/Debian)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP (for Let's Encrypt)
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
# Using firewalld (CentOS/RHEL)
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# Block direct access to application ports (use reverse proxy instead)
sudo ufw deny 3000/tcp # Frontend
sudo ufw deny 3001/tcp # Backend API
sudo ufw deny 5432/tcp # Database (unless needed for admin access)
Network Security:
- Only expose ports through reverse proxy (Nginx)
- Do not expose database port (5432) to internet
- Use Docker's internal networking for service communication
- Consider using Docker networks with custom configurations for isolation
Health Checks
services:
server:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
SSL/TLS Setup
Option 1: Let's Encrypt (Recommended)
Prerequisites:
- Domain name pointing to your server
- Port 80 and 443 open in firewall
- Nginx installed on host (not in container)
Step 1: Install Certbot
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install certbot python3-certbot-nginx
# CentOS/RHEL
sudo yum install certbot python3-certbot-nginx
Step 2: Configure Nginx (see Nginx section below)
Step 3: Obtain Certificate
# Single domain
sudo certbot --nginx -d yourdomain.com
# Multiple domains
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# API subdomain
sudo certbot --nginx -d api.yourdomain.com
Step 4: Auto-Renewal
Certbot automatically configures renewal. Test renewal:
sudo certbot renew --dry-run
Option 2: Manual Certificates
Step 1: Generate Certificate
# Using OpenSSL (self-signed, for testing only)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/arcos.key \
-out /etc/ssl/certs/arcos.crt
# For production, use a certificate from a trusted CA
Step 2: Configure Nginx
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/ssl/certs/arcos.crt;
ssl_certificate_key /etc/ssl/private/arcos.key;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# ... rest of configuration
}
Nginx Reverse Proxy Configuration
Installation
# Ubuntu/Debian
sudo apt-get install nginx
# CentOS/RHEL
sudo yum install nginx
# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
Basic Configuration
Create /etc/nginx/sites-available/arcos:
# Frontend (HTTP - redirect to HTTPS)
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
# Frontend (HTTPS)
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL certificates (Let's Encrypt)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Frontend
location / {
proxy_pass http://localhost:3000;
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;
}
# Static assets caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://localhost:3000;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# API (HTTPS)
server {
listen 443 ssl http2;
server_name api.yourdomain.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
# SSL configuration (same as above)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# API endpoints
location / {
proxy_pass http://localhost:3001;
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;
# Rate limiting (optional, if not handled by application)
limit_req zone=api_limit burst=20 nodelay;
}
# Health check (no rate limiting)
location /health {
proxy_pass http://localhost:3001/health;
access_log off;
}
}
# Rate limiting configuration (add to /etc/nginx/nginx.conf http block)
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# ... other configuration
}
Enable Configuration
# Create symlink
sudo ln -s /etc/nginx/sites-available/arcos /etc/nginx/sites-enabled/
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Nginx Optimization
Add to /etc/nginx/nginx.conf:
http {
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml+rss;
# Connection limits
worker_connections 1024;
worker_processes auto;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
}
Logging and Monitoring Setup
Docker Logging
View Logs:
# All services
docker compose logs -f
# Specific service with timestamps
docker compose logs -f --timestamps server
# Last 100 lines
docker compose logs --tail=100 server
# Since specific time
docker compose logs --since 30m server
Log Rotation:
Configure Docker log rotation in /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Restart Docker:
sudo systemctl restart docker
Application Logging
Backend Logs:
- Logs are output to stdout/stderr
- Captured by Docker logging driver
- Use structured logging (JSON format)
- Log levels: error, warn, info, debug
Access Logs:
# View backend logs
docker compose logs -f server | grep -i error
# View database logs
docker compose logs -f db | grep -i error
Monitoring
Basic Monitoring:
# Container resource usage
docker stats
# Disk usage
docker system df
# Volume usage
docker volume ls
docker volume inspect <volume_name>
Health Monitoring:
# Create monitoring script
cat > /usr/local/bin/arcos-health-check.sh << 'EOF'
#!/bin/bash
HEALTH=$(curl -s http://localhost:3001/health)
if [ "$HEALTH" != '{"status":"ok"}' ]; then
echo "Health check failed: $HEALTH"
exit 1
fi
EOF
chmod +x /usr/local/bin/arcos-health-check.sh
# Add to cron for regular checks
# */5 * * * * /usr/local/bin/arcos-health-check.sh
Recommended Monitoring Tools:
- Prometheus + Grafana - Metrics and dashboards
- ELK Stack - Log aggregation and analysis
- Uptime Robot - External uptime monitoring
- Sentry - Error tracking
Troubleshooting
Services Won't Start
Symptoms: Containers fail to start or exit immediately
Diagnosis:
# Check logs
docker compose logs
# Check service status
docker compose ps -a
# Check specific service logs
docker compose logs server
docker compose logs db
# Verify environment variables
docker compose config
# Check Docker daemon
sudo systemctl status docker
Common Causes:
- Missing environment variables
- Database connection string incorrect
- Port conflicts
- Insufficient disk space
- Docker daemon not running
Solutions:
- Verify
.envfiles are present and correct - Check
docker-compose.ymlsyntax:docker compose config - Ensure Docker daemon is running:
sudo systemctl start docker - Check disk space:
df -h
Database Connection Issues
Symptoms: Backend can't connect to database, migration errors
Diagnosis:
# Check database is running
docker compose ps db
# Check database health
docker compose exec db pg_isready -U arcos
# Test connection from server container
docker compose exec server pnpm prisma db push --skip-generate
# Check database logs
docker compose logs db | grep -i error
# Verify connection string
docker compose exec server printenv DATABASE_URL
Common Causes:
- Database not ready (wait for health check)
- Incorrect DATABASE_URL
- Database credentials wrong
- Network issues between containers
Solutions:
- Wait for database health check:
docker compose ps db(should show "healthy") - Verify DATABASE_URL matches docker-compose.yml
- Check database credentials in environment variables
- Restart database:
docker compose restart db
Port Conflicts
Symptoms: "Port already in use" errors
Diagnosis:
# Check what's using ports
sudo lsof -i :3000
sudo lsof -i :3001
sudo lsof -i :5432
# Or using netstat
sudo netstat -tulpn | grep :3001
Solutions:
# Option 1: Stop conflicting service
sudo systemctl stop <service-name>
# Option 2: Change ports in docker-compose.yml
ports:
- "3002:3001" # Use different host port
# Option 3: Kill process using port
sudo kill -9 <PID>
Disk Space Issues
Symptoms: "No space left on device" errors, containers can't start
Diagnosis:
# Check disk usage
df -h
# Check Docker disk usage
docker system df
# Check volume sizes
docker volume ls
docker volume inspect <volume_name>
Solutions:
# Clean up unused Docker resources
docker system prune -a
# Remove old images
docker image prune -a
# Remove stopped containers
docker container prune
# Remove unused volumes (⚠️ be careful!)
docker volume prune
# Clean up build cache
docker builder prune
# Check and clean large log files
sudo journalctl --vacuum-time=7d
Permission Issues
Symptoms: "Permission denied" errors when accessing volumes
Diagnosis:
# Check volume permissions
docker compose exec server ls -la /app/uploads
# Check container user
docker compose exec server whoami
# Check host directory permissions (if using bind mount)
ls -la ./uploads
Solutions:
# Fix volume permissions
sudo chown -R 1000:1000 /var/lib/docker/volumes/arcos_uploads_data/_data
# Or fix bind mount permissions
sudo chown -R 1000:1000 ./uploads
# Restart container
docker compose restart server
Container Crashes
Symptoms: Container starts then immediately exits
Diagnosis:
# Check exit code
docker compose ps -a
# Check logs for errors
docker compose logs server --tail=100
# Check container status
docker inspect arcos_server | grep -A 10 State
Common Causes:
- Application startup error
- Missing environment variables
- Database connection failure
- Port binding issues
Solutions:
- Review application logs:
docker compose logs server - Verify all required environment variables are set
- Check database is healthy:
docker compose ps db - Try starting container interactively:
docker compose run server sh
Health Check Failures
Symptoms: Health checks failing, containers marked as unhealthy
Diagnosis:
# Check health check status
docker inspect arcos_server | grep -A 5 Health
# Test health endpoint manually
docker compose exec server curl -f http://localhost:3001/health
# Check if health endpoint exists
docker compose exec server curl http://localhost:3001/health
Solutions:
- Verify health endpoint is accessible:
/health - Check application is running:
docker compose logs server - Increase health check timeout if needed
- Verify network connectivity between containers
Migration Errors
Symptoms: Database migration failures
Diagnosis:
# Check migration status
docker compose exec server pnpm prisma migrate status
# Check migration history
docker compose exec db psql -U arcos -d arcos -c "SELECT * FROM _prisma_migrations;"
# View migration errors
docker compose logs server | grep -i migration
Solutions:
# Reset database (⚠️ deletes all data)
docker compose exec server pnpm prisma migrate reset
# Force apply migrations
docker compose exec server pnpm prisma migrate deploy
# Resolve migration conflicts manually
docker compose exec db psql -U arcos -d arcos
Performance Issues
Symptoms: Slow response times, high resource usage
Diagnosis:
# Check resource usage
docker stats
# Check database performance
docker compose exec db psql -U arcos -d arcos -c "SELECT * FROM pg_stat_activity;"
# Check application logs for slow queries
docker compose logs server | grep -i slow
Solutions:
- Increase resource limits in docker-compose.yml
- Optimize database queries
- Add database indexes
- Scale horizontally (multiple backend instances)
- Use connection pooling
- Monitor and optimize slow queries
Network Issues
Symptoms: Containers can't communicate, connection timeouts
Diagnosis:
# Check Docker network
docker network ls
docker network inspect arcos_default
# Test connectivity between containers
docker compose exec server ping db
# Check DNS resolution
docker compose exec server nslookup db
Solutions:
- Verify services are on same Docker network
- Check service names match docker-compose.yml
- Restart Docker network:
docker compose down && docker compose up -d - Check firewall rules aren't blocking Docker network
Backup & Recovery
Backup Strategy
What to Backup:
- Database - All application data (users, tasks, training, etc.)
- File uploads - User-uploaded files and images
- Configuration - Environment variables, docker-compose.yml (store securely)
Backup Frequency:
- Database: Daily (automated)
- File uploads: Daily (automated)
- Configuration: On change (manual)
Docker Compose Backup Service
Backups are handled at infrastructure level via Docker Compose backup service (not from the API). The backup service runs as a separate container with access to database and upload volumes.
Backup Service Configuration:
- Service:
backupindocker-compose.yml - Script:
docker/backup.sh - Dockerfile:
docker/backup.Dockerfile - Volume:
backups_data(mounted at/backups) - Access:
- Read-only access to
uploads_datavolume (mounted at/uploads:ro) - Network access to database service (
db) - Writes backups to
backups_datavolume
- Read-only access to
Backup Service Features:
- Database backup: pg_dump with gzip compression
- File uploads backup: tar.gz archive
- Automatic cleanup: Removes backups older than retention period (default: 30 days)
- Verification: Checks backup integrity before completion
- Error handling: Exits with error code if backup fails
Manual Backup:
# Run backup manually
docker compose run --rm backup
# Backup will be stored in backups_data volume
# List backups in volume
docker run --rm -v arcos_backups_data:/backups alpine ls -lh /backups
Automated Backups
Create /usr/local/bin/arcos-backup.sh:
#!/bin/bash
set -e
# Configuration
PROJECT_DIR="/path/to/arcos" # Adjust to your project directory
RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-30}
cd "$PROJECT_DIR"
# Run backup using Docker Compose backup service
echo "Starting backup via Docker Compose..."
docker compose run --rm backup
# Optional: Copy backups from volume to host for external storage
# BACKUP_HOST_DIR="/backups/arcos"
# mkdir -p "$BACKUP_HOST_DIR"
# docker run --rm \
# -v arcos_backups_data:/backups \
# -v "$BACKUP_HOST_DIR":/host-backups \
# alpine sh -c "cp -r /backups/* /host-backups/ 2>/dev/null || true"
echo "✅ Backup completed successfully"
Make executable:
chmod +x /usr/local/bin/arcos-backup.sh
Setup cron:
# Edit crontab
sudo crontab -e
# Add daily backup at 2 AM
0 2 * * * /usr/local/bin/arcos-backup.sh >> /var/log/arcos-backup.log 2>&1
Alternative: Systemd Timer (more robust):
Create /etc/systemd/system/arcos-backup.service:
[Unit]
Description=ARC // OS Backup
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/arcos-backup.sh
Create /etc/systemd/system/arcos-backup.timer:
[Unit]
Description=ARC // OS Daily Backup Timer
[Timer]
OnCalendar=daily
OnCalendar=02:00
Persistent=true
[Install]
WantedBy=timers.target
Enable:
sudo systemctl enable arcos-backup.timer
sudo systemctl start arcos-backup.timer
Manual Backups
# Database backup
docker compose exec -T db pg_dump -U arcos arcos | gzip > backup_$(date +%Y%m%d).sql.gz
# File uploads backup
docker run --rm \
-v arcos_uploads_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/uploads_$(date +%Y%m%d).tar.gz -C /data .
# Verify backup
gunzip -t backup_*.sql.gz
tar -tzf uploads_*.tar.gz > /dev/null && echo "Backup valid"
Recovery Procedures
Restore from Backup Volume:
Step 1: Locate Backup in Volume
# List backups in volume
docker run --rm -v arcos_backups_data:/backups alpine ls -lh /backups
# Copy backup from volume to host (if needed)
docker run --rm \
-v arcos_backups_data:/backups \
-v $(pwd):/host-backups \
alpine sh -c "cp /backups/db_20260121_120000.sql.gz /host-backups/"
Restore Database:
# Stop application (optional, recommended)
docker compose stop server
# Option 1: Restore from volume directly
docker run --rm \
-v arcos_backups_data:/backups \
-v arcos_postgres_data:/var/lib/postgresql/data \
--network arcos_default \
-e PGPASSWORD=arcos_dev_password \
postgres:15-alpine \
sh -c "gunzip -c /backups/db_20260121_120000.sql.gz | psql -h db -U arcos -d arcos"
# Option 2: Restore from host file
gunzip < backup_20260121.sql.gz | docker compose exec -T db psql -U arcos arcos
# Verify restore
docker compose exec db psql -U arcos -c "SELECT COUNT(*) FROM users;"
# Restart application
docker compose start server
Restore File Uploads:
# Stop application (optional, recommended)
docker compose stop server
# Option 1: Restore from volume directly
docker run --rm \
-v arcos_backups_data:/backups \
-v arcos_uploads_data:/data \
alpine sh -c "cd /data && rm -rf * && tar xzf /backups/uploads_20260121_120000.tar.gz"
# Option 2: Restore from host file
docker run --rm \
-v arcos_uploads_data:/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && rm -rf * && tar xzf /backup/uploads_20260121.tar.gz"
# Verify restore
docker run --rm -v arcos_uploads_data:/data alpine ls -la /data
# Restart application
docker compose start server
Full System Recovery:
# 1. Restore database (from volume or host)
gunzip < backup_20260121.sql.gz | docker compose exec -T db psql -U arcos arcos
# 2. Restore uploads (from volume or host)
docker run --rm \
-v arcos_uploads_data:/data \
-v $(pwd):/backup \
alpine sh -c "cd /data && rm -rf * && tar xzf /backup/uploads_20260121.tar.gz"
# 3. Restore emails (if applicable - from host only)
tar xzf emails_20260121.tar.gz -C server/
# 4. Restart all services
docker compose restart
Backup Verification
Test Backup Integrity:
# List backups in volume
docker run --rm -v arcos_backups_data:/backups alpine ls -lh /backups
# Test database backup (from volume)
docker run --rm \
-v arcos_backups_data:/backups \
alpine sh -c "gunzip -t /backups/db_20260121_120000.sql.gz && echo 'Database backup valid'"
# Test uploads backup (from volume)
docker run --rm \
-v arcos_backups_data:/backups \
alpine sh -c "tar -tzf /backups/uploads_20260121_120000.tar.gz > /dev/null && echo 'Uploads backup valid'"
# Test from host file (if copied from volume)
gunzip -t backup_20260121.sql.gz && echo "Database backup valid"
tar -tzf uploads_20260121.tar.gz > /dev/null && echo "Uploads backup valid"
# Test restore on staging environment (recommended monthly)
Monitor Backup Success:
# Check backup log (if using cron/systemd)
tail -f /var/log/arcos-backup.log
# List recent backups in volume
docker run --rm -v arcos_backups_data:/backups alpine ls -lht /backups | head -20
# Check backup sizes (alert if unusually small)
docker run --rm -v arcos_backups_data:/backups alpine du -sh /backups/*
# Check volume size
docker volume inspect arcos_backups_data
Scaling
Horizontal Scaling
# Scale backend instances
docker compose up -d --scale server=3
# Use load balancer (Nginx) to distribute traffic
Database Scaling
- Use read replicas for read-heavy workloads
- Configure connection pooling
- Monitor query performance
Linux-Specific Considerations
File Permissions for Volumes
Issue: Docker volumes may have incorrect permissions on Linux hosts, especially when containers run as non-root users.
Solution:
# Check current permissions
ls -la /var/lib/docker/volumes/
# Fix permissions for uploads volume
sudo chown -R 1000:1000 /var/lib/docker/volumes/arcos_uploads_data/_data
# Or use bind mounts with proper permissions
# In docker-compose.yml:
volumes:
- ./uploads:/app/uploads:Z # :Z flag for SELinux (if enabled)
For Named Volumes:
# Create volume with specific permissions
docker volume create --driver local \
--opt type=none \
--opt device=/path/to/uploads \
--opt o=bind,uid=1000,gid=1000 \
uploads_data
Best Practice:
- Use named volumes for production (managed by Docker)
- Set proper permissions during volume creation
- Document the user/group IDs used in containers
Systemd Service Setup (Optional)
For production deployments, you may want Docker Compose to start automatically:
Create /etc/systemd/system/arcos.service:
[Unit]
Description=ARC // OS Docker Compose
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/path/to/arcos
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
Enable and Start:
# Reload systemd
sudo systemctl daemon-reload
# Enable service (start on boot)
sudo systemctl enable arcos.service
# Start service
sudo systemctl start arcos.service
# Check status
sudo systemctl status arcos.service
# View logs
sudo journalctl -u arcos.service -f
Firewall Rules (ufw/iptables)
Using ufw (Ubuntu/Debian):
# Allow SSH (important - do this first!)
sudo ufw allow 22/tcp
# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Block direct access to application ports
# (Access should be through reverse proxy only)
sudo ufw deny 3000/tcp # Frontend
sudo ufw deny 3001/tcp # Backend API
sudo ufw deny 5432/tcp # Database
# Enable firewall
sudo ufw enable
# Check status
sudo ufw status verbose
Using firewalld (CentOS/RHEL):
# Allow SSH
sudo firewall-cmd --permanent --add-service=ssh
# Allow HTTP/HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Block application ports
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --permanent --remove-port=3000/tcp # Block it
sudo firewall-cmd --permanent --add-port=3001/tcp
sudo firewall-cmd --permanent --remove-port=3001/tcp # Block it
# Reload firewall
sudo firewall-cmd --reload
# Check status
sudo firewall-cmd --list-all
Docker and Firewall:
Docker manages its own iptables rules. To allow Docker containers to communicate while maintaining firewall security:
# Allow Docker network (if needed)
sudo ufw allow from 172.17.0.0/16 # Docker default network
# Or configure Docker to use iptables=false (advanced)
# Edit /etc/docker/daemon.json:
{
"iptables": false
}
User Management (Non-Root User for Containers)
Security Best Practice: Run containers as non-root users.
Check Dockerfile:
# In server.Dockerfile
# Create non-root user
RUN addgroup -g 1000 node && \
adduser -u 1000 -G node -s /bin/sh -D node
# Switch to non-root user
USER node
# Set working directory with proper ownership
WORKDIR /app
RUN chown -R node:node /app
Verify Container User:
# Check what user the container runs as
docker compose exec server whoami
# Should show: node (or similar, not root)
# Check file permissions
docker compose exec server ls -la /app/uploads
Fix Permission Issues:
If containers need to write to volumes:
# Option 1: Fix volume permissions
sudo chown -R 1000:1000 /var/lib/docker/volumes/arcos_uploads_data/_data
# Option 2: Use bind mount with proper ownership
# In docker-compose.yml, ensure host directory has correct permissions
sudo chown -R 1000:1000 ./uploads
Resource Limits and Monitoring
Set Resource Limits in docker-compose.yml:
services:
server:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
db:
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
Monitor Resource Usage:
# Real-time resource usage
docker stats
# Specific containers
docker stats arcos_server arcos_db arcos_web
# Disk usage
docker system df
# Volume usage
docker volume inspect arcos_postgres_data
docker volume inspect arcos_uploads_data
Set Up Alerts:
# Create monitoring script
cat > /usr/local/bin/arcos-monitor.sh << 'EOF'
#!/bin/bash
# Check memory usage
MEMORY=$(docker stats --no-stream --format "{{.MemUsage}}" arcos_server | awk '{print $1}')
# Check disk usage
DISK=$(df -h /var/lib/docker | awk 'NR==2 {print $5}' | sed 's/%//')
# Alert if memory > 80% or disk > 90%
# (Add your alerting logic here)
EOF
chmod +x /usr/local/bin/arcos-monitor.sh
# Add to cron
# */5 * * * * /usr/local/bin/arcos-monitor.sh
System Resource Monitoring:
# Install monitoring tools
sudo apt-get install htop iotop
# Monitor system resources
htop # CPU and memory
iotop # Disk I/O
df -h # Disk space
free -h # Memory usage
SELinux Considerations (if enabled)
If SELinux is enabled on your system:
# Check SELinux status
getenforce
# If enforcing, add SELinux context to volumes
chcon -Rt svirt_sandbox_file_t /path/to/uploads
# Or use :Z flag in docker-compose.yml
volumes:
- ./uploads:/app/uploads:Z
For manual deployment, see DEPLOYMENT.md.