Skip to content

ERPNext

Full open-source ERP suite — accounting, inventory, HR/payroll, CRM, manufacturing, projects, and a built-in website/e-commerce module.

  • Upstream project: https://erpnext.com/
  • Replaces: SAP Business One, Odoo, Oracle NetSuite
  • Sign-in (SSO): Enable via the app’s admin UI — paste the OIDC_* values from the Environment tab once.
  1. Click Deploy. First boot takes 5-10 min — the site must be created, MariaDB initialized, Python apps installed. Watch the create-site container logs in Dokploy if you want to follow along.
  2. Visit your ERPNext domain. Sign in as Administrator / ERPNEXT_ADMIN_PASSWORD from the Environment tab.
  3. Complete the setup wizard: company name, fiscal year, base currency, chart of accounts template.
  4. (Optional) Enable Keycloak SSO: Integrations -> Social Login Keys -> New -> Provider: OpenID Connect. Fill:
    • Client ID: OIDC_CLIENT_ID from Environment
    • Client Secret: OIDC_CLIENT_SECRET
    • Base URL: OIDC_ISSUER_URL
    • Save. The login page shows Login with OpenID Connect.

Resource note. ERPNext is heavy — 10+ containers, ~3 GB RAM + 2 CPUs recommended minimum. Consider running it on a dedicated VPS tier if you also deploy Nextcloud + chat + other apps alongside.

Upgrade path. Major version bumps (v15 -> v16) require bench migrate. The compose template leaves vps.auto-update=patch on every service so weekly automated updates stay within v15.x.x — majors are a deliberate operator action, not a 3 a.m. auto-update.

These values live in the Dokploy compose’s Environment tab. Random secrets are minted automatically when the template is first seeded — you don’t need to generate them yourself.

VariableDefault
ERPNEXT_HOSTNAMEerp.yourdomain.com
ERPNEXT_ADMIN_PASSWORDauto-generated random value
DB_ROOT_PASSWORDauto-generated random value
  • Service and port: frontend:8080
  • Hostname: erp.yourdomain.com

The hostname is attached automatically when the template is seeded; change it in the Domains tab before clicking Deploy if you want something else.

For reference — this is what the template deploys. Do not paste this anywhere. The compose is seeded into Dokploy automatically; the client-facing adjustments you make happen in the Environment and Domains tabs (described above), never in the compose itself.

# ERPNext -- full ERP suite (accounting, inventory, HR, CRM, manufacturing,
# website). Multi-service stack based on the Frappe framework: web
# frontend (nginx) + Python backend + websocket + celery workers +
# scheduler + MariaDB + two Redis instances. Plus two one-shot init
# containers that bootstrap the site on first deploy.
#
# SSO via admin UI post-deploy: sign in as Administrator ->
# Integrations -> Social Login Keys -> Add -> OpenID Connect -> paste the
# OIDC_* values from the Environment tab.
#
# Heavy: plan for ≥4 GB VPS RAM and 2 CPUs.
x-common: &frappe-env
SITE_NAME: ${ERPNEXT_HOSTNAME}
DB_HOST: db
DB_PORT: "3306"
REDIS_CACHE: redis-cache:6379
REDIS_QUEUE: redis-queue:6379
services:
# One-shot: render common_site_config.json from the env above.
configurator:
image: frappe/erpnext:v15.107.0
restart: "no"
entrypoint:
- bash
- -c
command:
# Idempotent: bail early if common_site_config.json already has db_host
# so a redeploy / host reboot doesn't rewrite the same values.
- >
ls -1 apps > sites/apps.txt;
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && echo "configurator: already done" && exit 0;
bench set-config -g db_host $$DB_HOST;
bench set-config -gp db_port $$DB_PORT;
bench set-config -g redis_cache "redis://$$REDIS_CACHE";
bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
bench set-config -gp socketio_port 9000;
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
db:
condition: service_healthy
# One-shot: create the ERPNext site on first run (idempotent: bench
# new-site no-ops when the site already exists).
create-site:
image: frappe/erpnext:v15.107.0
restart: "no"
entrypoint:
- bash
- -c
command:
- >
wait-for-it -t 120 db:3306;
wait-for-it -t 120 redis-cache:6379;
wait-for-it -t 120 redis-queue:6379;
until ls sites/common_site_config.json; do sleep 1; done;
if [ ! -d "sites/${ERPNEXT_HOSTNAME}" ]; then
bench new-site --mariadb-user-host-login-scope='%'
--admin-password=${ERPNEXT_ADMIN_PASSWORD}
--db-root-username=root
--db-root-password=${DB_ROOT_PASSWORD}
--install-app erpnext
--set-default ${ERPNEXT_HOSTNAME};
fi
environment:
<<: *frappe-env
ERPNEXT_ADMIN_PASSWORD: ${ERPNEXT_ADMIN_PASSWORD}
DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- configurator
backend:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- create-site
labels:
- "vps.auto-update=patch"
networks:
- default
frontend:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
command: ["nginx-entrypoint.sh"]
environment:
BACKEND: backend:8000
SOCKETIO: websocket:9000
FRAPPE_SITE_NAME_HEADER: ${ERPNEXT_HOSTNAME}
UPSTREAM_REAL_IP_ADDRESS: 0.0.0.0/0
UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
UPSTREAM_REAL_IP_RECURSIVE: "On"
PROXY_READ_TIMEOUT: "120"
CLIENT_MAX_BODY_SIZE: 50m
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- backend
- websocket
labels:
- "vps.auth.mode=public"
- "vps.auto-update=patch"
networks:
dokploy-network:
aliases:
- erpnext
default: {}
websocket:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
command: ["node", "/home/frappe/frappe-bench/apps/frappe/socketio.js"]
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- create-site
labels:
- "vps.auto-update=patch"
networks:
- default
queue-default:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
command: ["bench", "worker", "--queue", "default"]
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- create-site
labels:
- "vps.auto-update=patch"
networks:
- default
queue-short:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
command: ["bench", "worker", "--queue", "short,default"]
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- create-site
labels:
- "vps.auto-update=patch"
networks:
- default
queue-long:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
command: ["bench", "worker", "--queue", "long,default,short"]
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- create-site
labels:
- "vps.auto-update=patch"
networks:
- default
scheduler:
image: frappe/erpnext:v15.107.0
restart: unless-stopped
command: ["bench", "schedule"]
environment: *frappe-env
volumes:
- sites:/home/frappe/frappe-bench/sites
- logs:/home/frappe/frappe-bench/logs
depends_on:
- create-site
labels:
- "vps.auto-update=patch"
networks:
- default
db:
image: mariadb:11.8.6
restart: unless-stopped
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
- --skip-innodb-read-only-compressed
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mariadb-admin ping -h localhost -u root -p$${MARIADB_ROOT_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
labels:
- "vps.auto-update=patch"
networks:
- default
redis-cache:
image: redis:8.6.3-alpine3.23
restart: unless-stopped
labels:
- "vps.auto-update=patch"
networks:
- default
redis-queue:
image: redis:8.6.3-alpine3.23
restart: unless-stopped
volumes:
- redis-queue-data:/data
labels:
- "vps.auto-update=patch"
networks:
- default
volumes:
sites:
logs:
db-data:
redis-queue-data:
networks:
dokploy-network:
external: true

<- Back to all pre-configured apps