refactor: build only one frappe/erpnext image (#1032)

* ci: skip frappe builds

* refactor: build only one frappe/erpnext image

* fix: lint nginx entrypoint script

* docs: update and organize docs

* docs: fix lint errors

* fix(custom): pass base64 encoded apps json

* ci: update dependabot

* docs: update contributing

* docs: remove info about multi image setup

* fix: initiate empty common_site_config.json

default config has host keys set to localhost
causes connection errors

* docs: add details for pwd volumes

* fix: symlink assets instead of copy

* fix: nginx private files

* ci: skip docker compose v2 install for ubuntu-latest

* fix: organize layers

* feat: allow remove git remote for custom image

* docs: allow remove git remote for custom image

* fix: remove duplicate --apps_path
This commit is contained in:
Revant Nandgaonkar
2023-01-16 04:20:09 +05:30
committed by GitHub
parent f8e43a3114
commit e6088af885
55 changed files with 931 additions and 936 deletions

145
images/custom/Containerfile Normal file
View File

@@ -0,0 +1,145 @@
ARG PYTHON_VERSION=3.10.5
FROM python:${PYTHON_VERSION}-slim-bullseye AS base
ARG WKHTMLTOPDF_VERSION=0.12.6-1
ARG NODE_VERSION=16.18.0
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 \
# 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.2/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.buster_${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 \
# 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
COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template
COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
COPY resources/push_backup.py /usr/local/bin/push-backup
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/*
# 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 REMOVE_GIT_REMOTE
ARG FRAPPE_BRANCH=version-14
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 "$(jq 'del(.db_host, .redis_cache, .redis_queue, .redis_socketio)' sites/common_site_config.json)" \
> sites/common_site_config.json && \
if [ -n "${REMOVE_GIT_REMOTE}" ]; then \
find apps -name .git -type d -prune | xargs -i git --git-dir {} remote rm upstream; \
fi
WORKDIR /home/frappe/frappe-bench
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" \
]
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" \
]

View File

@@ -1,62 +0,0 @@
FROM frappe/bench:latest as assets_builder
ARG FRAPPE_VERSION
ARG FRAPPE_REPO=https://github.com/frappe/frappe
ARG PYTHON_VERSION
ARG NODE_VERSION
ENV NVM_DIR=/home/frappe/.nvm
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
RUN PYENV_VERSION=${PYTHON_VERSION} bench init --version=${FRAPPE_VERSION} --frappe-path=${FRAPPE_REPO} --skip-redis-config-generation --verbose --skip-assets /home/frappe/frappe-bench
WORKDIR /home/frappe/frappe-bench
FROM assets_builder as frappe_assets
RUN bench setup requirements \
&& if [ -z "${FRAPPE_VERSION##*v14*}" ] || [ "$FRAPPE_VERSION" = "develop" ]; then \
export BUILD_OPTS="--production";\
fi \
&& FRAPPE_ENV=production bench build --verbose --hard-link ${BUILD_OPTS}
FROM assets_builder as erpnext_assets
ARG ERPNEXT_VERSION
ARG ERPNEXT_REPO=https://github.com/frappe/erpnext
RUN bench get-app --branch=${ERPNEXT_VERSION} --skip-assets --resolve-deps erpnext ${ERPNEXT_REPO}\
&& if [ -z "${ERPNEXT_VERSION##*v14*}" ] || [ "$ERPNEXT_VERSION" = "develop" ]; then \
export BUILD_OPTS="--production"; \
fi \
&& FRAPPE_ENV=production bench build --verbose --hard-link ${BUILD_OPTS}
FROM alpine/git as bench
# Error pages
ARG BENCH_REPO=https://github.com/frappe/bench
RUN git clone --depth 1 ${BENCH_REPO} /tmp/bench \
&& mkdir /out \
&& mv /tmp/bench/bench/config/templates/502.html /out \
&& touch /out/.build
FROM nginxinc/nginx-unprivileged:1.23.3-alpine as frappe
# Set default ENV variables for backwards compatibility
ENV PROXY_READ_TIMOUT=120
ENV CLIENT_MAX_BODY_SIZE=50m
# https://github.com/nginxinc/docker-nginx-unprivileged/blob/main/stable/alpine/20-envsubst-on-templates.sh
COPY nginx-template.conf /etc/nginx/templates/default.conf.template
# https://github.com/nginxinc/docker-nginx-unprivileged/blob/main/stable/alpine/docker-entrypoint.sh
COPY entrypoint.sh /docker-entrypoint.d/frappe-entrypoint.sh
COPY --from=bench /out /usr/share/nginx/html/
COPY --from=frappe_assets /home/frappe/frappe-bench/sites/assets /usr/share/nginx/html/assets
USER 1000
FROM frappe as erpnext
COPY --from=erpnext_assets /home/frappe/frappe-bench/sites/assets /usr/share/nginx/html/assets

View File

@@ -1,9 +0,0 @@
#!/bin/sh
set -e
# Update timestamp for ".build" file to enable caching assets:
# https://github.com/frappe/frappe/blob/52d8e6d952130eea64a9990b9fd5b1f6877be1b7/frappe/utils/__init__.py#L799-L805
if [ -d /usr/share/nginx/html/sites ]; then
touch /usr/share/nginx/html/sites/.build -r /usr/share/nginx/html/.build
fi

View File

@@ -1,120 +0,0 @@
upstream backend-server {
server ${BACKEND} fail_timeout=0;
}
upstream socketio-server {
server ${SOCKETIO} fail_timeout=0;
}
# Parse the X-Forwarded-Proto header - if set - defaulting to $scheme.
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
default $scheme;
https https;
}
server {
listen 8080;
server_name $http_host;
root /usr/share/nginx/html;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
add_header X-Frame-Options "SAMEORIGIN";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin";
set_real_ip_from ${UPSTREAM_REAL_IP_ADDRESS};
real_ip_header ${UPSTREAM_REAL_IP_HEADER};
real_ip_recursive ${UPSTREAM_REAL_IP_RECURSIVE};
location /assets {
try_files $uri =404;
}
location ~ ^/protected/(.*) {
internal;
try_files /sites/$http_host/$1 =404;
}
location /socket.io {
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER};
proxy_set_header Origin $scheme://$http_host;
proxy_set_header Host $host;
proxy_pass http://socketio-server;
}
location / {
rewrite ^(.+)/$ $proxy_x_forwarded_proto://$http_host$1 permanent;
rewrite ^(.+)/index\.html$ $proxy_x_forwarded_proto://$http_host$1 permanent;
rewrite ^(.+)\.html$ $proxy_x_forwarded_proto://$http_host$1 permanent;
location ~ ^/files/.*.(htm|html|svg|xml) {
add_header Content-disposition "attachment";
try_files /sites/${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver;
}
try_files /sites/${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver;
}
location @webserver {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER};
proxy_set_header Host $host;
proxy_set_header X-Use-X-Accel-Redirect True;
proxy_read_timeout ${PROXY_READ_TIMOUT};
proxy_redirect off;
proxy_pass http://backend-server;
}
# error pages
error_page 502 /502.html;
location /502.html {
internal;
}
# optimizations
sendfile on;
keepalive_timeout 15;
client_max_body_size ${CLIENT_MAX_BODY_SIZE};
client_body_buffer_size 16K;
client_header_buffer_size 1k;
# enable gzip compression
# based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
gzip on;
gzip_http_version 1.1;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/atom+xml
application/javascript
application/json
application/rss+xml
application/vnd.ms-fontobject
application/x-font-ttf
application/font-woff
application/x-web-app-manifest+json
application/xhtml+xml
application/xml
font/opentype
image/svg+xml
image/x-icon
text/css
text/plain
text/x-component;
# text/html is always compressed by HttpGzipModule
}

View File

@@ -0,0 +1,132 @@
ARG PYTHON_VERSION=3.10.5
FROM python:${PYTHON_VERSION}-slim-bullseye AS base
ARG WKHTMLTOPDF_VERSION=0.12.6-1
ARG NODE_VERSION=16.18.0
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 \
# 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.2/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.buster_${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 \
# 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
COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template
COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh
COPY resources/push_backup.py /usr/local/bin/push-backup
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/*
USER frappe
ARG FRAPPE_BRANCH=version-14
ARG FRAPPE_PATH=https://github.com/frappe/frappe
ARG ERPNEXT_REPO=https://github.com/frappe/erpnext
ARG ERPNEXT_BRANCH=version-14
RUN bench init \
--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 && \
bench get-app --branch=${ERPNEXT_BRANCH} --resolve-deps erpnext ${ERPNEXT_REPO} && \
echo "$(jq 'del(.db_host, .redis_cache, .redis_queue, .redis_socketio)' sites/common_site_config.json)" \
> sites/common_site_config.json
FROM base as erpnext
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" \
]
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" \
]

View File

@@ -1,34 +0,0 @@
FROM alpine/git as builder
ARG FRAPPE_VERSION
ARG FRAPPE_REPO=https://github.com/frappe/frappe
RUN apk add -U jq
RUN git clone --depth 1 -b ${FRAPPE_VERSION} ${FRAPPE_REPO} /opt/frappe
RUN jq --argjson dependencies "$(jq '.dependencies | INDEX( "express", "redis", "socket.io", "superagent" ) as $keep | \
del( \
. | objects | \
.[ \
keys_unsorted[] | \
select( $keep[ . ] | not ) \
] \
)' /opt/frappe/package.json)" '.dependencies = $dependencies | del(.scripts.prepare)' /opt/frappe/package.json > /opt/frappe/dependencies.json && \
mv /opt/frappe/dependencies.json /opt/frappe/package.json
# NodeJS LTS
FROM node:18-alpine
RUN addgroup -S frappe \
&& adduser -S frappe -G frappe
USER frappe
WORKDIR /home/frappe/frappe-bench
RUN mkdir -p sites apps/frappe
COPY --chown=frappe:frappe --from=builder /opt/frappe/package.json /opt/frappe/socketio.js /opt/frappe/node_utils.js apps/frappe/
RUN cd apps/frappe \
&& npm install --omit=dev
WORKDIR /home/frappe/frappe-bench/sites
CMD [ "node", "/home/frappe/frappe-bench/apps/frappe/socketio.js" ]

View File

@@ -1,146 +0,0 @@
# syntax=docker/dockerfile:1.3
ARG PYTHON_VERSION
FROM python:${PYTHON_VERSION}-slim-bullseye as base
RUN apt-get update \
&& apt-get install --no-install-recommends -y \
# Postgres
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -ms /bin/bash frappe
USER frappe
RUN mkdir -p /home/frappe/frappe-bench/apps /home/frappe/frappe-bench/logs /home/frappe/frappe-bench/sites /home/frappe/frappe-bench/config
WORKDIR /home/frappe/frappe-bench
USER root
RUN pip install --no-cache-dir -U pip wheel \
&& python -m venv env \
&& env/bin/pip install --no-cache-dir -U pip wheel
COPY install-app.sh /usr/local/bin/install-app
FROM base as build_deps
RUN apt-get update \
&& apt-get install --no-install-recommends -y \
# Install git here because it is not required in production
git \
# gcc and g++ are required for building different packages across different versions
# of Frappe and ERPNext and also on different platforms (for example, linux/arm64).
# It is safe to install build deps even if they are not required
# because they won't be included in final images.
gcc \
g++ \
# Make is required to build wheels of ERPNext deps in develop branch for linux/arm64
make \
&& rm -rf /var/lib/apt/lists/*
FROM build_deps as frappe_builder
ARG FRAPPE_VERSION
ARG FRAPPE_REPO=https://github.com/frappe/frappe
RUN --mount=type=cache,target=/root/.cache/pip \
git clone --depth 1 -b ${FRAPPE_VERSION} ${FRAPPE_REPO} apps/frappe \
&& install-app frappe \
&& env/bin/pip install -U gevent \
# Link Frappe's node_modules/ to make Website Theme work
&& mkdir -p /home/frappe/frappe-bench/sites/assets/frappe/node_modules \
&& ln -s /home/frappe/frappe-bench/sites/assets/frappe/node_modules /home/frappe/frappe-bench/apps/frappe/node_modules
FROM frappe_builder as erpnext_builder
ARG PAYMENTS_VERSION=develop
ARG PAYMENTS_REPO=https://github.com/frappe/payments
ARG ERPNEXT_VERSION
ARG ERPNEXT_REPO=https://github.com/frappe/erpnext
RUN --mount=type=cache,target=/root/.cache/pip \
if [ -z "${ERPNEXT_VERSION##*v14*}" ] || [ "$ERPNEXT_VERSION" = "develop" ]; then \
git clone --depth 1 -b ${PAYMENTS_VERSION} ${PAYMENTS_REPO} apps/payments && install-app payments; \
fi \
&& git clone --depth 1 -b ${ERPNEXT_VERSION} ${ERPNEXT_REPO} apps/erpnext \
&& install-app erpnext
FROM base as configured_base
ARG WKHTMLTOPDF_VERSION=0.12.6-1
ARG NODE_VERSION
ENV NVM_DIR=/home/frappe/.nvm
ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH}
RUN apt-get update \
# Setup Node lists
&& apt-get install --no-install-recommends -y curl git \
# NodeJS with NVM
&& mkdir -p ${NVM_DIR} \
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/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"' >>~/.bashrc \
&& echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> ~/.bashrc \
&& echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> ~/.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.buster_${ARCH}.deb \
&& curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \
&& apt-get install -y ./$downloaded_file \
&& rm $downloaded_file \
# Cleanup
&& apt-get purge -y --auto-remove curl \
&& apt-get update \
&& apt-get install --no-install-recommends -y \
# MariaDB
mariadb-client \
# Postgres
postgresql-client \
# For healthcheck
wait-for-it \
jq \
# Clean up
&& rm -rf /var/lib/apt/lists/*
COPY pretend-bench.sh /usr/local/bin/bench
COPY push_backup.py /usr/local/bin/push-backup
COPY configure.py patched_bench_helper.py /usr/local/bin/
COPY gevent_patch.py /opt/patches/
WORKDIR /home/frappe/frappe-bench/sites
CMD [ "/home/frappe/frappe-bench/env/bin/gunicorn", \
"--bind=0.0.0.0:8000", \
"--threads=4", \
"--workers=2", \
"--worker-class=gthread", \
"--worker-tmp-dir=/dev/shm", \
"--timeout=120", \
"--preload", \
"frappe.app:application" \
]
FROM configured_base as frappe
COPY --from=frappe_builder /home/frappe/frappe-bench/apps/frappe /home/frappe/frappe-bench/apps/frappe
COPY --from=frappe_builder /home/frappe/frappe-bench/env /home/frappe/frappe-bench/env
COPY --from=frappe_builder /home/frappe/frappe-bench/sites/apps.txt /home/frappe/frappe-bench/sites/
USER frappe
# Split frappe and erpnext to reduce image size (because of frappe-bench/env/ directory)
FROM configured_base as erpnext
COPY --from=erpnext_builder --chown=frappe:frappe /home/frappe/frappe-bench/apps /home/frappe/frappe-bench/apps
COPY --from=erpnext_builder --chown=frappe:frappe /home/frappe/frappe-bench/env /home/frappe/frappe-bench/env
COPY --from=erpnext_builder --chown=frappe:frappe /home/frappe/frappe-bench/sites/apps.txt /home/frappe/frappe-bench/sites/
USER frappe

View File

@@ -1,56 +0,0 @@
#!/usr/local/bin/python
from __future__ import annotations
import json
import os
from typing import Any, TypeVar
def update_config(**values: Any):
fname = "common_site_config.json"
if not os.path.exists(fname):
with open(fname, "a") as f:
json.dump({}, f)
with open(fname, "r+") as f:
config: dict[str, Any] = json.load(f)
config.update(values)
f.seek(0)
f.truncate()
json.dump(config, f)
_T = TypeVar("_T")
def env(name: str, type_: type[_T] = str) -> _T:
value = os.getenv(name)
if not value:
raise RuntimeError(f'Required environment variable "{name}" not set')
try:
value = type_(value)
except Exception:
raise RuntimeError(
f'Cannot convert environment variable "{name}" to type "{type_}"'
)
return value
def generate_redis_url(url: str):
return f"redis://{url}"
def main() -> int:
update_config(
db_host=env("DB_HOST"),
db_port=env("DB_PORT", int),
redis_cache=generate_redis_url(env("REDIS_CACHE")),
redis_queue=generate_redis_url(env("REDIS_QUEUE")),
redis_socketio=generate_redis_url(env("REDIS_SOCKETIO")),
socketio_port=env("SOCKETIO_PORT", int),
)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,3 +0,0 @@
import gevent.monkey
gevent.monkey.patch_all()

View File

@@ -1,11 +0,0 @@
#!/bin/bash
set -e
set -x
APP=$1
cd /home/frappe/frappe-bench
env/bin/pip install -e "apps/$APP"
echo "$APP" >>sites/apps.txt

View File

@@ -1,48 +0,0 @@
from __future__ import annotations
import click
import click.exceptions
import frappe.app
import frappe.database.db_manager
import frappe.utils.bench_helper
def patch_database_creator():
"""
We need to interrupt Frappe site database creation to monkeypatch
functions that resolve host for user that owns site database.
In frappe_docker this was implemented in "new" command:
https://github.com/frappe/frappe_docker/blob/c808ad1767feaf793a2d14541ac0f4d9cbab45b3/build/frappe-worker/commands/new.py#L87
"""
frappe.database.db_manager.DbManager.get_current_host = lambda self: "%"
def patch_click_usage_error():
bits: tuple[str, ...] = (
click.style(
"Only Frappe framework bench commands are available in container setup.",
fg="yellow",
bold=True,
),
"https://frappeframework.com/docs/v13/user/en/bench/frappe-commands",
)
notice = "\n".join(bits)
def format_message(self: click.exceptions.UsageError):
if "No such command" in self.message:
return f"{notice}\n\n{self.message}"
return self.message
click.exceptions.UsageError.format_message = format_message
def main() -> int:
patch_database_creator()
patch_click_usage_error()
frappe.utils.bench_helper.main()
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,5 +0,0 @@
#!/bin/bash
set -e
# shellcheck disable=SC2068
~/frappe-bench/env/bin/python /usr/local/bin/patched_bench_helper.py frappe $@

View File

@@ -1,117 +0,0 @@
#!/home/frappe/frappe-bench/env/bin/python
from __future__ import annotations
import argparse
import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any, List, cast
import boto3
import frappe
from frappe.utils.backups import BackupGenerator
if TYPE_CHECKING:
from mypy_boto3_s3.service_resource import _Bucket
class Arguments(argparse.Namespace):
site: str
bucket: str
region_name: str
endpoint_url: str
aws_access_key_id: str
aws_secret_access_key: str
bucket_directory: str
def _get_files_from_previous_backup(site_name: str) -> list[Path]:
frappe.connect(site_name)
conf = cast(Any, frappe.conf)
backup_generator = BackupGenerator(
db_name=conf.db_name,
user=conf.db_name,
password=conf.db_password,
db_host=frappe.db.host,
db_port=frappe.db.port,
db_type=conf.db_type,
)
recent_backup_files = backup_generator.get_recent_backup(24)
frappe.destroy()
return [Path(f) for f in recent_backup_files if f]
def get_files_from_previous_backup(site_name: str) -> list[Path]:
files = _get_files_from_previous_backup(site_name)
if not files:
print("No backup found that was taken <24 hours ago.")
return files
def get_bucket(args: Arguments) -> _Bucket:
return boto3.resource(
service_name="s3",
endpoint_url=args.endpoint_url,
region_name=args.region_name,
aws_access_key_id=args.aws_access_key_id,
aws_secret_access_key=args.aws_secret_access_key,
).Bucket(args.bucket)
def upload_file(
path: Path, site_name: str, bucket: _Bucket, bucket_directory: str = None
) -> None:
filename = str(path.absolute())
key = str(Path(site_name) / path.name)
if bucket_directory:
key = bucket_directory + "/" + key
print(f"Uploading {key}")
bucket.upload_file(Filename=filename, Key=key)
os.remove(path)
def push_backup(args: Arguments) -> None:
"""Get latest backup files using Frappe utils, push them to S3 and remove local copy"""
files = get_files_from_previous_backup(args.site)
bucket = get_bucket(args)
for path in files:
upload_file(
path=path,
site_name=args.site,
bucket=bucket,
bucket_directory=args.bucket_directory,
)
print("Done!")
def parse_args(args: list[str]) -> Arguments:
parser = argparse.ArgumentParser()
parser.add_argument("--site", required=True)
parser.add_argument("--bucket", required=True)
parser.add_argument("--region-name", required=True)
parser.add_argument("--endpoint-url", required=True)
# Looking for default AWS credentials variables
parser.add_argument(
"--aws-access-key-id", required=True, default=os.getenv("AWS_ACCESS_KEY_ID")
)
parser.add_argument(
"--aws-secret-access-key",
required=True,
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
)
parser.add_argument("--bucket-directory")
return parser.parse_args(args, namespace=Arguments())
def main(args: list[str]) -> int:
push_backup(parse_args(args))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))