The Technical Guides: Infrastructure as Code
This section provides the “Cookbook” for deploying the top 3 systems (ERPNext, Odoo, Dolibarr) using Docker Compose. These guides are designed for production use, assuming a standard Linux VPS (Ubuntu 24.04 LTS) with at least 4GB of RAM and 2 vCPUs.
Prerequisites: The Connectivity Layer
Before deploying specific applications, a robust foundation is required. We do not expose ERPs directly to the internet on port 80. Instead, we use a Reverse Proxy to handle traffic routing, SSL termination (HTTPS), and security headers.
The Stack:
- Docker & Docker Compose: The runtime engine.
- Traefik v3: The modern reverse proxy that integrates natively with Docker.
- Portainer (Optional): A GUI for managing Docker containers.
Step 1: Server Preparation
Access your VPS via SSH and prepare the environment:
# 1. Update the system
sudo apt update && sudo apt upgrade -y
# 2. Install Docker (Official Script)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# 3. Add current user to Docker group (Security Best Practice)
sudo usermod -aG docker $USER
newgrp docker
# 4. Create a dedicated Docker network for the proxy
# This ensures isolation; only containers on this network are accessible via Traefik.
docker network create proxy
# 5. Create directory structure
mkdir -p ~/docker-services/traefik
mkdir -p ~/docker-services/erpnext
mkdir -p ~/docker-services/odoo
mkdir -p ~/docker-services/dolibarr
Step 2: Traefik v3 Deployment
Traefik v3 (released 2024/2025) introduces syntax changes from v2. It will automatically obtain valid SSL certificates from Let’s Encrypt for any service we deploy.
File: ~/docker-services/traefik/docker-compose.yml
services:
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- "80:80" # HTTP Entrypoint
- "443:443" # HTTPS Entrypoint
environment:
# Optional: CF_DNS_API_TOKEN=... if using DNS Challenge
- TZ=UTC
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro # Access to listen to Docker events
-./data/traefik.yml:/traefik.yml:ro
-./data/acme.json:/acme.json # Stores SSL certificates
labels:
- "traefik.enable=true"
# Dashboard Router
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`monitor.yourdomain.com`)"
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:YOUR_HASHED_PASSWORD"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`monitor.yourdomain.com`)"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik-secure.service=api@internal"
# Global Middleware: Redirect HTTP to HTTPS
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
networks:
proxy:
external: true
Configuration File: ~/docker-services/traefik/data/traefik.yml
api:
dashboard: true
insecure: false
entryPoints:
http:
address: ":80"
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false # Crucial for security: containers are hidden by default
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: acme.json
httpChallenge:
entryPoint: http
Action: Run touch ~/docker-services/traefik/data/acme.json && chmod 600 ~/docker-services/traefik/data/acme.json then docker compose up -d to start the proxy.
Guide: ERPNext (Production Setup)
ERPNext requires a sophisticated stack: Redis (for caching, queuing, and socket.io), MariaDB (database), and the Python backend. While the official frappe_docker repository suggests complex overrides, we present a consolidated “Single File” production setup for simplicity and stability.
Folder: ~/docker-services/erpnext
Step 1: The.env Configuration
Create a .env file to store sensitive variables. This keeps them out of the main compose file.
# ERPNext Version (Check Docker Hub for latest)
ERPNEXT_VERSION=v15.20.0
# Database Credentials
DB_PASSWORD=SecureDbPass123!
DB_ROOT_PASSWORD=SecureRootPass123!
# Administrator Password for the ERP
ADMIN_PASSWORD=AdminLoginPass123!
# The domain name
SITE_NAME=erp.yourdomain.com
Step 2: The Docker Compose File
This configuration defines the entire stack. Note the use of the configurator container, which automates the first-time site creation process.
name: erpnext-production
services:
# 1. Backend Service (Gunicorn/Python)
backend:
image: frappe/erpnext:${ERPNEXT_VERSION}
deploy:
restart_policy:
condition: on-failure
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
environment:
- DB_HOST=db
- DB_PORT=3306
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_CACHE=redis-cache:6379
- REDIS_QUEUE=redis-queue:6379
- REDIS_SOCKETIO=redis-socketio:6379
depends_on:
- db
- redis-cache
- redis-queue
# 2. Frontend Service (Nginx) - Exposed via Traefik
frontend:
image: frappe/erpnext:${ERPNEXT_VERSION}
deploy:
restart_policy:
condition: on-failure
command:
- nginx-entrypoint
environment:
- BACKEND=backend:8000
- FRAPPE_SITE_NAME_HEADER=${SITE_NAME}
- SOCKETIO=websocket:9000
- UPSTREAM_REAL_IP_ADDRESS=127.0.0.1
- UPSTREAM_REAL_IP_HEADER=X-Forwarded-For
- UPSTREAM_REAL_IP_RECURSIVE=on
volumes:
- sites:/home/frappe/frappe-bench/sites
networks:
- proxy
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.erpnext.rule=Host(`${SITE_NAME}`)"
- "traefik.http.routers.erpnext.entrypoints=https"
- "traefik.http.routers.erpnext.tls=true"
- "traefik.http.routers.erpnext.tls.certresolver=letsencrypt"
- "traefik.http.services.erpnext.loadbalancer.server.port=8080"
# 3. Database (MariaDB)
db:
image: mariadb:10.6 # ERPNext is highly specific about MariaDB versions
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
environment:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
- MYSQL_DATABASE=frappe
- MYSQL_USER=root
volumes:
- db-data:/var/lib/mysql
# 4. Redis Services
redis-cache:
image: redis:6.2-alpine
redis-queue:
image: redis:6.2-alpine
redis-socketio:
image: redis:6.2-alpine
# 5. WebSocket (Socket.io for real-time updates)
websocket:
image: frappe/erpnext:${ERPNEXT_VERSION}
command:
- node
- /home/frappe/frappe-bench/apps/frappe/socketio.js
volumes:
- sites:/home/frappe/frappe-bench/sites
depends_on:
- redis-socketio
# 6. Configurator (Runs ONCE to create the site)
configurator:
image: frappe/erpnext:${ERPNEXT_VERSION}
restart: "no"
command: >
bash -c "
ls -1 apps > sites/apps.txt;
bench set-config -g db_host db;
bench set-config -gp db_port 3306;
bench set-config -g redis_cache redis://redis-cache:6379;
bench set-config -g redis_queue redis://redis-queue:6379;
bench set-config -g redis_socketio redis://redis-socketio:6379;
bench set-config -gp socketio_port 9000;
if; then
echo 'Creating new site ${SITE_NAME}...'
bench new-site ${SITE_NAME} --no-mariadb-socket --admin-password '${ADMIN_PASSWORD}' --db-root-password '${DB_ROOT_PASSWORD}' --install-app erpnext --set-default;
echo 'Site created successfully';
else
echo 'Site already exists';
fi;"
environment:
- DB_HOST=db
- DB_PASSWORD=${DB_PASSWORD}
volumes:
- sites:/home/frappe/frappe-bench/sites
depends_on:
- backend
- db
volumes:
db-data:
sites:
logs:
networks:
proxy:
external: true
Step 3: Installation & Verification
- Run
docker compose up -d. - Wait. ERPNext initialization is heavy. It can take 5โ10 minutes.
- Monitor the process:
docker compose logs -f configurator. - Once the logs show “Site created successfully,” navigate to
https://erp.yourdomain.com. - Log in with username
Administratorand the password from your.env.
Guide: Odoo 18 Community (With Postgres 16)
Deploying Odoo is simpler architecturally but requires specific configuration to enable the missing accounting features via community addons.
Folder: ~/docker-services/odoo
Step 1: Configuration File
Create ~/docker-services/odoo/config/odoo.conf. This file secures the Master Password (used to create/delete databases).
[options]
admin_passwd = YourSuperSecretMasterPassword
db_host = db
db_port = 5432
db_user = odoo
db_password = odoo
addons_path = /mnt/extra-addons
Step 2: Docker Compose
services:
odoo:
image: odoo:18.0
container_name: odoo18
depends_on:
- db
volumes:
- odoo-web-data:/var/lib/odoo
-./config:/etc/odoo
-./addons:/mnt/extra-addons # Critical for community modules
environment:
- HOST=db
- USER=odoo
- PASSWORD=odoo
networks:
- proxy
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.odoo.rule=Host(`odoo.yourdomain.com`)"
- "traefik.http.routers.odoo.entrypoints=https"
- "traefik.http.routers.odoo.tls=true"
- "traefik.http.routers.odoo.tls.certresolver=letsencrypt"
- "traefik.http.services.odoo.loadbalancer.server.port=8069"
db:
image: postgres:16
container_name: odoo-db
environment:
- POSTGRES_DB=postgres
- POSTGRES_PASSWORD=odoo
- POSTGRES_USER=odoo
volumes:
- odoo-db-data:/var/lib/postgresql/data
volumes:
odoo-web-data:
odoo-db-data:
networks:
proxy:
external: true
Step 3: Installing the “Missing” Accounting
- Download: Locate the “Odoo 18 Full Accounting Kit” (e.g., from Cybrosys or Odoo Mates on the Odoo App Store). Download the ZIP file.12
- Upload: Upload the extracted folders into
~/docker-services/odoo/addonson your server (use SCP or FileZilla). - Restart: Run
docker compose restart odoo. - Activate: Log in to Odoo, enable “Developer Mode” (Settings -> Scroll down -> Activate Developer Mode), go to “Apps”, click “Update Apps List”, search for “Accounting”, and install the kit. This restores the dashboard, reports, and bank reconciliation features blocked in the standard Community edition.
Guide: Dolibarr
Dolibarr’s simplicity makes it the most robust option for low-resource environments.
Folder: ~/docker-services/dolibarr
Step 1: Docker Compose
services:
dolibarr:
image: tuxgasy/dolibarr:19 # Community-maintained image, highly stable
container_name: dolibarr
restart: unless-stopped
depends_on:
- mariadb
environment:
- DOLI_DB_HOST=mariadb
- DOLI_DB_USER=dolibarr
- DOLI_DB_PASSWORD=DoliDbPass2025!
- DOLI_DB_NAME=dolibarr
- DOLI_ADMIN_LOGIN=admin
- DOLI_ADMIN_PASSWORD=InitialAdminPass!
- DOLI_URL_ROOT=https://crm.yourdomain.com
volumes:
- dolibarr_docs:/var/www/documents # Persist uploaded files
- dolibarr_html:/var/www/html # Persist extensions
networks:
- proxy
- default
labels:
- "traefik.enable=true"
- "traefik.http.routers.dolibarr.rule=Host(`crm.yourdomain.com`)"
- "traefik.http.routers.dolibarr.entrypoints=https"
- "traefik.http.routers.dolibarr.tls=true"
- "traefik.http.routers.dolibarr.tls.certresolver=letsencrypt"
- "traefik.http.services.dolibarr.loadbalancer.server.port=80"
mariadb:
image: mariadb:10.6
container_name: dolibarr_db
restart: unless-stopped
environment:
- MYSQL_ROOT_PASSWORD=RootPass2025!
- MYSQL_DATABASE=dolibarr
- MYSQL_USER=dolibarr
- MYSQL_PASSWORD=DoliDbPass2025!
volumes:
- dolibarr_db:/var/lib/mysql
volumes:
dolibarr_docs:
dolibarr_html:
dolibarr_db:
networks:
proxy:
external: true
Maintenance & Security: Keeping the Lights On
Self-hosting shifts the responsibility of uptime from the vendor to the administrator. To mitigate risk, we employ automated systems for backup and security.
Automated Backups with S3 (The 3-2-1 Strategy)
We adhere to the 3-2-1 backup rule: 3 copies of data, 2 different media, 1 offsite.
To achieve this automatically in Docker, we use the offen/docker-volume-backup container. This “sidecar” container attaches to your database volume, compresses it, creates a GPG-encrypted archive, and uploads it to Amazon S3 (or any S3-compatible storage like Wasabi or MinIO).23
Add this service to ANY of the compose files above:
backup:
image: offen/docker-volume-backup:v2
restart: always
environment:
# Cron schedule: Runs daily at 2:00 AM
- BACKUP_CRON_EXPRESSION="0 2 * * *"
- BACKUP_FILENAME=erp-backup-%Y-%m-%dT%H-%M-%S.tar.gz
- BACKUP_RETENTION_DAYS=14 # Automatically delete old backups
# S3 Configuration
- AW_BUCKET_NAME=my-erp-backups
- AWS_ACCESS_KEY_ID=YOUR_AWS_KEY
- AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET
- AWS_ENDPOINT=https://s3.us-east-1.amazonaws.com # Or Wasabi/Backblaze endpoint
volumes:
# Mount the volume you want to backup (e.g., db-data) as Read-Only
- db-data:/backup/db-data:ro
# Access Docker socket to stop/start containers during backup (optional but safer)
- /var/run/docker.sock:/var/run/docker.sock:ro
Security Best Practices
1. Cloudflare Tunnels (Zero Trust):
While Traefik handles SSL, exposing ports 80/443 directly to the internet invites port scanners. A more secure approach is Cloudflare Tunnels. A cloudflared container creates an encrypted outbound tunnel to Cloudflare’s edge network. This allows you to close all inbound ports on your firewall. Users access the ERP via Cloudflare, which forwards the traffic through the tunnel to your Docker container.25
2. CrowdSec (Intrusion Prevention):
CrowdSec is a collaborative security automation tool. By installing the CrowdSec agent on your host (or as a container), it analyzes logs from Traefik and SSH. If an IP address attempts to brute-force your ERP login, CrowdSec detects the pattern and blocks the IP. Crucially, it shares this IP with the global community; if an IP attacks a server in France, your server in New York will preemptively block it.26
Conclusion & Verdict
The landscape of business software in 2025 has bifurcated. On one side are the SaaS giants, increasing prices and locking down data. On the other is the thriving ecosystem of self-hosted open-source ERPs, powered by the reliability and portability of Docker.
For the small business owner or IT administrator, the path forward depends on the organization’s DNA:
- The “Power User” Choice: If you require a system that can scale from 5 to 500 users without incurring licensing fees, and you are willing to navigate a steeper learning curve, ERPNext is the undisputed champion. It offers the most “freedom” and functionality per dollar.
- The “Design-First” Choice: If user experience and e-commerce integration are paramount, Odoo Community is the strongest contender, provided you are willing to manage the complexity of community modules to unlock accounting features.
- The “Set and Forget” Choice: For freelancers and micro-businesses that need a stable, low-maintenance ledger and CRM, Dolibarr remains the most reliable workhorse.
By following the guides in this report, you can deploy a sovereign, cost-effective infrastructure that transforms your ERP from a monthly liability into a permanent business asset.
Disclaimer: This guide involves manipulating server infrastructure. Always test your backups and perform dry runs in a staging environment before committing production data.




