From b62b3758a7cd45a08bc55c95601d324f8db1d846 Mon Sep 17 00:00:00 2001 From: Lev Vereshchagin Date: Mon, 13 Dec 2021 19:25:40 +0300 Subject: [PATCH] Update frappe-nginx, erpnext-nginx --- build/frappe-nginx/Dockerfile | 116 +++++++++-------- build/frappe-nginx/docker-entrypoint.sh | 66 ---------- .../frappe-nginx/nginx-default.conf.template | 118 ------------------ build/frappe-nginx/nginx-template.conf | 109 ++++++++++++++++ 4 files changed, 171 insertions(+), 238 deletions(-) delete mode 100755 build/frappe-nginx/docker-entrypoint.sh delete mode 100644 build/frappe-nginx/nginx-default.conf.template create mode 100644 build/frappe-nginx/nginx-template.conf diff --git a/build/frappe-nginx/Dockerfile b/build/frappe-nginx/Dockerfile index d1c9f635..dd66d23e 100644 --- a/build/frappe-nginx/Dockerfile +++ b/build/frappe-nginx/Dockerfile @@ -1,75 +1,83 @@ -# This image uses nvm and same base image as the worker image. -# This is done to ensures that node-sass binary remains common. -# node-sass is required to enable website theme feature used -# by Website Manager role in Frappe Framework -ARG PYTHON_VERSION=3.9 -FROM python:${PYTHON_VERSION}-slim-bullseye as builder - -ARG GIT_REPO=https://github.com/frappe/frappe -ARG GIT_BRANCH=develop - -ENV NODE_VERSION=14.18.1 -ENV NVM_DIR=/root/.nvm -ENV PATH ${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} +# TODO: Add errorpages +FROM node:14-bullseye-slim as base RUN apt-get update \ - && apt-get install --no-install-recommends -y \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ git \ build-essential \ - wget \ - # python2 for version-12 builds - python2 \ + python \ + ca-certificates \ && rm -rf /var/lib/apt/lists/* -# Install nvm with node and yarn -RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash \ - && . ${NVM_DIR}/nvm.sh \ - && nvm install ${NODE_VERSION} \ - && npm install -g yarn \ - && rm -rf ${NVM_DIR}/.cache +WORKDIR /root/frappe-bench +RUN mkdir -p apps sites/assets/frappe -WORKDIR /home/frappe/frappe-bench +ARG FRAPPE_VERSION +RUN git clone --depth 1 -b ${FRAPPE_VERSION} https://github.com/frappe/frappe apps/frappe \ + && echo "frappe" >sites/apps.txt -RUN mkdir -p apps sites/assets/css sites/assets/frappe /var/www/error_pages -RUN echo "frappe" > sites/apps.txt +WORKDIR /root/frappe-bench/apps/frappe -RUN git clone --depth 1 -b ${GIT_BRANCH} ${GIT_REPO} apps/frappe -RUN cd apps/frappe \ - && yarn \ - && yarn run production \ - && yarn install --production=true -RUN git clone --depth 1 https://github.com/frappe/bench /tmp/bench \ - && cp -r /tmp/bench/bench/config/templates /var/www/error_pages +FROM base as frappe_node_modules -RUN cp -R apps/frappe/frappe/public/* sites/assets/frappe \ - && cp -R apps/frappe/node_modules sites/assets/frappe/ +RUN yarn --prod -FROM nginxinc/nginx-unprivileged:latest -USER root +FROM frappe_node_modules as frappe_assets -RUN usermod -u 1000 nginx && groupmod -g 1000 nginx +RUN if [ "$(uname -m)" = "aarch64" ]; then \ + yarn remove svg-sprite \ + && yarn add sass; \ + fi \ + && yarn -COPY --from=builder --chown=1000:1000 /home/frappe/frappe-bench/sites /var/www/html/ -COPY --from=builder --chown=1000:1000 /var/www/error_pages /var/www/ -COPY build/frappe-nginx/nginx-default.conf.template /etc/nginx/conf.d/default.conf.template -COPY build/frappe-nginx/docker-entrypoint.sh / +RUN yarn run production \ + && cp -R frappe/public/* ../../sites/assets/frappe \ + && rm ../../sites/apps.txt -RUN apt-get update \ - && apt-get install --no-install-recommends -y \ - rsync \ - && rm -rf /var/lib/apt/lists/* +# Get rid of development node modules +COPY --from=frappe_node_modules /root/frappe-bench/apps/frappe/node_modules /root/frappe-bench/sites/assets/frappe/ -RUN echo "#!/bin/bash" > /rsync \ - && chmod +x /rsync -RUN mkdir /assets -VOLUME [ "/assets" ] +FROM nginx:1.21-alpine as frappe -RUN chown -R nginx:nginx /assets /etc/nginx/conf.d/ /var/www/html/ +COPY --from=frappe_assets /root/frappe-bench/sites /usr/share/nginx/html +COPY nginx-template.conf / -USER nginx +CMD [ "/bin/sh" , "-c" , "envsubst '${BACKEND} ${SOCKETIO} ${FRAPPE_SITE_NAME_HEADER}' /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'" ] -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["nginx", "-g", "daemon off;"] + +# Next stages are relevant to ERPNext. +# Builds are much more efficient if we reuse Frappe assets and Node modules. + +FROM base as erpnext_node_modules + +ARG ERPNEXT_VERSION +RUN cd ../.. \ + && git clone --depth 1 -b ${ERPNEXT_VERSION} https://github.com/frappe/erpnext apps/erpnext \ + && echo "frappe\nerpnext" >sites/apps.txt + +RUN yarn --cwd ../erpnext --prod + + +FROM erpnext_node_modules as erpnext_assets + +# Reuse development node_modules from frappe_assets +COPY --from=frappe_assets /root/frappe-bench/apps/frappe/node_modules /root/frappe-bench/apps/frappe/node_modules +COPY --from=frappe_assets /root/frappe-bench/apps/frappe/package.json /root/frappe-bench/apps/frappe/yarn.lock /root/frappe-bench/apps/frappe/ + +RUN mkdir -p ../../sites/assets/erpnext +RUN yarn --cwd ../erpnext + +RUN yarn run production --app erpnext \ + && cp -R ../erpnext/erpnext/public/* ../../sites/assets/erpnext \ + && rm ../../sites/apps.txt + +# Get rid of development node modules +COPY --from=erpnext_node_modules /root/frappe-bench/apps/erpnext/node_modules /root/frappe-bench/apps/erpnext/node_modules + + +FROM frappe as erpnext + +COPY --from=erpnext_assets /root/frappe-bench/sites /usr/share/nginx/html diff --git a/build/frappe-nginx/docker-entrypoint.sh b/build/frappe-nginx/docker-entrypoint.sh deleted file mode 100755 index 976c4704..00000000 --- a/build/frappe-nginx/docker-entrypoint.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash -ae - -## Thanks -# https://serverfault.com/a/919212 -## - -rsync -a --delete /var/www/html/assets/* /assets - -/rsync - -# shellcheck disable=SC2012 -touch /var/www/html/sites/.build -r "$(ls -td /assets/* | head -n 1)" - -[[ -z "${FRAPPE_PY}" ]] && FRAPPE_PY='0.0.0.0' - -[[ -z "${FRAPPE_PY_PORT}" ]] && FRAPPE_PY_PORT='8000' - -[[ -z "${FRAPPE_SOCKETIO}" ]] && FRAPPE_SOCKETIO='0.0.0.0' - -[[ -z "${SOCKETIO_PORT}" ]] && SOCKETIO_PORT='9000' - -[[ -z "${HTTP_TIMEOUT}" ]] && HTTP_TIMEOUT='120' - -[[ -z "${UPSTREAM_REAL_IP_ADDRESS}" ]] && UPSTREAM_REAL_IP_ADDRESS='127.0.0.1' - -[[ -z "${UPSTREAM_REAL_IP_RECURSIVE}" ]] && UPSTREAM_REAL_IP_RECURSIVE='off' - -[[ -z "${UPSTREAM_REAL_IP_HEADER}" ]] && UPSTREAM_REAL_IP_HEADER='X-Forwarded-For' - -[[ -z "${FRAPPE_SITE_NAME_HEADER}" ]] && FRAPPE_SITE_NAME_HEADER="\$host" - -[[ -z "${HTTP_HOST}" ]] && HTTP_HOST="\$http_host" - -[[ -z "${WEB_PORT}" ]] && WEB_PORT="8080" - -[[ -z "${SKIP_NGINX_TEMPLATE_GENERATION}" ]] && SKIP_NGINX_TEMPLATE_GENERATION='0' - -if [[ ${SKIP_NGINX_TEMPLATE_GENERATION} == 1 ]]; then - echo "Skipping default NGINX template generation. Please mount your own NGINX config file inside /etc/nginx/conf.d" -else - echo "Generating default template" - # shellcheck disable=SC2016 - envsubst '${FRAPPE_PY} - ${FRAPPE_PY_PORT} - ${FRAPPE_SOCKETIO} - ${SOCKETIO_PORT} - ${HTTP_TIMEOUT} - ${UPSTREAM_REAL_IP_ADDRESS} - ${UPSTREAM_REAL_IP_RECURSIVE} - ${FRAPPE_SITE_NAME_HEADER} - ${HTTP_HOST} - ${UPSTREAM_REAL_IP_HEADER} - ${WEB_PORT}' \ - /etc/nginx/conf.d/default.conf -fi - -echo "Waiting for frappe-python to be available on ${FRAPPE_PY} port ${FRAPPE_PY_PORT}" -# shellcheck disable=SC2016 -timeout 10 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' ${FRAPPE_PY} ${FRAPPE_PY_PORT} -echo "Frappe-python available on ${FRAPPE_PY} port ${FRAPPE_PY_PORT}" -echo "Waiting for frappe-socketio to be available on ${FRAPPE_SOCKETIO} port ${SOCKETIO_PORT}" -# shellcheck disable=SC2016 -timeout 10 bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/$0/$1; do sleep 1; done' ${FRAPPE_SOCKETIO} ${SOCKETIO_PORT} -echo "Frappe-socketio available on ${FRAPPE_SOCKETIO} port ${SOCKETIO_PORT}" - -exec "$@" diff --git a/build/frappe-nginx/nginx-default.conf.template b/build/frappe-nginx/nginx-default.conf.template deleted file mode 100644 index fd7c9719..00000000 --- a/build/frappe-nginx/nginx-default.conf.template +++ /dev/null @@ -1,118 +0,0 @@ -upstream frappe-server { - server ${FRAPPE_PY}:${FRAPPE_PY_PORT} fail_timeout=0; -} - -upstream socketio-server { - server ${FRAPPE_SOCKETIO}:${SOCKETIO_PORT} 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 ${WEB_PORT}; - server_name $http_host; - root /var/www/html; - - 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"; - - # Define ${UPSTREAM_REAL_IP_ADDRESS} as our trusted upstream address, so we will be using - # its ${UPSTREAM_REAL_IP_HEADER} address as our remote address - 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 Origin $proxy_x_forwarded_proto://$http_host; - proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER}; - proxy_set_header Host ${HTTP_HOST}; - - proxy_pass http://socketio-server; - } - - location / { - rewrite ^(.+)/$ $1 permanent; - rewrite ^(.+)/index\.html$ $1 permanent; - rewrite ^(.+)\.html$ $1 permanent; - - location ~ ^/files/.*.(htm|html|svg|xml) { - add_header Content-disposition "attachment"; - try_files /sites/$http_host/public/$uri @webserver; - } - - try_files /sites/$http_host/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 ${HTTP_HOST}; - proxy_set_header X-Use-X-Accel-Redirect True; - proxy_read_timeout ${HTTP_TIMEOUT}; - proxy_redirect off; - - proxy_pass http://frappe-server; - } - - # error pages - error_page 502 /502.html; - location /502.html { - root /var/www/templates; - internal; - } - - # optimizations - sendfile on; - keepalive_timeout 15; - client_max_body_size 50m; - 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 -} diff --git a/build/frappe-nginx/nginx-template.conf b/build/frappe-nginx/nginx-template.conf new file mode 100644 index 00000000..27a1d3ba --- /dev/null +++ b/build/frappe-nginx/nginx-template.conf @@ -0,0 +1,109 @@ +upstream backend-server { + server ${BACKEND} fail_timeout=0; +} + +upstream socketio-server { + server ${SOCKETIO} fail_timeout=0; +} + +server { + listen 80; + 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"; + + 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 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 ^(.+)/$ $1 permanent; + rewrite ^(.+)/index\.html$ $1 permanent; + rewrite ^(.+)\.html$ $1 permanent; + + location ~ ^/files/.*.(htm|html|svg|xml) { + add_header Content-disposition "attachment"; + try_files /sites/$http_host/public/$uri @webserver; + } + + try_files /sites/$http_host/public/$uri @webserver; + } + + location @webserver { + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + 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 120; + proxy_redirect off; + + proxy_pass http://backend-server; + } + + # error pages + error_page 502 /502.html; + location /502.html { + root /var/www/templates; + internal; + } + + # optimizations + sendfile on; + keepalive_timeout 15; + client_max_body_size 50m; + 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 +}