back to top
Friday, January 2, 2026
spot_img

5 Best Self-Hosted ERP or Small Business in 2026 (with Docker Install Guide)

Share this Guide

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:

  1. Docker & Docker Compose: The runtime engine.
  2. Traefik v3: The modern reverse proxy that integrates natively with Docker.
  3. 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

  1. Run docker compose up -d.
  2. Wait. ERPNext initialization is heavy. It can take 5โ€“10 minutes.
  3. Monitor the process: docker compose logs -f configurator.
  4. Once the logs show “Site created successfully,” navigate to https://erp.yourdomain.com.
  5. Log in with username Administrator and 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

  1. 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
  2. Upload: Upload the extracted folders into ~/docker-services/odoo/addons on your server (use SCP or FileZilla).
  3. Restart: Run docker compose restart odoo.
  4. 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.

Leave a review

Reviews (0)

N๏ฟฝj๏ฟฝbrW๏ฟฝ๏ฟฝ๏ฟฝ'๏ฟฝ๏ฟฝy๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ{ 2z
Pilฤni
clear sky
18.6 ° C
18.6 °
18.6 °
43 %
3.6kmh
0 %
Fri
20 °
Sat
21 °
Sun
22 °
Mon
23 °
Tue
24 °

Related Posts

How to Get the Best FPS in CS2 on Any PC (Ultimate Settings Guide)

This comprehensive guide covers all CS2 video settings that impact performance and quality to Get the Best FPS in CS2 on Any PC

Helldivers 2 Weapons Tier List | All Guns Ranked & Best Uses

This updated Helldivers 2 Weapons Tier List August 2025 ranks every primary and secondary weapon, including Warbond weapons โ€“ from S-tier to D-tier. Discover each gunโ€™s stats, strengths, and best scenarios (which factions or missions they excel in) so you can optimize your Helldivers 2 loadout and bring Democracy to the enemies of Super Earth with the right firepower!

Comprehensive Guide to Bambu Lab 3D Printers Lineup (2025)

Bambu Lab has rapidly become a leading name in...

Bambu Lab Calibration Guide (P1, X1, A1 Mini & H2D)

Bambu Labโ€™s 3D printers are renowned for their automated...

Using Seeed Studio mmWave Module with ESPHome

In the ever-expanding universe of smart technology, the fusion...

Raspberry Pi Automatic Fans Using L298n PWM

Welcome, We all know Raspberry Pi SBC Likes to...
- Advertisement -spot_img