mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-28 12:36:33 +00:00
* docker: prepare plugin volume mountpoint Create src/plugin_packages in the development and production runtime image stages so a fresh Docker named volume is initialized with Etherpad user ownership instead of root-only ownership. Fixes #8026 * tests: align docker plugin volume regression test with project style Replace CommonJS imports with Node.js module imports and reformat the test to match the project's coding style. This change is purely stylistic and does not alter the regression test's behavior. * tests: make docker plugin volume test less brittle Match Dockerfile instructions with regexes instead of exact substrings so the regression test validates the required behavior without rejecting harmless formatting or equivalent command changes. * docker: explain the plugin_packages mountpoint, drop the Dockerfile-text test The reason the two `mkdir`s exist is not obvious from the Dockerfile, so state it where the next reader will be: a named volume inherits the mountpoint's ownership from the image, and USER is already etherpad at that point. Removes dockerfilePluginVolume.ts. It asserted that the Dockerfile *text* contains `mkdir -p ./src/plugin_packages` after the `COPY ./src` line — it fails when the Dockerfile is refactored (a `.gitkeep`, a `VOLUME`, a different order all keep the behaviour intact) and it cannot fail when the actual bug returns, since it never inspects a built image. The build-test job already builds both runtime stages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
253 lines
9.9 KiB
Docker
253 lines
9.9 KiB
Docker
# Etherpad Dockerfile
|
|
#
|
|
# https://github.com/ether/etherpad
|
|
#
|
|
# Author: muxator
|
|
# Set to "copy" for builds without git metadata (source tarballs, some CI):
|
|
# docker build --build-arg BUILD_ENV=copy .
|
|
ARG BUILD_ENV=git
|
|
|
|
# NOTE: this intentionally lags the "packageManager" pin in package.json. pnpm
|
|
# 11.1.x enforces the minimum-release-age supply-chain policy during install,
|
|
# which the frozen-lockfile Docker build can't satisfy, so the image stays on
|
|
# 11.0.x. The version gap is made harmless by pnpm_config_pm_on_fail=ignore in
|
|
# the build stage below — see ether/etherpad#7911.
|
|
ARG PnpmVersion=11.0.6
|
|
|
|
FROM node:24-alpine AS adminbuild
|
|
# Install pnpm directly via npm (rather than via corepack) so the same
|
|
# image recipe keeps working on Node 25+, where corepack has been
|
|
# dropped from the distribution. The node:24-alpine image also bundles
|
|
# yarn; remove it first to avoid leaving an unused binary on PATH.
|
|
# Drop bundled npm afterwards — its older transitives (picomatch,
|
|
# brace-expansion) carry CVEs we don't otherwise need.
|
|
RUN rm -f /usr/local/bin/yarn /usr/local/bin/yarnpkg && \
|
|
npm install -g pnpm@${PnpmVersion} && \
|
|
rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
|
|
WORKDIR /opt/etherpad-lite
|
|
COPY . .
|
|
RUN pnpm install
|
|
RUN pnpm run build:ui
|
|
|
|
|
|
FROM node:24-alpine AS build
|
|
LABEL maintainer="Etherpad team, https://github.com/ether/etherpad"
|
|
|
|
# The image's pnpm intentionally lags the "packageManager" pin (see the ARG
|
|
# note above). pnpm would otherwise try to self-provision the pinned version on
|
|
# invocation — including the informational `pnpm --version` probe Etherpad runs
|
|
# at startup — which fails closed with no network and breaks air-gapped boots
|
|
# (ether/etherpad#7911). pm_on_fail=ignore makes pnpm use the installed version
|
|
# instead. Inherited by the development and production runtime stages, so it
|
|
# also covers the updater's pnpm-on-PATH check and ad-hoc `pnpm` in an exec
|
|
# shell. It does not change which pnpm runs the build-time install (still the
|
|
# installed 11.0.x), so the frozen-lockfile build is unaffected.
|
|
ENV pnpm_config_pm_on_fail=ignore
|
|
|
|
# Set these arguments when building the image from behind a proxy
|
|
ARG http_proxy=
|
|
ARG https_proxy=
|
|
ARG no_proxy=
|
|
|
|
ARG TIMEZONE=
|
|
|
|
RUN \
|
|
[ -z "${TIMEZONE}" ] || { \
|
|
apk add --no-cache tzdata && \
|
|
cp /usr/share/zoneinfo/${TIMEZONE} /etc/localtime && \
|
|
echo "${TIMEZONE}" > /etc/timezone; \
|
|
}
|
|
ENV TIMEZONE=${TIMEZONE}
|
|
|
|
# Control the configuration file to be copied into the container.
|
|
ARG SETTINGS=./settings.json.docker
|
|
|
|
# plugins to install while building the container. By default no plugins are
|
|
# installed.
|
|
# If given a value, it has to be a space-separated, quoted list of plugin names.
|
|
#
|
|
# EXAMPLE:
|
|
# ETHERPAD_PLUGINS="ep_codepad ep_author_neat"
|
|
ARG ETHERPAD_PLUGINS=
|
|
|
|
# local plugins to install while building the container. By default no plugins are
|
|
# installed.
|
|
# If given a value, it has to be a space-separated, quoted list of plugin names.
|
|
#
|
|
# EXAMPLE:
|
|
# ETHERPAD_LOCAL_PLUGINS="../ep_my_plugin ../ep_another_plugin"
|
|
ARG ETHERPAD_LOCAL_PLUGINS=
|
|
|
|
# github plugins to install while building the container. By default no plugins are
|
|
# installed.
|
|
# If given a value, it has to be a space-separated, quoted list of plugin names.
|
|
#
|
|
# EXAMPLE:
|
|
# ETHERPAD_GITHUB_PLUGINS="ether/ep_plugin"
|
|
ARG ETHERPAD_GITHUB_PLUGINS=
|
|
|
|
# Control whether libreoffice will be installed, enabling exports to DOC/DOCX/PDF/ODT formats.
|
|
# By default, it is not installed.
|
|
# If given any value, libreoffice will be installed.
|
|
#
|
|
# EXAMPLE:
|
|
# INSTALL_LIBREOFFICE=true
|
|
ARG INSTALL_SOFFICE=
|
|
|
|
# Install dependencies required for modifying access.
|
|
RUN apk add --no-cache shadow bash
|
|
# Follow the principle of least privilege: run as unprivileged user.
|
|
#
|
|
# Running as non-root enables running this image in platforms like OpenShift
|
|
# that do not allow images running as root.
|
|
#
|
|
# If any of the following args are set to the empty string, default
|
|
# values will be chosen.
|
|
ARG EP_HOME=
|
|
ARG EP_UID=5001
|
|
ARG EP_GID=0
|
|
ARG EP_SHELL=
|
|
|
|
RUN groupadd --system ${EP_GID:+--gid "${EP_GID}" --non-unique} etherpad && \
|
|
useradd --system ${EP_UID:+--uid "${EP_UID}" --non-unique} --gid etherpad \
|
|
${EP_HOME:+--home-dir "${EP_HOME}"} --create-home \
|
|
${EP_SHELL:+--shell "${EP_SHELL}"} etherpad
|
|
|
|
ARG EP_DIR=/opt/etherpad-lite
|
|
RUN mkdir -p "${EP_DIR}" && chown etherpad:etherpad "${EP_DIR}"
|
|
|
|
# Install pnpm directly via npm (rather than via corepack) so the same
|
|
# recipe stays valid on Node 25+, which dropped corepack. Then drop
|
|
# both npm and the pre-bundled yarn binary to keep the runtime image
|
|
# free of unused tooling and known-CVE transitives.
|
|
#
|
|
# the mkdir is needed for configuration of openjdk-11-jre-headless, see
|
|
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=863199
|
|
RUN \
|
|
mkdir -p /usr/share/man/man1 && \
|
|
rm -f /usr/local/bin/yarn /usr/local/bin/yarnpkg && \
|
|
npm install -g pnpm@${PnpmVersion} && \
|
|
rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx && \
|
|
apk update && apk upgrade && \
|
|
apk add --no-cache \
|
|
ca-certificates \
|
|
git \
|
|
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common} && \
|
|
rm -rf /var/cache/apk/*
|
|
|
|
USER etherpad
|
|
|
|
WORKDIR "${EP_DIR}"
|
|
|
|
# etherpads version feature requires this. Only copy what is really needed
|
|
COPY --chown=etherpad:etherpad ${SETTINGS} ./settings.json
|
|
COPY --chown=etherpad:etherpad ./var ./var
|
|
COPY --chown=etherpad:etherpad ./bin ./bin
|
|
COPY --chown=etherpad:etherpad ./pnpm-workspace.yaml ./package.json ./
|
|
|
|
|
|
|
|
FROM build AS build_git
|
|
# When checked out as a git submodule, .git is a file (gitlink) instead of a
|
|
# directory, so .git/HEAD and .git/refs do not exist. Copy the whole .git
|
|
# entry (the .dockerignore already strips the heavy objects) and normalise it
|
|
# with a shell step so the build succeeds in both cases and across builders
|
|
# (Docker, buildah, podman). See #6663 and containers/buildah#5742.
|
|
ONBUILD COPY --chown=etherpad:etherpad ./.git ./.git
|
|
ONBUILD RUN if [ -f .git ]; then rm .git; fi
|
|
|
|
FROM build AS build_copy
|
|
|
|
|
|
|
|
|
|
FROM build_${BUILD_ENV} AS development
|
|
|
|
ARG ETHERPAD_PLUGINS=
|
|
ARG ETHERPAD_LOCAL_PLUGINS=
|
|
ARG ETHERPAD_LOCAL_PLUGINS_ENV=
|
|
ARG ETHERPAD_GITHUB_PLUGINS=
|
|
|
|
COPY --chown=etherpad:etherpad ./src/ ./src/
|
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/templates/admin ./src/templates/admin
|
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/oidc ./src/static/oidc
|
|
# docker-compose mounts a named volume over src/plugin_packages. Docker seeds a
|
|
# fresh named volume from the mountpoint in the image, so the directory has to
|
|
# exist here (owned by etherpad, since USER is already etherpad) — otherwise
|
|
# Docker creates it root:root and plugin installs cannot write install.lock.
|
|
# See ether/etherpad#8026.
|
|
RUN mkdir -p ./src/plugin_packages
|
|
|
|
COPY --chown=etherpad:etherpad ./local_plugin[s] ./local_plugins/
|
|
|
|
RUN bash -c ./bin/installLocalPlugins.sh
|
|
|
|
RUN bin/installDeps.sh && \
|
|
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_GITHUB_PLUGINS}" ]; then \
|
|
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_GITHUB_PLUGINS:+--github ${ETHERPAD_GITHUB_PLUGINS}}; \
|
|
fi
|
|
|
|
|
|
FROM build_${BUILD_ENV} AS production
|
|
|
|
ARG ETHERPAD_PLUGINS=
|
|
ARG ETHERPAD_LOCAL_PLUGINS=
|
|
ARG ETHERPAD_LOCAL_PLUGINS_ENV=
|
|
ARG ETHERPAD_GITHUB_PLUGINS=
|
|
|
|
ENV NODE_ENV=production
|
|
ENV ETHERPAD_PRODUCTION=true
|
|
|
|
# The full pnpm-workspace.yaml references admin, doc, ui which are not
|
|
# needed at runtime. Overwrite it with a production-only version so
|
|
# pnpm install doesn't warn about missing workspace directories.
|
|
# Preserve the build-script policy from the source workspace file so
|
|
# pnpm 11 doesn't error out with ERR_PNPM_IGNORED_BUILDS for transitive
|
|
# postinstalls (e.g. @scarf/scarf via swagger-ui-dist).
|
|
RUN printf 'packages:\n - src\n - bin\nonlyBuiltDependencies:\n - esbuild\nignoredBuiltDependencies:\n - "@scarf/scarf"\nstrictDepBuilds: false\n' > pnpm-workspace.yaml
|
|
|
|
COPY --chown=etherpad:etherpad ./src ./src
|
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/templates/admin ./src/templates/admin
|
|
COPY --chown=etherpad:etherpad --from=adminbuild /opt/etherpad-lite/src/static/oidc ./src/static/oidc
|
|
# docker-compose mounts a named volume over src/plugin_packages. Docker seeds a
|
|
# fresh named volume from the mountpoint in the image, so the directory has to
|
|
# exist here (owned by etherpad, since USER is already etherpad) — otherwise
|
|
# Docker creates it root:root and plugin installs cannot write install.lock.
|
|
# See ether/etherpad#8026.
|
|
RUN mkdir -p ./src/plugin_packages
|
|
|
|
COPY --chown=etherpad:etherpad ./local_plugin[s] ./local_plugins/
|
|
|
|
RUN bash -c ./bin/installLocalPlugins.sh
|
|
|
|
RUN bin/installDeps.sh && \
|
|
if [ ! -z "${ETHERPAD_PLUGINS}" ] || [ ! -z "${ETHERPAD_GITHUB_PLUGINS}" ]; then \
|
|
pnpm run plugins i ${ETHERPAD_PLUGINS} ${ETHERPAD_GITHUB_PLUGINS:+--github ${ETHERPAD_GITHUB_PLUGINS}}; \
|
|
fi && \
|
|
pnpm store prune
|
|
|
|
# Copy the configuration file.
|
|
COPY --chown=etherpad:etherpad ${SETTINGS} "${EP_DIR}"/settings.json
|
|
|
|
# Fix group permissions
|
|
# Note: For some reason increases image size from 257 to 334.
|
|
# RUN chmod -R g=u .
|
|
|
|
USER etherpad
|
|
|
|
HEALTHCHECK --interval=5s --timeout=3s \
|
|
CMD wget -qO- http://127.0.0.1:9001/health | grep -E "pass|ok|up" > /dev/null || exit 1
|
|
|
|
EXPOSE 9001
|
|
# Run node directly instead of via `pnpm run prod`. pnpm 11's
|
|
# `runDepsStatusCheck` fires before every `pnpm run …` and spuriously
|
|
# decides node_modules is out of sync on first start under the named-
|
|
# volume layout used by docker-compose (mounting src/plugin_packages).
|
|
# It then tries to `pnpm install --production`, which either prompts to
|
|
# wipe node_modules (tty: true) or aborts with
|
|
# ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY (no tty). Bypassing pnpm
|
|
# at runtime sidesteps the check; the image's node_modules was already
|
|
# verified during build. See ether/etherpad#7718.
|
|
# `exec` makes node PID 1 so it receives SIGTERM directly and shuts down
|
|
# cleanly.
|
|
CMD ["sh", "-c", "cd src && exec node --require tsx/cjs node/server.ts"]
|