From db464afc34581ecdd16744667209ae2f25edcc61 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 1 Sep 2026 10:39:37 +0000 Subject: [PATCH] Run docker container as non-root user --- contrib/container/Dockerfile | 24 ++++++++++++++++++++++++ contrib/container/init.sh | 9 +++++++++ docs/docs/start/docker.md | 3 +++ docs/docs/start/docker_install.md | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/contrib/container/Dockerfile b/contrib/container/Dockerfile index 5864a68627e1..6e7f1e5468b9 100644 --- a/contrib/container/Dockerfile +++ b/contrib/container/Dockerfile @@ -63,6 +63,8 @@ LABEL org.opencontainers.image.vendor="inventree" \ # Install basic system level packages RUN apt-get update && apt-get install -y --no-install-recommends \ git gettext libldap2 wget curl ssh \ + # Used by the entrypoint script to drop root privileges in the production image + gosu \ # Weasyprint requirements : https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#alpine-3-12 weasyprint libpango-1.0-0 libcairo2 poppler-utils \ # Database client libraries @@ -83,6 +85,11 @@ EXPOSE 8000 # Fix invoke command path for InvenTree environment check RUN python -m pip install --no-cache-dir -U invoke +# Dedicated non-root user for running InvenTree in the 'production' image. +# Fixed uid/gid so that operators can chown their host-mounted data volume to match. +RUN groupadd --gid 1000 inventree \ + && useradd --uid 1000 --gid inventree --home-dir ${INVENTREE_HOME} --shell /bin/bash inventree + RUN mkdir -p ${INVENTREE_HOME} WORKDIR ${INVENTREE_HOME} @@ -132,6 +139,10 @@ FROM inventree_base AS production ENV INVENTREE_DEBUG=False +# Container starts as root, but init.sh drops to this user before launching the +# CMD (fixing up ownership of the mounted data volume along the way) - see init.sh +ENV INVENTREE_RUN_AS_USER="inventree" + # As .git directory is not available in production image, we pass the commit information via ENV ENV INVENTREE_COMMIT_HASH="${commit_hash}" ENV INVENTREE_COMMIT_DATE="${commit_date}" @@ -141,6 +152,11 @@ COPY src/backend/InvenTree ${INVENTREE_BACKEND_DIR}/InvenTree COPY src/backend/requirements.txt ${INVENTREE_BACKEND_DIR}/requirements.txt # Copy compiled dependencies from prebuild image +# PYTHONUSERBASE must be pinned explicitly: pip installed these packages as --user +# under root, and Python resolves the user site-packages dir relative to the *running* +# user's home directory unless overridden - without this, imports break once we +# switch away from root below. +ENV PYTHONUSERBASE=/root/.local ENV PATH=/root/.local/bin:$PATH COPY --from=builder_stage ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web ${INVENTREE_BACKEND_DIR}/InvenTree/web/static/web @@ -153,6 +169,14 @@ RUN bash -c "cd '${INVENTREE_HOME}' && invoke int.backend-compilemessages" RUN pip-licenses --format=json --with-license-file --no-license-path > "${INVENTREE_BACKEND_DIR}/InvenTree/InvenTree/licenses.txt" \ && test -s "${INVENTREE_BACKEND_DIR}/InvenTree/InvenTree/licenses.txt" +# Fix up ownership of the application code/static files, and open up read access to +# the --user installed python packages under /root/.local (not readable by other users +# by default) - init.sh drops to the non-root 'inventree' user before launch, and +# additionally fixes up ownership of the (host-mounted) data volume at that point. +RUN chown -R inventree:inventree ${INVENTREE_HOME} \ + && chmod 755 /root \ + && chmod -R o+rX /root/.local + # Launch the production server CMD ["sh", "-c", "exec gunicorn -c ./gunicorn.conf.py InvenTree.wsgi -b ${INVENTREE_WEB_ADDR}:${INVENTREE_WEB_PORT} --chdir ${INVENTREE_BACKEND_DIR}/InvenTree"] diff --git a/contrib/container/init.sh b/contrib/container/init.sh index 4883c49b0b5a..b23b32cfb790 100644 --- a/contrib/container/init.sh +++ b/contrib/container/init.sh @@ -55,5 +55,14 @@ fi cd ${INVENTREE_HOME} +# If requested (production image only), drop root privileges before launching the CMD. +# This lets the external data volume be bind-mounted with any host ownership - +# we fix it up here rather than requiring the operator to chown it in advance. +if [[ "$(id -u)" = "0" && -n "$INVENTREE_RUN_AS_USER" ]]; then + echo "Setting ownership of ${INVENTREE_DATA_DIR} to '${INVENTREE_RUN_AS_USER}'" + chown -R "${INVENTREE_RUN_AS_USER}:${INVENTREE_RUN_AS_USER}" "${INVENTREE_DATA_DIR}" + exec gosu "${INVENTREE_RUN_AS_USER}" "$@" +fi + # Launch the CMD *after* the ENTRYPOINT completes exec "$@" diff --git a/docs/docs/start/docker.md b/docs/docs/start/docker.md index a4cdbe3fc1c7..8bc00a6a6699 100644 --- a/docs/docs/start/docker.md +++ b/docs/docs/start/docker.md @@ -115,6 +115,9 @@ InvenTree stores any persistent data (e.g. uploaded media files, database data, !!! info "Data Directory" Make sure you change the path to the local directory where you want persistent data to be stored. +!!! info "Directory Ownership" + The `inventree-server` and `inventree-worker` containers run application code as a non-root user (`uid=1000`, `gid=1000`), rather than as `root`. On startup, each container automatically takes ownership of the directory mapped to `INVENTREE_EXT_VOLUME` (equivalent to `chown -R 1000:1000`) before dropping root privileges - no manual setup of this directory is required. + ### Database Connection The `inventree-db` container is configured to use the `postgres:{{ config.extra.docker_postgres_version }}` docker image. diff --git a/docs/docs/start/docker_install.md b/docs/docs/start/docker_install.md index d36e0f084a9f..426e11271027 100644 --- a/docs/docs/start/docker_install.md +++ b/docs/docs/start/docker_install.md @@ -55,7 +55,7 @@ Download these files to a directory on your local machine. The first step is to edit the environment variables, located in the `.env` file. !!! warning "External Volume" - You must define the `INVENTREE_EXT_VOLUME` variable - this must point to a directory *on your local machine* where persistent data is to be stored. + You must define the `INVENTREE_EXT_VOLUME` variable - this must point to a directory *on your local machine* where persistent data is to be stored. See [directory ownership](./docker.md#data-volume) for notes on how this directory's permissions are managed. !!! warning "Database Credentials" You must also define the database username (`INVENTREE_DB_USER`) and password (`INVENTREE_DB_PASSWORD`). You should ensure they are changed from the default values for added security