[Tutorial][Docker] Installing ERPNext-15 on Docker: A Step-by-Step Guide [Production]

Summary

ERPNext is an open-source ERP system that helps businesses manage their operations efficiently. This guide will walk you through the process of installing ERPNext version 15 on your server using Docker.
NewProject

Prerequisites:

Before you begin, ensure you have the following prerequisites in place:

  1. Operating System: Linux (Ubuntu 20+ recommended)
  2. Python: Version 3.10.12+
  3. Node.js: Version 18.x+
  4. MariaDB: Version 10.6+
  5. Redis: Latest version
  6. Docker: Latest version

App Configuration

If you have a custom app follow this step
you have to configure your custom app and Username and Password. here Password means Auth Token not actual password.

export APPS_JSON='[
  {
    "url": "https://github.com/frappe/erpnext",
    "branch": "version-15"
  },
  {
    "url": "https://username:password@github.com/username/<CUSTOM_APP>.git",
    "branch": "main"
  }
]'

If you don’t have a custom app
just only configure the erpnext app

export APPS_JSON='[
  {
    "url": "https://github.com/frappe/erpnext",
    "branch": "version-15"
  }
]'

That’s you can follow the bellow command as usual

export APPS_JSON_BASE64=$(echo ${APPS_JSON} | base64 -w 0)

Get Start :rocket:

Step 1

Clone the frappe_docker from the GitHub

git clone https://github.com/frappe/frappe_docker
cd frappe_docker

Step 2

Build the image and I name the image as customapp:1.0.0 as you can see below in the build command

Make sure your using the correct arguments while taking build

  • FRAPPE_BRANCH=v15.0.0
  • PYTHON_VERSION=3.10.12
  • NODE_VERSION=18.18.2

If Your taking image build correctly everything fine

docker build \
  --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
  --build-arg=FRAPPE_BRANCH=v15.0.0 \
  --build-arg=PYTHON_VERSION=3.10.12 \
  --build-arg=NODE_VERSION=18.18.2 \
  --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \
  --tag=customapp:1.0.0 \
  --file=images/custom/Containerfile .

Step 3

Modify the compose.yaml file inside the frappe_docker folder like this

x-customizable-image: &customizable_image
  image: customapp:1.0.0
  pull_policy: never

in coming commands you have to run inside the frappe_docker folder keep in mind

Step 4

Create a directory with the name of gitops

mkdir ~/gitops

Step 5

Here Im assume im using the domain ziptor.com but its not exist in real. you can change your domain where ever you see ziptor.com the reason instead of giving example.com I’m giving this. but in one file example.com will be there we have to correct there that’s why I’m giving some random domain example keep in mind

echo 'TRAEFIK_DOMAIN=ziptor.com' > ~/gitops/traefik.env
echo 'EMAIL=admin@ziptor.com' >> ~/gitops/traefik.env
echo 'HASHED_PASSWORD='$(openssl passwd -apr1 changeit | sed 's/\$/\\\$/g') >> ~/gitops/traefik.env

Step 6

Up the traefik container

docker compose --project-name traefik \
  --env-file ~/gitops/traefik.env \
  -f overrides/compose.traefik.yaml \
  -f overrides/compose.traefik-ssl.yaml up -d

Step 7

Note: change the DB_PASSWORD with some more secure basically this command creates a file inside the gitops folder with the name of mariadb.env if not create create manually add the DB_PASSWORD=changeit

echo "DB_PASSWORD=changeit" > ~/gitops/mariadb.env

Step 7

Make sure running these all commands inside the frappe_docker folder
Deploy the mariadb container

docker compose --project-name mariadb --env-file ~/gitops/mariadb.env -f overrides/compose.mariadb-shared.yaml up -d

Step 8

Please do this step very carefully change the required data and run the command
change your domain and DB Password here you can see the erp.example.com don’t change that one only change the domain where you see the ziptor.com. please do it carefully

cp example.env ~/gitops/erpnext-one.env
sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-one.env
sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-one.env
sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-one.env
sed -i 's/SITES=`erp.example.com`/SITES=\`ziptor.com\`/g' ~/gitops/erpnext-one.env
echo 'ROUTER=erpnext-one' >> ~/gitops/erpnext-one.env
echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/erpnext-one.env

Step 9

Create erpnext-one.yaml


docker compose --project-name erpnext-one \

--env-file ~/gitops/erpnext-one.env \

-f compose.yaml \

-f overrides/compose.redis.yaml \

-f overrides/compose.multi-bench.yaml \

-f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-one.yaml

Step 10

Deploy erpnext-one containers:

docker compose --project-name erpnext-one -f ~/gitops/erpnext-one.yaml up -d

Step 11

Create sites ziptor.com
Change your site instead of ziptor.com

In this command the site password is changei and DB password is changeit so please change those and make it secure. where ever you see changeit in the command change with a secure one.


docker compose --project-name erpnext-one exec backend \

bench new-site ziptor.com --no-mariadb-socket --mariadb-root-password changeit --install-app erpnext --admin-password changeit

After the command you can see like this

To enable sceduler login inside the backend container using this command

docker exec -it <CONTAINER_NAME> bash

example

docker exec -it erpnext-one-backend-1 bash

Before enabling the scheduler if you have a backup database restore it. After Enable Schedular

Here the enabling command

bench --site ziptor.com enable-scheduler

If site not working

Check the the site is working if not working down and up the container

Down the container

docker compose --project-name erpnext-one -f ~/gitops/erpnext-one.yaml down

Up the container

docker compose --project-name erpnext-one -f ~/gitops/erpnext-one.yaml up -d

even though its not working try 2 or three times down and up.

Contact

Github : Antony-M1 · GitHub
Linkedin : Antony

9 Likes

For some reason, I cannot get past the certificate issue

I even tried setting a traefik domain but its also behaving the same.

My Bench Env file:

# Reference: https://github.com/frappe/frappe_docker/blob/main/docs/images-and-compose-files.md

ERPNEXT_VERSION=v15.10.0

DB_PASSWORD=passwd

# Only if you use external database
DB_HOST=mariadb-database
DB_PORT=3306

# Only if you use external Redis
REDIS_CACHE=
REDIS_QUEUE=

# Only with HTTPS override
LETSENCRYPT_EMAIL=devs@thebantoo.com

# These environment variables are not required.

# Default value is `$$host` which resolves site by host. For example, if your host is `example.com`,
# site's name should be `example.com`, or if host is `127.0.0.1` (local debugging), it should be `127.0.0.1`.
# This variable allows to override described behavior. Let's say you create site named `mysite`
# and do want to access it by `127.0.0.1` host. Than you would set this variable to `mysite`.
FRAPPE_SITE_NAME_HEADER=

# Default value is `127.0.0.1`. Set IP address as our trusted upstream address.
UPSTREAM_REAL_IP_ADDRESS=

# Default value is `X-Forwarded-For`. Set request header field whose value will be used to replace the client address
UPSTREAM_REAL_IP_HEADER=

# Allowed values are on|off. Default value is `off`. If recursive search is disabled,
# the original client address that matches one of the trusted addresses
# is replaced by the last address sent in the request header field defined by the real_ip_header directive.
# If recursive search is enabled, the original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field.
UPSTREAM_REAL_IP_RECURSIVE=

# All Values Allowed by nginx proxy_read_timeout are allowed, default value is 120s
# Useful if you have longrunning print formats or slow loading sites
PROXY_READ_TIMEOUT=

# All Values allowed by nginx client_max_body_size are allowed, default value is 50m
# Necessary if the upload limit in the frappe application is increased
CLIENT_MAX_BODY_SIZE=

# List of sites for letsencrypt certificates quoted with backtick (`) and separated by comma (,)
# More https://doc.traefik.io/traefik/routing/routers/#rule
# About acme https://doc.traefik.io/traefik/https/acme/#domain-definition
SITES=`test.bantoo.app`
ROUTER=bench15
BENCH_NETWORK=bench15

.yaml at the bottom

My Container list: doesnt have a <project-name>-proxy-1 container (like @revant_one does in the BWH video)

Edit: bench15.yaml (my erpnext-one.yaml file)

name: bench15
services:
  backend:
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.10.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
  configurator:
    command:
      - |
        ls -1 apps > sites/apps.txt; 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 $$SOCKETIO_PORT;
    depends_on:
      redis-cache:
        condition: service_started
        required: true
      redis-queue:
        condition: service_started
        required: true
    entrypoint:
      - bash
      - -c
    environment:
      DB_HOST: mariadb-database
      DB_PORT: "3306"
      REDIS_CACHE: redis-cache:6379
      REDIS_QUEUE: redis-queue:6379
      SOCKETIO_PORT: "9000"
    image: frappe/erpnext:v15.10.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
  frontend:
    command:
      - nginx-entrypoint.sh
    depends_on:
      backend:
        condition: service_started
        required: true
      websocket:
        condition: service_started
        required: true
    environment:
      BACKEND: backend:8000
      CLIENT_MAX_BODY_SIZE: 50m
      FRAPPE_SITE_NAME_HEADER: $$host
      PROXY_READ_TIMOUT: "120"
      SOCKETIO: websocket:9000
      UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
      UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
      UPSTREAM_REAL_IP_RECURSIVE: "off"
    image: frappe/erpnext:v15.10.0
    labels:
      traefik.docker.network: traefik-public
      traefik.enable: "true"
      traefik.http.routers.bench15-http.entrypoints: http
      traefik.http.routers.bench15-http.middlewares: https-redirect
      traefik.http.routers.bench15-http.rule: Host(`test.bantoo.app`)
      traefik.http.routers.bench15-http.service: bench15
      traefik.http.routers.bench15-https.entrypoints: https
      traefik.http.routers.bench15-https.rule: Host(`test.bantoo.app`)
      traefik.http.routers.bench15-https.service: bench15
      traefik.http.routers.bench15-https.tls: "true"
      traefik.http.routers.bench15-https.tls.certresolver: le
      traefik.http.services.bench15.loadbalancer.server.port: "8080"
    networks:
      bench-network: null
      traefik-public: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
  queue-long:
    command:
      - bench
      - worker
      - --queue
      - long,default,short
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.10.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
  queue-short:
    command:
      - bench
      - worker
      - --queue
      - short,default
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.10.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
  redis-cache:
    image: redis:6.2-alpine
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: redis-cache-data
        target: /data
        volume: {}
  redis-queue:
    image: redis:6.2-alpine
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: redis-queue-data
        target: /data
        volume: {}
  scheduler:
    command:
      - bench
      - schedule
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.10.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
  websocket:
    command:
      - node
      - /home/frappe/frappe-bench/apps/frappe/socketio.js
    depends_on:
      configurator:
        condition: service_completed_successfully
        required: true
    image: frappe/erpnext:v15.10.0
    networks:
      bench-network: null
      mariadb-network: null
    volumes:
      - type: volume
        source: sites
        target: /home/frappe/frappe-bench/sites
        volume: {}
networks:
  bench-network:
    name: bench15
  mariadb-network:
    name: mariadb-network
    external: true
  traefik-public:
    name: traefik-public
    external: true
volumes:
  redis-cache-data:
    name: bench15_redis-cache-data
  redis-queue-data:
    name: bench15_redis-queue-data
  sites:
    name: bench15_sites
x-backend-defaults:
  depends_on:
    configurator:
      condition: service_completed_successfully
  image: frappe/erpnext:v15.10.0
  volumes:
    - sites:/home/frappe/frappe-bench/sites
x-customizable-image:
  image: frappe/erpnext:v15.10.0
x-depends-on-configurator:
  depends_on:
    configurator:
      condition: service_completed_successfully

Here the Core Documentation please refer this site

Document

echo 'TRAEFIK_DOMAIN=ziptor.com' > ~/gitops/traefik.env
echo 'EMAIL=admin@ziptor.com' >> ~/gitops/traefik.env
echo 'HASHED_PASSWORD='$(openssl passwd -apr1 changeit | sed 's/\$/\\\$/g') >> ~/gitops/traefik.env

Make Sure the above command worked properly

Thank you for your response. I should have included my traefik.env

I was following the same docs closely to create my setup. I noticed problems with Traefik and decided to continue thinking it’d resolve down the line. But it hasnt.

Here’s my traefik.env

without quotes around the password, I get this in the console. So I put quotes around the password:

With quotes it doesn’t complain

UPDATE:

spun up a new server and this time didnt run docker in non-priviledged mode. It seems to work now:

1 Like

Hey,

I was wondering why a Linux system is the prerequisite?
Is it because of VOLUMEs?
Because you can probably get away with only using /home/frappe/frappe-bench/sites/sitename.local/public/ and /home/frappe/frappe-bench/sites/sitename.local/private/ as VOLUMEs (making sure to have backups running for the database, of course…
any other reason one cannot use Windows for this?
Notwithstanding, of course I want to use Linux as much as possible. It’s not always possible.

Does using TRAEFIK makes life easier as a Frappe developer? What problems does it solve?

YES. Treafik SSL Freely and it will get renewed automatically every 3 months.

There is one major USE is there using windows instead of Linux. I run the ERPNext in windows for more than 8 months. its getting very slow because the frappe containers are Linux based containers. after that I moved to linux the performance is really good.

1 Like

Here’s a thought (a little off topic, but bear with me here…):
What if I use WSL for a normal frappe installation :thinking:?
Just a thought…

That would probably be fine for testing/dev. But not scalable for production at all.

2 Likes

I get 404 error
even after multiple down and up

Which Operating System You are working. Ensure you mapped the IP in domain provider

First: I use Debian 12.
Second: what do you mean

mapped the IP in domain provider

?
Do you mean DNS record to the domain?
I begin by trying to connect to localhost

Local Host is not working for us in local you should avoid the travik you have to do the changes in the .yml files. please keep in mind this setup for full hosting through Publicaly.

Why not just run a sentence :docker compose --project-name erpnext_docker -f pwd.yml up -d?

Hey, is this the guide to install the free offline version, I have installed erpnext on wsl before but don’t remember how to to do it, I need the offline free version, is this the way to get that?