Skip to main content

Docker Deployment Guide

Last Updated: 2026-01-21
Audience: Developers and system administrators deploying ARC // OS with Docker


Prerequisites​

Required Software​

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_data volume
  • Auto-initialization: Database created on first run

Backend:

  • Port: 3001 (internal)
  • Depends on: postgres
  • Environment: Production mode
  • Health check: /health endpoint

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 string
  • JWT_SECRET - Strong random secret (min 32 chars)
  • NODE_ENV=production
  • CORS_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 to server/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/uploads in 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:

  1. 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_data Docker volume
    • Retention: Configurable via BACKUP_RETENTION_DAYS (default: 30 days)
    • Old backups automatically cleaned by backup script
  2. Backup Service:

    • Runs as separate Docker Compose service (backup)
    • Has access to database and upload volumes
    • Writes backups to backups_data volume
    • Can be run manually: docker compose run --rm backup
    • Automated via cron/systemd timer on host
  3. 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
  4. 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​

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 .env files are present and correct
  • Check docker-compose.yml syntax: 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:

  1. Database - All application data (users, tasks, training, etc.)
  2. File uploads - User-uploaded files and images
  3. 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: backup in docker-compose.yml
  • Script: docker/backup.sh
  • Dockerfile: docker/backup.Dockerfile
  • Volume: backups_data (mounted at /backups)
  • Access:
    • Read-only access to uploads_data volume (mounted at /uploads:ro)
    • Network access to database service (db)
    • Writes backups to backups_data volume

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.