Container Basics

Why?

People who don’t know containers land up successfully installing ERPNext with containers and get stuck later. Read for container basics.

Prerequisites

Unix/Linux basics, init system, processes, mount points, bash shell.

podman or docker, podman-compose or docker compose is installed. Go for rootless podman if you can. Standard docker will work. Make sure you’re NOT using snap to install docker, it’ll not work as expected through snap.

Install,

Or alternatively install,

This guide explains interacting with containers, it is not a production or development setup guide.

What are containers?

  • Not a virtual machines. No dedicated kernel and no VM like isolation on host machine.
    • Try uname -a in container and outside container. Observe same output.
    • Start a container and see the process in output of ps -aux on host machine.
  • No Dedicated init system like systemd e.g. NO systemctl enable mariadb, systemctl restart mariadb. Management of containers is done by container engine so there is no need of init system in containers.
  • It is a package (container) with all runtime dependencies included that runs a process on your host machine.
  • There is no state maintained in container. Volumes are required for persistence.

Starting containers from ready images

Start a container with the familiar bench.

podman run --rm -it frappe/bench:latest bash

You will enter into a container in interactive mode with pseudo-TTY for container (-it). The container will be removed when it stops (--rm). Try bench commands here. You can even do bench init, you will lose all the data created after container is stopped.

frappe@5a738e029df9:~$ bench init --verbose --skip-redis-config-generation --version version-14 frappe-bench
...
Done in 18.72s.
SUCCESS: Bench frappe-bench initialized

You can see the frappe-bench directory. It will vanish when container is closed.

Volumes

We need volumes to maintain the data after container is removed. New container can start with the volume resuming from the state in volume.

podman run --name=bench -v "${HOME}"/bench-data:/home/frappe/bench-host --rm -it frappe/bench:latest bash

Now you will see a directory inside container which mounts directory (“${HOME}”/bench-data) from host machine under specified location in container (/home/frappe/bench-host)

You can try to create a bench in that location to keep the data, it’ll fail.

frappe@5a738e029df9:~$ bench init --verbose --skip-redis-config-generation --version version-14 bench-host/frappe-bench
Traceback (most recent call last):
  File "/home/frappe/.bench/bench/commands/make.py", line 68, in init
    init(
  File "/home/frappe/.bench/bench/utils/render.py", line 105, in wrapper_fn
    return fn(*args, **kwargs)
  File "/home/frappe/.bench/bench/utils/system.py", line 63, in init
    bench.setup.dirs()
  File "/home/frappe/.bench/bench/utils/render.py", line 126, in wrapper_fn
    return fn(*args, **kwargs)
  File "/home/frappe/.bench/bench/bench.py", line 337, in dirs
    os.makedirs(self.bench.name, exist_ok=True)
  File "/home/frappe/.pyenv/versions/3.10.5/lib/python3.10/os.py", line 225, in makedirs
    mkdir(name, mode)
PermissionError: [Errno 13] Permission denied: 'bench-host/frappe-bench'

ERROR: There was a problem while creating bench-host/frappe-bench
Do you want to rollback these changes? [y/N]: y
INFO: Rolling back Bench "bench-host/frappe-bench"

you need to set the right permission for directory.

enter the container as root in another terminal and set it.

podman exec --user root -it bench-data bash
root@5a738e029df9:/home/frappe# chown -R 1000:1000 bench-host/

or set it from host machine.

sudo chown -R 1000:1000 "${HOME}"/bench-data

Now try bench init again from previous terminal.

bench init --verbose --skip-redis-config-generation --version version-14 bench-host/frappe-bench

You will have a frappe-bench at location "${HOME}"/bench-data on host.

What we just did was we used bind mount to mount a directory from host machine into container. This is fine for development or single vm setup. Better approach will be to use volumes instead of bind mount.

To use volume don’t mention it as a path. e.g.

podman run --name=bench -v bench-data:/home/frappe/bench-host --rm -it frappe/bench:latest bash

Above command will create volume called bench-data and mount it under specified location in container.
It will work without issuing any additional permission commands.

You can verify with command podman volume ls.

Volumes can be further extended using drivers, serve them over NFS or cloud provided volumes making them more flexible over bind mounts.

Execute following to know where it is located on host machine.

podman volume inspect bench-data

Now that you have initiated a bench. If you try to start bench from location /home/frappe/bench-host in container you will face errors due to redis and database not available. We solve this problem in next section.

Exit the bench container before continuing further.

Environment variables, Networks

Let’s create a common network under which all our containers can connect.

podman network create bench-network

Let’s start 3 redis containers that are require for redis-cache, redis-queue and redis-socketio

podman run --name=redis-cache --net=bench-network --rm -d redis:6.2-alpine
podman run --name=redis-queue --net=bench-network --rm -d redis:6.2-alpine
podman run --name=redis-socketio --net=bench-network --rm -d redis:6.2-alpine

We need to start mariadb container with special command params that meet the needs of frappe framework.
We also need to mount mariadb volume to persist MariaDB state.

podman run \
  --name=mariadb \
  --net=bench-network \
  -e MYSQL_ROOT_PASSWORD=123 \
  -v mariadb:/var/lib/mysql \
  --rm -d mariadb:10.6 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci \
  --skip-character-set-client-handshake \
  --skip-innodb-read-only-compressed

Params after the image name mariadb:10.6 are passed to mariadb container. -e sets the required MYSQL_ROOT_PASSWORD env variable which sets the db root password.

Now start the bench container again connected to bench-network and publishing the port 8000 and 9000.

podman run --name=bench --net=bench-network -v bench-data:/home/frappe/bench-host -p 8000:8000 -p 9000:9000 --rm -it frappe/bench:latest bash

Once inside container configure bench to use the started redis and mariadb

frappe@12cf8104d704:~$ cd bench-host/frappe-bench
frappe@12cf8104d704:~/bench-host/frappe-bench$ bench set-config -g redis_cache redis://redis-cache:6379
frappe@12cf8104d704:~/bench-host/frappe-bench$ bench set-config -g redis_queue redis://redis-queue:6379
frappe@12cf8104d704:~/bench-host/frappe-bench$ bench set-config -g redis_socketio redis://redis-socketio:6379
frappe@12cf8104d704:~/bench-host/frappe-bench$ bench set-config -g db_host mariadb

Now you can start the bench.

bench start

You should see services running, check using:

podman ps
CONTAINER ID  IMAGE                               COMMAND               CREATED         STATUS             PORTS       NAMES
12cf8104d704  docker.io/frappe/bench:latest       bash                  14 minutes ago  Up 14 minutes ago              bench
d18a402e2311  docker.io/library/redis:6.2-alpine  redis-server          12 minutes ago  Up 12 minutes ago              redis-cache
2feda9a72654  docker.io/library/redis:6.2-alpine  redis-server          12 minutes ago  Up 12 minutes ago              redis-queue
2f8f637f59e5  docker.io/library/redis:6.2-alpine  redis-server          12 minutes ago  Up 12 minutes ago              redis-socketio
69ab1c7a3c52  docker.io/library/mariadb:10.6      --character-set-s...  4 seconds ago   Up 4 seconds ago               mariadb

enter into the running bench container in separate terminal to create new site

podman exec -it bench bash

Now enter the frappe-bench directory and create a site called site1.localhost

frappe@12cf8104d704:~$ cd bench-host/frappe-bench/
frappe@12cf8104d704:~/bench-host/frappe-bench$ bench new-site site1.localhost --no-mariadb-socket --db-root-password=123 --admin-password=admin

you can check http://site1.localhost:8000 in browser now.

Logs and other commands

To check logs run podman logs -f {container-name}. -f will follow the logs where you need to CTRL + C to quit. Example:

podman logs -f bench

List running containers:

podman ps

List all container (including stopped)

podman container ls --all

Stop all containers

podman stop $(podman ps -a -q)

Cleanup everything, delete containers, volumes, networks

podman system prune -f && podman volume prune -f

Check podman --help for cli help. Check podman or docker docs for in details commands.

Before continuing further you can stop all containers and clean up everything.

Start a set of services

The manual starting of each container and connecting to network is time consuming and prone human errors.
We can use docker compose or podman-compose that simplifies the configuration and process of starting multiple containers with shared volumes and network. Check the complete docker-compose reference on docker docs for more details.

Let’s create a compose.yml that does the above manual steps with one yaml file.

version: "3.7"
services:
  mariadb:
    image: mariadb:10.6
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --skip-character-set-client-handshake
      - --skip-innodb-read-only-compressed
    environment:
      MYSQL_ROOT_PASSWORD: 123
    volumes:
      - mariadb-data:/var/lib/mysql

  redis-cache:
    image: redis:6.2-alpine

  redis-queue:
    image: redis:6.2-alpine

  redis-socketio:
    image: redis:6.2-alpine

  bench:
    image: frappe/bench:latest
    command: ["tail", "-f", "/dev/null"]
    environment:
      - SHELL=/bin/bash
    volumes:
      - bench-data:/home/frappe/bench-host
    ports:
      - 8000-8005:8000-8005
      - 9000-9005:9000-9005

volumes:
  bench-data:
  mariadb-data:

now start all service together

podman-compose --project-name=bench up -d

specifying project name with --project-name=bench will create containers with that prefix or else it will create containers with directory name as prefix.

List the running container

podman ps
CONTAINER ID  IMAGE                           COMMAND               CREATED         STATUS             PORTS                                                               NAMES
2897e0156475  docker.io/library/mariadb:10.6  --character-set-s...  30 seconds ago  Up 30 seconds ago                                                                      bench_mariadb_1
ce663bed0a43  docker.io/library/redis:alpine  redis-server          22 seconds ago  Up 22 seconds ago                                                                      bench_redis-cache_1
c5399b4f1d45  docker.io/library/redis:alpine  redis-server          21 seconds ago  Up 21 seconds ago                                                                      bench_redis-queue_1
cff7336b37c6  docker.io/library/redis:alpine  redis-server          20 seconds ago  Up 20 seconds ago                                                                      bench_redis-socketio_1
887ce56c0df3  docker.io/frappe/bench:latest   tail -f /dev/null     18 seconds ago  Up 18 seconds ago  0.0.0.0:8000-8005->8000-8005/tcp, 0.0.0.0:9000-9005->9000-9005/tcp  bench_bench_1

Enter the bench container. It is named bench_bench_1 as per the previous output.

podman exec -it bench_bench_1 bash

once inside container execute the rest of the bench setup commands.

frappe@887ce56c0df3:~$ bench init --verbose --skip-redis-config-generation --version version-14 bench-host/frappe-bench
frappe@887ce56c0df3:~$ cd bench-host/frappe-bench
frappe@887ce56c0df3:~/bench-host/frappe-bench$ bench set-config -g redis_cache redis://redis-cache:6379
frappe@887ce56c0df3:~/bench-host/frappe-bench$ bench set-config -g redis_queue redis://redis-queue:6379
frappe@887ce56c0df3:~/bench-host/frappe-bench$ bench set-config -g redis_socketio redis://redis-socketio:6379
frappe@887ce56c0df3:~/bench-host/frappe-bench$ bench set-config -g db_host mariadb
frappe@887ce56c0df3:~/bench-host/frappe-bench$ bench start

Enter the container from another terminal again to create site.

podman exec -it bench_bench_1 bash

Create site

frappe@887ce56c0df3:~$ cd bench-host/frappe-bench
frappe@887ce56c0df3:~/bench-host/frappe-bench$ bench new-site site1.localhost --no-mariadb-socket --db-root-password=123 --admin-password=admin

Once the site creation is complete you can visit it http://site1.localhost:8000.

Notice how we can only execute 1 command at a time in container. We can configure that command to start multiple processes, but we can only execute one command.

Next: Container Builds

25 Likes

I am honestly unsure if this makes things simpler or harder :sweat_smile:. I guess it makes some things simpler and some harder.

4 Likes

It is to point posts like this here. Somehow I find posts like this.
Other reason, new easy-install.py uses containers.

3 Likes

Thank you Revant! Very helpful information!

1 Like

Very helpful guide. Thanks a lot, @revant_one :pray:

2 Likes

We would like to know the scenario in which we should go for this container installation instead of traditional VPS installation. A comparison of benefits and pros and cons with respect to Frappe and ERP , can help all the community members who are coming from non engineering background like me .

Don’t go for containers unless you are already familiar and using them in your environment.

For others who don’t know containers yet have somehow successfully installed EPRNext using containers, they can read the guide if they wish.

1 Like

We have not tried this till date, though have little conceptual knowledge. We would be interested to know and hence will follow your instructions for learning perspective . Thanks for sharing @mohitchechani

Great Guide thanks for sharing. maybe you can also add how to install app guide because that where could not continue as bench get-app did not work inside the container. i remember that docker buildx binary is needed to make images with our apps, anyway i will give it another try Thanks

2 Likes

Ideally next step is to understand how to build simple container and connect it with database. Then understand how erpnext production container is built.

Guide for custom apps exists, people who know how to build and use are already using it.

I’ve sent a PR that simplifies image building by using the built-in bench init --apps_path=apps.json. refactor: build only one frappe/erpnext image by revant · Pull Request #1032 · frappe/frappe_docker · GitHub

3 Likes

podman podman podman

Hello community, looks the nice video from @revant_one

Thank’s revant_one.

jannis

1 Like