How to add custom app to production docker setup?

Hey there

I have setup erp next using the Easy Install Script.
I have now a custom docker compose script and several containers running:

docker ps
CONTAINER ID   IMAGE                    COMMAND                  CREATED      STATUS                PORTS                                                                      NAMES
f17283135290   frappe/erpnext:v15.0.0   "nginx-entrypoint.sh"    2 days ago   Up 2 days                                                                                        frappe-frontend-1
28274a9db8eb   frappe/erpnext:v15.0.0   "bench schedule"         2 days ago   Up 2 days                                                                                        frappe-scheduler-1
d15bde1777b9   frappe/erpnext:v15.0.0   "bench worker --queu…"   2 days ago   Up 2 days                                                                                        frappe-queue-long-1
da8e5e0a26b6   frappe/erpnext:v15.0.0   "node /home/frappe/f…"   2 days ago   Up 2 days                                                                                        frappe-websocket-1
16afb00b1c87   frappe/erpnext:v15.0.0   "/home/frappe/frappe…"   2 days ago   Up 2 days                                                                                        frappe-backend-1
a6e922247e94   frappe/erpnext:v15.0.0   "bench worker --queu…"   2 days ago   Up 2 days                                                                                        frappe-queue-short-1
77a0479a87af   redis:6.2-alpine         "docker-entrypoint.s…"   2 days ago   Up 2 days             6379/tcp                                                                   frappe-redis-cache-1
a42029811592   redis:6.2-alpine         "docker-entrypoint.s…"   2 days ago   Up 2 days             6379/tcp                                                                   frappe-redis-queue-1
54e67fe8b8e5   mariadb:10.6             "docker-entrypoint.s…"   2 days ago   Up 2 days (healthy)   3306/tcp                                                                   frappe-db-1
08d5adc88f3b   traefik:2.5              "/entrypoint.sh --pr…"   2 days ago   Up 2 days             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   frappe-proxy-1

Now i would like to add the following app to my setup:

I have read through various topics but most of them seem to be outdated or have not provided a clear answer.

It seems that one of the possible solutions would be to add this app to the approriate .json config and rebuild the container from scratch. But this is not my preferred way.

Are there any alternatives without re-building the whole container?
Can i execute the bench commands within one of the docker containers?

Thanks

@swissbyte try to open the container that has frappe/frappe-bench folder with docker exec -it id bash

Thanks for your answer. the bench command is available within multiple containers.
I think that maybe the backend container could be the right one?

Update:
I tried to install it within the backend container:

frappe@16afb00b1c87:~/frappe-bench$ bench get-app https://github.com/libracore/erpnextswiss.git
Getting erpnextswiss
$ git clone https://github.com/libracore/erpnextswiss.git   --origin upstream
Cloning into 'erpnextswiss'...
remote: Enumerating objects: 8443, done.
remote: Counting objects: 100% (2722/2722), done.
remote: Compressing objects: 100% (365/365), done.
remote: Total 8443 (delta 2423), reused 2593 (delta 2355), pack-reused 5721
Receiving objects: 100% (8443/8443), 5.38 MiB | 1.75 MiB/s, done.
Resolving deltas: 100% (6438/6438), done.
Ignoring dependencies of https://github.com/libracore/erpnextswiss.git. To install dependencies use --resolve-deps
Installing erpnextswiss
$ /home/frappe/frappe-bench/env/bin/python -m pip install --quiet --upgrade -e /home/frappe/frappe-bench/apps/erpnextswiss 
$ bench build --app erpnextswiss
✔ Application Assets Linked                                                                                                                                                       


yarn run v1.22.19
$ node esbuild --production --apps erpnextswiss --run-build-command
File                                                        Size

 DONE  Total Build Time: 121.406ms

Done in 0.71s.
WARN: restart failed: Couldn't find supervisorctl in PATH
frappe@16afb00b1c87:~/frappe-bench$ 

And then:

frappe@16afb00b1c87:~/frappe-bench$ bench install-app erpnextswiss

Installing erpnextswiss...
An error occurred while installing erpnextswiss: Module import failed for ERPNextSwiss Settings, the DocType you're trying to open might be deleted.<br> Error: No module named 'frappe.core.doctype.erpnextswiss_settings'
Traceback (most recent call last):
  File "apps/frappe/frappe/modules/utils.py", line 241, in load_doctype_module
    doctype_python_modules[key] = frappe.get_module(module_name)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/__init__.py", line 1409, in get_module
    return importlib.import_module(modulename)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1126, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'frappe.core.doctype.erpnextswiss_settings'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "apps/frappe/frappe/commands/site.py", line 462, in install_app
    _install_app(app, verbose=context.verbose, force=force)
  File "apps/frappe/frappe/installer.py", line 306, in install_app
    frappe.get_attr(after_install)()
  File "apps/erpnextswiss/erpnextswiss/setup/install.py", line 7, in after_install
    install_basic_docs()
  File "apps/erpnextswiss/erpnextswiss/setup/install.py", line 23, in install_basic_docs
    doc = frappe.get_doc("ERPNextSwiss Settings", "ERPNextSwiss Settings")
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/__init__.py", line 1266, in get_doc
    doc = frappe.model.document.get_doc(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/model/document.py", line 80, in get_doc
    controller = get_controller(doctype)
                 ^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/model/base_document.py", line 69, in get_controller
    site_controllers[doctype] = import_controller(doctype)
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/model/base_document.py", line 94, in import_controller
    module = load_doctype_module(doctype, module_name)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "apps/frappe/frappe/modules/utils.py", line 245, in load_doctype_module
    raise ImportError(msg) from e
ImportError: Module import failed for ERPNextSwiss Settings, the DocType you're trying to open might be deleted.<br> Error: No module named 'frappe.core.doctype.erpnextswiss_settings'

frappe@16afb00b1c87:~/frappe-bench$ 

Result:

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 282, in handle
    keepalive = self.handle_request(req, conn)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 334, in handle_request
    respiter = self.wsgi(environ, resp.start_response)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 74, in application
    app(environ, start_response),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 189, in application
    resp = f(*args[:-2] + (request,))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 128, in application
    response = handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 389, in handle_exception
    response = get_response("message", http_status_code=http_status_code)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/serve.py", line 26, in get_response
    response = ErrorPage(exception=e).render()
               ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/error_page.py", line 7, in __init__
    super().__init__(path=path, http_status_code=http_status_code)
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 45, in __init__
    self.set_template_path()
  File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 54, in set_template_path
    app_path = frappe.get_app_path(app)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1438, in get_app_path
    return get_pymodule_path(app_name, *joins)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1468, in get_pymodule_path
    return abspath(join(dirname(get_module(scrub(modulename)).__file__ or ""), *joins))
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1409, in get_module
    return importlib.import_module(modulename)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1140, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'erpnextswiss

Follow docs to build custom image https://github.com/frappe/frappe_docker/blob/main/docs/custom-apps.md

Thanks for your answer.

Is this the only way to load the above mentioned app?
Cause the app instructions tells us to use the bench command instead of creating a new image.

Yes, for production containers image is required.

I was able to create an image with expnextswiss inside.
This was my procedure:
Can you please have a look and let me know if you see potential for improvement?
Especially the part

apt update && apt install ffmpeg libsm6 libxext6

Would be nice to be run as part of the Dockerfile. Which dockerfile do i need to modify?

Step-by-Step Guide for Setting up ERPNextSwiss with Frappe Docker:

Clone the Frappe Docker Repository:

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

Create an apps.json File:
Create an apps.json file with the following content:

[
  {
    "url": "https://github.com/frappe/payments",
    "branch": "develop"
  },
  {
    "url": "https://github.com/frappe/erpnext",
    "branch": "version-15"
  },
  {
    "url": "https://github.com/libracore/erpnextswiss.git",
    "branch": "v15"
  }
]

Replace the payments app with your desired example. Save this file, and then encode it to base64:

export APPS_JSON_BASE64=$(base64 -w 0 /path/to/apps.json)

Build the Docker Image:

docker build \
  --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \
  --build-arg=FRAPPE_BRANCH=version-15 \
  --build-arg=PYTHON_VERSION=3.10.12 \
  --build-arg=NODE_VERSION=18.18.2 \
  --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \
  --tag=databyte.ch/user/repo/custom:1.0.0 \
  --file=images/custom/Containerfile .

Run the Easy Installer Script:

wget https://raw.githubusercontent.com/frappe/bench/develop/easy-install.py
python3 easy-install.py --prod --email <yourmail> -s <yourdomain> -i databyte.ch/user/repo/custom:1.0.0

Note the parameter -i for specifying the image. The script will likely end with an error due to incorrect references. The new Compose file is named frappe-compose.yml.

Replace the Incorrect Version in the Compose File:

sed -i 's/:v15.1.0//g' frappe-compose.yml

Start the Docker Containers:

/usr/bin/docker compose -p frappe -f /root/frappe-compose.yml up -d

This process may take some time. After the start, create a new site:

docker compose -p frappe exec backend bench new-site <yourdomain> --no-mariadb-socket --db-root-password <yourdbpassword> --admin-password <yourpassword> --install-app erpnext --install-app erpnextswiss --set-default

Resolve ERPNextSwiss Installation Issues:
Install required tools inside the backend container:

docker compose -p frappe exec -it -u 0 backend bash

Inside the container, run:

apt update && apt install ffmpeg libsm6 libxext6

Exit the container and then install ERPNextSwiss:

bench --site <yourdomain> install-app erpnextswiss

If you encounter duplicate entry issues, force the installation:

bench --site <yourdomain> install-app erpnextswiss --force

Verify Installation:
The installation should now be complete, and you can access the website. If issues persist, refer to the

Make it,

--version=1.0.0 --image=databyte.ch/user/repo/custom

Then you’ll not need to replace version

I am following the process, and I have reached the step: “Start the Docker Containers.”

When I run the command:

sudo /usr/bin/docker compose -p frappe -f ~/frappe-compose.yml up -d

thanhpci@ubuntu-s-2vcpu-4gb-120gb-intel-sgp1-01:~$ sudo /usr/bin/docker compose -p frappe -f ~/frappe-compose.yml up -d
[sudo] password for thanhpci:
[+] Running 7/7
:heavy_check_mark: queue-long Pulled 2.3s
:heavy_check_mark: queue-short Pulled 2.2s
:heavy_check_mark: configurator Pulled 2.4s
:heavy_check_mark: websocket Pulled 2.6s
:heavy_check_mark: frontend Pulled 2.2s
:heavy_check_mark: scheduler Pulled 2.3s
:heavy_check_mark: backend Pulled 2.2s
[+] Running 11/11
:heavy_check_mark: Container frappe-redis-cache-1 Running 0.0s
:heavy_check_mark: Container frappe-db-1 Healthy 0.5s
:heavy_check_mark: Container frappe-redis-queue-1 Running 0.0s
:heavy_check_mark: Container frappe-configurator-1 Exited 4.3s
:heavy_check_mark: Container frappe-queue-short-1 Running 0.0s
:heavy_check_mark: Container frappe-proxy-1 Running 0.0s
:heavy_check_mark: Container frappe-backend-1 Running 0.0s
:heavy_check_mark: Container frappe-queue-long-1 Running 0.0s
:heavy_check_mark: Container frappe-websocket-1 Running 0.0s
:heavy_check_mark: Container frappe-frontend-1 Running 0.0s
:heavy_check_mark: Container frappe-scheduler-1 Running

Everything runs successfully without any errors. However, when I try to access the site using the IP on port 80 or 443, I get a “404 page not found” error.

During the easy install step, I entered the domain name in the site field. I’m not sure if that was correct.

How can I debug this? I tried accessing the backend container to debug with bench, but it didn’t seem to work. How should I approach debugging this issue and find where the error is?

I actually want to install my custom app on that site. I want to make sure that Frappe and ERPNext are running. After they have successfully started, I will install the required packages into the environment, and then I will install the app. However, Frappe isn’t even running yet.