Help to install a single site ERPNext under docker-compose

I have access to a Debian 10 server, with docker and docker-compose installed via apt-get (i.e. not the latest docker with docker-compose built in, but the old docker-compose with the dash).

I am allowed to use docker-compose (but not docker itself).

I want to create an instance of erpnext, listening on a port. It will be accessed via the host’s nginx (so I will need an nginx file for the site, to proxy to the docker instance).

With most other software, you can Google the software name and docker-compose, find an example docker-compose file, download it, set a few, well-documented environment variables, and you are good to go.

However, with ERPNext, I haven’t been able to find anything suitable. The official docker repo requires a more up-to-date docker version than is supplied with even the latest Debian or Ubuntu, and there is no way my sysadmin will install that, in case it breaks any of the 30 or so docker images they are already running, or it interferes with a future OS upgrade.

As an experiment, just to try to learn more, I did fire up a VM here at home, with the latest Debian on it, and the latest docker, but I am still baffled. I can fire it up using the compose and ancillary files supplied, but I can’t for the life of me actually access anything.

I have spent 3 whole days on this so far, and have got precisely nowhere.

So I am appealing for help here. If anyone can point me in the right direction, or even do some paid consultancy to show me how to set it up, that would be really helpful.

1 Like

Well, many, many thanks to @revant_one :heart: :heart: :heart:. I have been in email correspondence with them about trying to get it running in the old version of docker.

I tried installing the new version of docker on a scratch VM, but I couldn’t even get it running then!

But, with their patient help, I now have it running on that scratch VM, so it’s a good start.

The key things I found are:

  • There is a pwd.yml file in the repo. This is the one to use to just get erpnext working on http, port 8080, without any fancy changes.
  • It is best to start with a clean slate - if you have previously started containers in the frappe-docker directory, there may be volumes lurking around that get used when you start a different combination of yml files. This can seriously screw things up! I solved this by renaming the directory, so the volumes and images were given different names. (I know there are other ways of achieving this, but the rename seemed the simplest.)

Variables I set in the .env file (which I copied from example.env):

ERPNEXT_VERSION=v14.15.1

DB_PASSWORD=123
MYSQL_ROOT_PASSWORD=123

FRAPPE_SITE_NAME_HEADER=(I( put the domain name I wanted to use here)

SITES=`(I( put the domain name I wanted to use here)`
1 Like

Just to add - pwd.yml works out of the box for docker-compose, even without the latest version of docker.

This setup is a very simple single compose file that does everything to start required services and a frappe-bench. It is used to start play with docker instance with a site. The file is located in the root of repo and named pwd.yml .

use the pwd.yml as a reference to build more complex setup

more about pwd.yml frappe_docker/docs/single-compose-setup.md at main · frappe/frappe_docker · GitHub

Just FYI, that page is slightly incomplete. It says:

Volumes

  • sites: Volume for bench data. Common config, all sites, all site configs and site files will be stored here.
  • logs: Volume for bench logs. all process logs are dumped here. No need to mount it. Each container will create a temporary volume for logs if not specified.

But there are 6 volumes defined.

It also says:

This serves only site called frontend through the nginx. FRAPPE_SITE_NAME_HEADER is set to frontend and a default site called frontend is created.

Change the $host will allow container to accept any host header and serve that site. To escape $ in compose yaml use it like $. To unset default site remove currentsite.txt file from sites directory.

It would be helpful to show where $$host should be set (or do you mean “Change FRAPPE_SITE_NAME_HEADER to $host” rather than "Change the $host "?

Our standard here is to have all volumes mounted to folders in the same directory as the app, rather than to use docker volumes. I think this is to allow easy individual backup and restore of an app.

I tried changing all the docker volumes to local folders, but it no longer works if I do. e.g.

    volumes:
      - ./sites:/home/frappe/frappe-bench/sites
      - ./logs:/home/frappe/frappe-bench/logs

If I run that, it no longer works. Here is a selection of the error messages in the log:

configurator_1    | Traceback (most recent call last):
configurator_1    |   File "/usr/local/lib/python3.10/runpy.py", line 196, in _run_module_as_main
configurator_1    |     return _run_code(code, main_globals, None,
configurator_1    |   File "/usr/local/lib/python3.10/runpy.py", line 86, in _run_code
configurator_1    |     exec(code, run_globals)
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 109, in <module>
configurator_1    |     main()
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 16, in main
configurator_1    |     commands = get_app_groups()
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 25, in get_app_groups
configurator_1    |     for app in get_apps():
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", line 102, in get_apps
configurator_1    |     return frappe.get_all_apps(with_internal_apps=False, sites_path=".")
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1386, in get_all_apps
configurator_1    |     apps = get_file_items(os.path.join(sites_path, "apps.txt"), raise_not_found=True)
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1552, in get_file_items
configurator_1    |     content = read_file(path, raise_not_found=raise_not_found)
configurator_1    |   File "/home/frappe/frappe-bench/apps/frappe/frappe/__init__.py", line 1580, in read_file
configurator_1    |     raise OSError(f"{path} Not Found")
configurator_1    | OSError: b'./apps.txt' Not Found
create-site_1     | Waiting for sites/common_site_config.json to be created
create-site_1     | Waiting for sites/common_site_config.json to be created
create-site_1     | could not find sites/common_site_config.json with required keys
crm_create-site_1 exited with code 1

The sites directory looks like this:

$ ls -lR sites
sites:
total 4
drwxr-xr-x 2 root root 4096 Feb  6 10:19 assets

sites/assets:
total 0

send PR.

4 from them are for mariadb and redis. I felt I should explain only frappe related volumes.

https://github.com/frappe/frappe_docker/blob/4a18d57e8d6366c4279513eea8ee8e4045966958/pwd.yml#L100 change this from frontend to $$host as $$ is required to escape $ in compose.yml. Compose V1 I’ve not confirmed.

refer volumes section: Container Basics

You are so helpful, and I feel I am being a pain.

All the volume directories are owned by user 1000 and group 1000 already.

But the assets directory is created (by one of the containers, presumably) as owned by root.

I use cron for backup.

restic command is available in image. use it to sync backup with source of your choice. I’m using S3 + restic.

volume mount locations has nothing to do with backups.

Raid, HA servers are also not backups.

Taking backups is separate and mandatory activity.

Thanks - I see you have thought of this all before. But my sysadmin will not be happy!

Everything is already backed up thoroughly and regularly. I don’t think they will be happy with a different backup program just for erpnext (which will be one of many docker webapps, all following our standards).

As far as I can see, bench backup-all-sites backs up the sites within one of the volumes. They want to be able to see the data in a sub-directory on the host, so it can be accessed without docker running at all.

Plus, as I understand it, there is limited space where docker stores its volumes.

Sorry to be so awkward.

I have been fiddling.

I have a working version using pwd.yml on a debian server with the latest Docker.

I have been trying to get a version which stores its data in local directories - using , for example:

    volumes:
      - ./volumes/sites:/home/frappe/frappe-bench/sites
      - ./volumes/logs:/home/frappe/frappe-bench/logs

It continually fails, complaining about apps.txt or common_site_config.json being missing.

I looked in the docker volumes of the working version, and copied apps.txt, apps.json and common_site_config.json to my sites directory.

Now it works.

Is there a step missing to create these? Or does the creation fail when the directory is not a docker volume, somehow?

you can make it part of the configuration service that runs once. use ls -1 apps > sites/apps.txt and echo "{}" > sites/common_site_config.json to create those files. check:

yes it fails when it’s a bind mount.

Any idea why it fails?

I don’t understand why the behaviour for a bind mount is different to that for a docker volume.

I think that I have to chown the sites directory to user 1000 (it, and its subdirectories, are created as owned by root, for some reason). Of course, if I am not root, and not user 1000, this is hard to do. I assume, if I don’t do it, the create will fail too?

Also, I notice apps.json is also missing.

you can use additional service to chown as root, it can be used with docker-compose

services:
  # Use if in case of vol user/owner issues
   fix-vol:
     image: frappe/erpnext:v14
     user: root
     command: chown -R 1000:1000 /sites
     volumes:
       - /path/to/sites:sites

Good point. Still want to know why it works differently with directory volume rather than docker volume.

Did I say thanks for your help? Lots of times? I’ll say it again!

search string: docker volume vs bind mount permissions - Google Search

That’s a useful explanation, but I already knew that, I think.

I am creating my sites directory in the host with permissions 777. So anyone could access it.

However, the assets folder was created (in one of the containers - don’t know which one) as owned by root, and no apps.txt, apps.json or common_site_config.json was created.

I have been worrying away at this for some time now, and have learned a few things:

Your Container (usually called Dockerfile) files have VOLUME directives, and expect stuff copied into those folders to end up in the volume.

If people use regular docker volumes (which are stored in an obscure place in the filesystem, and are difficult to access from the host) that’s fine, provided no future version of your Container file puts something different in that volume. The contents of the volume will be whatever the first build of that Container put there, along with any later changes made during the running of the container.

But if people choose to use bind mounts the volumes will be created empty - none of the data your RUN commands created will be put in there.

See the latter part of When pulling a docker image, what is downloaded, and what built locally? - Stack Overflow for my research and conclusions.

Supplementary question - why is /sites/assets created as a separate volume? I can find no obvious reason, and it just confuses me.