I am setting up a Docker build workflow using apps.json
to include custom apps. Currently, my apps.json
looks like this:
[
{
"url": "https://github.com/frappe/payments",
"branch": "develop"
},
{
"url": "https://github.com/frappe/erpnext",
"branch": "version-15"
},
{
"url": "https://user:password@git.example.com/project/repository.git",
"branch": "main"
}
]
Now, I have a Git repository where the custom apps are organized inside a folder named apps
. Each custom app resides in its own folder under the apps
directory. For example:
repo/
βββ apps/
β βββ custom_app1/
β βββ custom_app2/
I want to include these custom apps in apps.json
for the Docker build workflow. How can I specify the path to these apps within the Git repository and ensure they are properly included during the build?
Did a workaround for this with docker file sharing the below dockerfile which worked for my usecase
# Base Stage
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
ARG WKHTMLTOPDF_VERSION=0.12.6.1-3
ARG WKHTMLTOPDF_DISTRO=bookworm
ARG NODE_VERSION=18.20.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 \
file \
# weasyprint dependencies
libpango-1.0-0 \
libharfbuzz0b \
libpangoft2-1.0-0 \
libpangocairo-1.0-0 \
# For backups
restic \
gpg \
# MariaDB
mariadb-client \
less \
# 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 \
# 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
# Builder Stage
FROM base AS builder
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
wget \
libcairo2-dev \
libpango1.0-dev \
libjpeg-dev \
libgif-dev \
librsvg2-dev \
libpq-dev \
libffi-dev \
liblcms2-dev \
libldap2-dev \
libmariadb-dev \
libsasl2-dev \
libtiff5-dev \
libwebp-dev \
redis-tools \
rlwrap \
tk8.6-dev \
cron \
gcc \
build-essential \
libbz2-dev \
&& rm -rf /var/lib/apt/lists/*
# Include apps.json
ARG APPS_JSON_BASE64
RUN if [ -n "${APPS_JSON_BASE64}" ]; then \
mkdir -p /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \
fi
# Clone monorepo, initialize git, and prepare apps.json as root
ARG MONOREPO_URL
ARG MONOREPO_BRANCH
RUN mkdir -p /tmp/monorepo && \
if [ -n "${MONOREPO_URL}" ]; then \
git clone --branch ${MONOREPO_BRANCH} ${MONOREPO_URL} /tmp/monorepo && \
if [ -d /tmp/monorepo/apps ]; then \
echo "Preparing apps.json..."; \
echo "[]" > /opt/frappe/apps.json; \
for app in /tmp/monorepo/apps/*; do \
if [ -d "$app" ]; then \
app_name=$(basename $app); \
cd "$app" && \
git init && \
git checkout -b main && \
git config user.email "docker@local" && \
git config user.name "Docker Build" && \
git add . && git commit -m "Initial commit for $app_name"; \
# Change ownership to frappe user
chown -R frappe:frappe /tmp/monorepo/apps/$app_name && \
# Append app entry to apps.json
jq --arg url "file:///tmp/monorepo/apps/$app_name" --arg branch "main" \
'. += [{"url": $url, "branch": $branch}]' /opt/frappe/apps.json > /opt/frappe/apps_updated.json && \
mv /opt/frappe/apps_updated.json /opt/frappe/apps.json; \
fi; \
done; \
else \
echo "ERROR: 'apps' directory not found in branch ${MONOREPO_BRANCH}" && exit 1; \
fi; \
fi
# Merge APPS_JSON_BASE64 if provided
ARG APPS_JSON_BASE64
RUN if [ -n "${APPS_JSON_BASE64}" ]; then \
echo "${APPS_JSON_BASE64}" | base64 -d > /tmp/decoded_apps.json && \
jq -s '.[0] + .[1]' /opt/frappe/apps.json /tmp/decoded_apps.json > /opt/frappe/combined_apps.json && \
mv /opt/frappe/combined_apps.json /opt/frappe/apps.json && \
rm /tmp/decoded_apps.json; \
fi
# Ensure /opt/frappe is owned by frappe user
RUN chown -R frappe:frappe /opt/frappe
# Switch to frappe user for bench init and further operations
USER frappe
# Initialize bench with combined apps.json
ARG FRAPPE_BRANCH=version-15
ARG FRAPPE_PATH=https://github.com/frappe/frappe
RUN bench init \
--apps_path=/opt/frappe/apps.json \
--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
# Backend Stage
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" \
]
I clone the list of apps as part of CI job and keep them in repos
directory and then copy them into image. Then you can run bench setup requirements && bench build --production
I tried this approach and when i do bench setup requirements
i got InvalidGitRepositoryError
so went with a workaround to initalise git, modify APPS_JSON_BASE64
then pass that to. bench init
which worked
In my case i had the below setup
repo/
βββ apps/
β βββ custom_app1/
β βββ custom_app2/
Check the complete Containerfile
, it is handling that by removing git at the end.
Hereβs a video which I created for it.
2 Likes