I have a custom version of ERPNext docker image file which I modified from this build file, mostly to support the additional apps I am using.
ARG PYTHON_VERSION=3.11.6
ARG DEBIAN_BASE=bookworm
FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base
COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template
COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
COPY resources/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY resources/supervisord.conf /etc/supervisord.conf
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
ARG WKHTMLTOPDF_DISTRO=bookworm
ARG NODE_VERSION=18.18.2
ENV NVM_DIR=/home/frappe/.nvm
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
RUN useradd -ms /bin/bash frappe \
&& apt-get update \
&& apt-get install --no-install-recommends -y \
curl \
git \
vim \
nginx \
gettext-base \
# weasyprint dependencies
libpango-1.0-0 \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
# For backups
restic \
# MariaDB
mariadb-client \
# Postgres
libpq-dev \
postgresql-client \
# For healthcheck
wait-for-it \
jq \
# NodeJS
&& mkdir -p ${NVM_DIR} \
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
&& . ${NVM_DIR}/nvm.sh \
&& nvm install ${NODE_VERSION} \
&& nvm use v${NODE_VERSION} \
&& npm install -g yarn \
&& nvm alias default v${NODE_VERSION} \
&& rm -rf ${NVM_DIR}/.cache \
&& echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \
# Install wkhtmltopdf with patched qt
&& if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \
&& if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \
&& downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \
&& curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
&& apt-get install -y ./$downloaded_file \
&& rm $downloaded_file \
# Clean up
&& rm -rf /var/lib/apt/lists/* \
&& rm -fr /etc/nginx/sites-enabled/default \
&& pip3 install frappe-bench \
# Custom install Python Pillow Package
&& pip3 install --upgrade pip \
# Fixes for non-root nginx and logs to stdout
&& sed -i '/user www-data/d' /etc/nginx/nginx.conf \
&& ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \
&& touch /run/nginx.pid \
&& chown -R frappe:frappe /etc/nginx/conf.d \
&& chown -R frappe:frappe /etc/nginx/nginx.conf \
&& chown -R frappe:frappe /var/log/nginx \
&& chown -R frappe:frappe /var/lib/nginx \
&& chown -R frappe:frappe /run/nginx.pid \
&& chmod 755 /usr/local/bin/nginx-entrypoint.sh \
&& chmod 644 /templates/nginx/frappe.conf.template
RUN apt-get update \
&& apt-get -y install supervisor \
&& apt-get install ffmpeg libsm6 libxext6 -y \
&& apt-get install libmagic-dev -y \
&& apt-get install libgl1 -y
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
FROM base AS builder
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
# For frappe framework
wget \
# For psycopg2
libpq-dev \
# Other
libffi-dev \
liblcms2-dev \
libldap2-dev \
libmariadb-dev \
libsasl2-dev \
libtiff5-dev \
libwebp-dev \
redis-tools \
rlwrap \
tk8.6-dev \
cron \
# For pandas
gcc \
build-essential \
libbz2-dev \
&& rm -rf /var/lib/apt/lists/*
# Added by custom to remove `Couldn't find supervisorctl in PATH` warning
# RUN apt-get update && apt-get -y install supervisor
# apps.json includes
ARG APPS_JSON_BASE64
RUN if [ -n "${APPS_JSON_BASE64}" ]; then \
mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \
fi
USER frappe
ARG FRAPPE_BRANCH=version-15
ARG FRAPPE_PATH=https://github.com/frappe/frappe
RUN export APP_INSTALL_ARGS="" && \
if [ -n "${APPS_JSON_BASE64}" ]; then \
export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \
fi && \
bench init ${APP_INSTALL_ARGS}\
--frappe-branch=${FRAPPE_BRANCH} \
--frappe-path=${FRAPPE_PATH} \
--no-procfile \
--no-backups \
--skip-redis-config-generation \
--verbose \
/home/frappe/frappe-bench && \
cd /home/frappe/frappe-bench && \
echo "{}" > sites/common_site_config.json && \
find apps -mindepth 1 -path "*/.git" | xargs rm -fr && \
pip3 install 'python-barcode~=0.15.1' --force-reinstall && \
pip3 install 'Pillow~=10.0.1' --force-reinstall && \
pip3 install 'opencv-python-headless~=4.8.1.78' --force-reinstall && \
mkdir -p /home/frappe/frappe-bench/backup
FROM base as backend
USER frappe
COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench
WORKDIR /home/frappe/frappe-bench
VOLUME [ \
"/home/frappe/frappe-bench/sites", \
"/home/frappe/frappe-bench/sites/assets", \
"/home/frappe/frappe-bench/logs" \
"/home/frappe/frappe-bench/backup" \
]
CMD [ \
"/home/frappe/frappe-bench/env/bin/gunicorn", \
"--chdir=/home/frappe/frappe-bench/sites", \
"--bind=0.0.0.0:8000", \
"--threads=4", \
"--workers=2", \
"--worker-class=gthread", \
"--worker-tmp-dir=/dev/shm", \
"--timeout=120", \
"--preload", \
"frappe.app:application" \
]
Below are the contents of my docker-compose configurator
section which I modified a bit from this file.
services:
configurator:
<<: *backend_defaults
container_name: erpnext_configurator
deploy:
restart_policy:
condition: none
env_file:
- .env
networks:
- web
entrypoint:
- bash
- -c
volumes: *volumes_with_bkp_mount
# add redis_socketio for backward compatibility
command:
- >
echo "{}" > sites/common_site_config.json;
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;
wait-for-it -t 120 $$DB_HOST:$$DB_PORT;
wait-for-it -t 120 $$REDIS_CACHE;
wait-for-it -t 120 $$REDIS_CACHE;
export start=`date +%s`;
until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
[[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
do
echo "Waiting for sites/common_site_config.json to be created";
sleep 5;
if (( `date +%s`-start > 120 )); then
echo "could not find sites/common_site_config.json with required keys";
exit 1
fi
done;
echo "sites/common_site_config.json found";
echo "attempting to create new-site using bench";
bench new-site --admin-password=$$ADMIN_PWD --db-host $$DB_HOST --db-port $$DB_PORT --no-mariadb-socket --db-root-username root --db-root-password=$$DB_ROOT_PWD --db-name $$DB_NAME --db-password $$DB_PWD --verbose --install-app erpnext --set-default $$SITE_NAME;
# bench new-site --admin-password=$$ADMIN_PWD --db-host $$PG_HOST --db-type postgres --db-port $$PG_PORT --db-root-username $$PG_ADMIN --db-root-password=$$PG_ADMIN_PASS --db-name $$DB_NAME --db-password $$PG_DB_PASS --verbose --install-app erpnext --set-default $$SITE_NAME;
echo "successfully created new-site using bench";
echo "initiating backup...";
backup_file_name=ERPNext_backup_"$(date +"%Y%m%d_%H%M%S%Z")"_start;
backup_folder=/home/frappe/frappe-bench/backup;
backup_root_dir="$$backup_folder"/"$$backup_file_name";
mkdir -p "$$backup_root_dir";
echo "backup directory:";
echo "$$backup_root_dir";
backup_compress_file="$$backup_root_dir.tar.gz";
echo "backup compress file name: ";
echo "$$backup_compress_file";
bench --site $$SITE_NAME backup --with-files --compress --verbose --exclude 'Error Log,Access Log,Activity Log' --backup-path-conf "$$backup_root_dir"/conf.tar.tgz --backup-path-db "$$backup_root_dir"/db.tar.tgz --backup-path-files "$$backup_root_dir"/files.tar.tgz --backup-path-private-files "$$backup_root_dir"/pvt_files.tar.tgz;
echo "backup completed!";
echo "installing apps";
for app_name in $$APPS_TO_INSTALL;
do
echo "installing $$app_name";
bench --site $$SITE_NAME install-app $$app_name;
done;
echo "completed!"
environment:
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
REDIS_CACHE: ${REDIS_CACHE}
REDIS_QUEUE: ${REDIS_QUEUE}
SOCKETIO_PORT: 9000
APPS_TO_INSTALL: ${APPS_TO_INSTALL}
SITE_NAME: ${SITE_NAME}
ADMIN_PWD: ${ADMIN_PWD}
DB_ROOT_PWD: ${DB_ROOT_PWD}
DB_NAME: ${DB_NAME}
DB_PWD: ${DB_PWD}
PUID: ${PUID}
PGID: ${PGID}
depends_on: {}
When I try to open the site, I am getting Internal Server Error
blank page, and the logs from the backend
container is as follows:
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 392, 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/template_page.py", line 84, in render
html = self.get_html()
^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/utils.py", line 524, in cache_html_decorator
html = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 101, in get_html
html = self.render_template()
^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/website/page_renderers/template_page.py", line 238, in render_template
html = frappe.render_template(self.source, self.context, safe_render=safe_render)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/jinja.py", line 98, in render_template
return get_jenv().from_string(template).render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/jinja2/environment.py", line 1301, in render
self.environment.handle_exception()
File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/jinja2/environment.py", line 936, in handle_exception
raise rewrite_traceback_stack(source=source)
File "<template>", line 1, in top-level template code
File "/home/frappe/frappe-bench/apps/frappe/frappe/templates/web.html", line 1, in top-level template code
{% extends base_template_path %}
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/templates/base.html", line 23, in top-level template code
{%- block head -%}
File "/home/frappe/frappe-bench/apps/frappe/frappe/templates/base.html", line 24, in block 'head'
{% include "templates/includes/head.html" %}
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/templates/includes/head.html", line 9, in top-level template code
{{ include_style('website.bundle.css') }}
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/env/lib/python3.11/site-packages/jinja2/sandbox.py", line 393, in call
return __context.call(__obj, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/jinja_globals.py", line 118, in include_style
path = bundled_asset(path)
^^^^^^^^^^^^^^^^^^^
File "/home/frappe/frappe-bench/apps/frappe/frappe/utils/jinja_globals.py", line 136, in bundled_asset
path = bundled_assets.get(path) or path
^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'get'
I want to make a note that the site was working fine previously and it broke all of a sudden. I tried bench migrate
command, reinstalling the site using bench new-site --force
but of no use.