Files
bookorbit/Dockerfile
neon 569cc8c84b security(docker): move both images to node 26.12.1
Rebuild the app and read-aloud worker images on Node 26.12.1, which fixes
CVE-2026-111497 in the bundled Brotli decoder and the other October 2026
Node security issues. The app image stays on Alpine 3.23, pinned by digest.
Closes #1912
2026-10-24 22:48:29 +00:00

94 lines
4.0 KiB
Docker

ARG NODE_IMAGE=node:26.12.1-alpine3.23@sha256:bc2b334006f15c7167bbeb2f08f28c2df883f7594a0470e04454ebe7560cfe68
FROM ${NODE_IMAGE} AS base
RUN npm install -g pnpm@11.22.0
# Stage 1: Build client
FROM base AS client-builder
WORKDIR /app
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY patches/ ./patches/
COPY packages/types/package.json ./packages/types/
COPY client/package.json ./client/
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --filter client... --frozen-lockfile
COPY packages/ ./packages/
COPY client/ ./client/
# pnpm 11 defaults verifyDepsBeforeRun to "install", so running a script
# re-installs first. Each stage installed its own filtered subset with a frozen
# lockfile two steps up, and no stage carries the whole workspace, so that
# re-install is both redundant and wrong: it resolves against a partial
# workspace. In the server stage it is fatal, because client/ is absent and the
# @embedpdf patches then look unused.
RUN pnpm --config.verify-deps-before-run=false --filter client run build-only
# Stage 2: Build server + create deploy bundle
FROM base AS server-builder
WORKDIR /app
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
COPY patches/ ./patches/
COPY packages/types/package.json ./packages/types/
COPY server/package.json ./server/
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --filter server... --frozen-lockfile
COPY packages/ ./packages/
COPY server/ ./server/
RUN pnpm --config.verify-deps-before-run=false --filter server run build
# pnpm deploy prunes to prod deps; dist/ is gitignored so copy it in after.
RUN pnpm --config.allow-unused-patches=true --filter server deploy --prod --legacy /deploy
RUN cp -r /app/server/dist /deploy/dist
RUN mkdir -p /deploy/migrations && cp -r /app/server/src/db/migrations/. /deploy/migrations/
# Stage 3: Runtime image
FROM ${NODE_IMAGE} AS runtime
WORKDIR /app
ARG APP_VERSION=dev
ENV APP_VERSION=${APP_VERSION}
ENV KOBO_CLOUDSCRAPER_PYTHON=/opt/bookorbit-python/bin/python
ENV KOREADER_PLUGIN_PATH=/app/koreader-plugin/bookorbit.koplugin
COPY server/requirements/kobo-cloudscraper.txt /tmp/kobo-cloudscraper-requirements.txt
# pip is build-only here. Leaving it installed also leaves pip/_vendor/vendor.txt,
# which Trivy reads as installed msgpack and setuptools and fails the image scan on.
RUN apk upgrade --no-cache && \
apk add --no-cache 'libexpat>=2.9.0-r0' poppler-utils djvulibre util-linux su-exec ffmpeg python3 py3-pip tini tzdata && \
python3 -m venv /opt/bookorbit-python && \
/opt/bookorbit-python/bin/python -m pip install --no-cache-dir -r /tmp/kobo-cloudscraper-requirements.txt && \
/opt/bookorbit-python/bin/python -m pip uninstall -y pip && \
apk del py3-pip && \
rm -f /tmp/kobo-cloudscraper-requirements.txt && \
rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=server-builder --chown=node:node /deploy ./
COPY --from=client-builder --chown=node:node /app/client/dist ./public
COPY --from=server-builder --chown=node:node /app/server/entrypoint.sh /app/server/file-env.sh ./
COPY --chown=node:node LICENSE NOTICE ADDITIONAL_TERMS.md ./
COPY --chown=node:node server/bin/kepubify/ ./bin/kepubify/
COPY --chown=node:node koreader-plugin/bookorbit.koplugin/ ./koreader-plugin/bookorbit.koplugin/
RUN sed -i 's/\r$//' /app/entrypoint.sh /app/file-env.sh && chmod +x /app/entrypoint.sh /app/bin/kepubify/* && mkdir -p /books /data/covers /data/book-bucket /tmp && chown -R node:node /data /tmp
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD host="$(printf '%s' "${HOST:-}" | tr -d '[:space:]')"; \
case "$host" in \
''|0.0.0.0) host=127.0.0.1 ;; \
::) host='[::1]' ;; \
*:*) host="[$host]" ;; \
esac; \
wget -q -T 4 -O /dev/null "http://${host}:${PORT:-3000}/api/v1/health"
ENTRYPOINT ["/sbin/tini", "-s", "--"]
CMD ["sh", "/app/entrypoint.sh"]