Files
shelfmark/Dockerfile
T
dependabot[bot] 3ff6d88681 build(deps): bump astral-sh/uv from 0.12.24 to 0.13.5 in the docker-base-image-digests group (#1550)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps the docker-base-image-digests group with 1 update:
[astral-sh/uv](https://github.com/astral-sh/uv).

Updates `astral-sh/uv` from 0.12.24 to 0.13.5
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/releases">astral-sh/uv's
releases</a>.</em></p>
<blockquote>
<h2>0.13.5</h2>
<h2>Release Notes</h2>
<p>Released on 2026-10-21.</p>
<p>Following <a
href="https://github.com/astral-sh/uv/releases/tag/0.13.4">uv
0.13.4</a>, which removed the affected eMudhra/emSign root from uv's own
certificate store, this release updates the latest managed CPython
builds for 3.11 and newer to <a
href="https://github.com/astral-sh/python-build-standalone/releases/tag/20261021">python-build-standalone
20261021</a>, including the CPython <a
href="https://www.python.org/downloads/release/python-3151/">3.15.1</a>,
<a
href="https://www.python.org/downloads/release/python-3149/">3.14.9</a>,
and <a
href="https://www.python.org/downloads/release/python-31317/">3.13.17</a>
security patch releases. These builds include <a
href="https://pip.pypa.io/en/stable/news/#v26-2-2">pip 26.2.2</a> both
preinstalled in the managed interpreter and bundled with
<code>ensurepip</code>, which installs pip by default when you run
<code>python -m venv</code>. Both copies use certifi 2026.10.19, which
removes the same affected root. After upgrading uv, run <code>uv python
upgrade</code> to install the updated builds. Virtual environments
pinned to an older Python patch version do not switch to the updated
interpreter automatically. Upgrade pip separately in existing virtual
environments.</p>
<p>Please note upstream CPython 3.9 and 3.10 still bundle pip releases
whose certificate bundles trust the affected eMudhra/emSign root. We
recommend migrating off of those versions and will no longer be
releasing Docker images with uv installed for EOL Python versions.</p>
<h3>Enhancements</h3>
<ul>
<li>Report incompatible self-dependencies directly in resolution errors
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21845">#21845</a>)</li>
</ul>
<h3>Python</h3>
<ul>
<li>Add CPython 3.15.1, 3.14.9, and 3.13.17 (<a
href="https://redirect.github.com/astral-sh/uv/pull/22802">#22802</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Select and rank wheel variants with no required properties or
properties from PEP 850 static providers in platform-specific
resolutions with <code>wheel-variants</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/22535">#22535</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22536">#22536</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22687">#22687</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22688">#22688</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reduce peak memory use when resolving dependencies across multiple
environments (<a
href="https://redirect.github.com/astral-sh/uv/pull/22769">#22769</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22770">#22770</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22784">#22784</a>)</li>
<li>Speed up exports with complex transitive extras by resolving
activation across the dependency graph (<a
href="https://redirect.github.com/astral-sh/uv/pull/22751">#22751</a>)</li>
<li>Reduce uv's binary size by keeping additional error formatters out
of line (<a
href="https://redirect.github.com/astral-sh/uv/pull/22775">#22775</a>)</li>
<li>Reduce overhead when parsing custom build cache keys (<a
href="https://redirect.github.com/astral-sh/uv/pull/22749">#22749</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Lock packages distributed only as Android wheels without a source
distribution (<a
href="https://redirect.github.com/astral-sh/uv/pull/22806">#22806</a>)</li>
<li>Export optional dependencies correctly when transitive extras are
activated through different or incompatible environment paths (<a
href="https://redirect.github.com/astral-sh/uv/pull/22751">#22751</a>)</li>
<li>Reject invalid custom build cache keys instead of silently falling
back to the defaults (<a
href="https://redirect.github.com/astral-sh/uv/pull/22749">#22749</a>)</li>
</ul>
<h3>Security</h3>
<ul>
<li>Reject certificate chains containing compromised eMudhra
intermediate keys via <code>rustls-webpki</code> 0.103.16 (<a
href="https://redirect.github.com/astral-sh/uv/pull/22777">#22777</a>)</li>
<li>Stop publishing uv Docker images based on Python 3.9 and 3.10 images
that no longer receive upstream updates (<a
href="https://redirect.github.com/astral-sh/uv/pull/22773">#22773</a>)</li>
</ul>
<h2>Install uv 0.13.5</h2>
<h3>Install prebuilt binaries via shell script</h3>
<pre lang="sh"><code>curl --proto '=https' --tlsv1.2 -LsSf
https://releases.astral.sh/github/uv/releases/download/0.13.5/uv-installer.sh
| sh
</code></pre>
<h3>Install prebuilt binaries via powershell script</h3>
<pre lang="sh"><code>&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/uv/blob/main/CHANGELOG.md">astral-sh/uv's
changelog</a>.</em></p>
<blockquote>
<h2>0.13.5</h2>
<p>Released on 2026-10-21.</p>
<p>Following <a
href="https://github.com/astral-sh/uv/releases/tag/0.13.4">uv
0.13.4</a>, which removed the affected eMudhra/emSign root from uv's own
certificate store, this release updates the latest managed CPython
builds for 3.11 and newer to <a
href="https://github.com/astral-sh/python-build-standalone/releases/tag/20261021">python-build-standalone
20261021</a>, including the CPython <a
href="https://www.python.org/downloads/release/python-3151/">3.15.1</a>,
<a
href="https://www.python.org/downloads/release/python-3149/">3.14.9</a>,
and <a
href="https://www.python.org/downloads/release/python-31317/">3.13.17</a>
security patch releases. These builds include <a
href="https://pip.pypa.io/en/stable/news/#v26-2-2">pip 26.2.2</a> both
preinstalled in the managed interpreter and bundled with
<code>ensurepip</code>, which installs pip by default when you run
<code>python -m venv</code>. Both copies use certifi 2026.10.19, which
removes the same affected root. After upgrading uv, run <code>uv python
upgrade</code> to install the updated builds. Virtual environments
pinned to an older Python patch version do not switch to the updated
interpreter automatically. Upgrade pip separately in existing virtual
environments.</p>
<p>Please note upstream CPython 3.9 and 3.10 still bundle pip releases
whose certificate bundles trust the affected eMudhra/emSign root. We
recommend migrating off of those versions and will no longer be
releasing Docker images with uv installed for EOL Python versions.</p>
<h3>Enhancements</h3>
<ul>
<li>Report incompatible self-dependencies directly in resolution errors
(<a
href="https://redirect.github.com/astral-sh/uv/pull/21845">#21845</a>)</li>
</ul>
<h3>Python</h3>
<ul>
<li>Add CPython 3.15.1, 3.14.9, and 3.13.17 (<a
href="https://redirect.github.com/astral-sh/uv/pull/22802">#22802</a>)</li>
</ul>
<h3>Preview features</h3>
<ul>
<li>Select and rank wheel variants with no required properties or
properties from PEP 850 static providers in platform-specific
resolutions with <code>wheel-variants</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/22535">#22535</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22536">#22536</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22687">#22687</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22688">#22688</a>)</li>
</ul>
<h3>Performance</h3>
<ul>
<li>Reduce peak memory use when resolving dependencies across multiple
environments (<a
href="https://redirect.github.com/astral-sh/uv/pull/22769">#22769</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22770">#22770</a>,
<a
href="https://redirect.github.com/astral-sh/uv/pull/22784">#22784</a>)</li>
<li>Speed up exports with complex transitive extras by resolving
activation across the dependency graph (<a
href="https://redirect.github.com/astral-sh/uv/pull/22751">#22751</a>)</li>
<li>Reduce uv's binary size by keeping additional error formatters out
of line (<a
href="https://redirect.github.com/astral-sh/uv/pull/22775">#22775</a>)</li>
<li>Reduce overhead when parsing custom build cache keys (<a
href="https://redirect.github.com/astral-sh/uv/pull/22749">#22749</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Lock packages distributed only as Android wheels without a source
distribution (<a
href="https://redirect.github.com/astral-sh/uv/pull/22806">#22806</a>)</li>
<li>Export optional dependencies correctly when transitive extras are
activated through different or incompatible environment paths (<a
href="https://redirect.github.com/astral-sh/uv/pull/22751">#22751</a>)</li>
<li>Reject invalid custom build cache keys instead of silently falling
back to the defaults (<a
href="https://redirect.github.com/astral-sh/uv/pull/22749">#22749</a>)</li>
</ul>
<h3>Security</h3>
<ul>
<li>Reject certificate chains containing compromised eMudhra
intermediate keys via <code>rustls-webpki</code> 0.103.16 (<a
href="https://redirect.github.com/astral-sh/uv/pull/22777">#22777</a>)</li>
<li>Stop publishing uv Docker images based on Python 3.9 and 3.10 images
that no longer receive upstream updates (<a
href="https://redirect.github.com/astral-sh/uv/pull/22773">#22773</a>)</li>
</ul>
<h2>0.13.4</h2>
<p>Released on 2026-10-20.</p>
<h3>Preview features</h3>
<ul>
<li>Validate PEP 825 variant metadata and treat package versions with
invalid metadata as unavailable under <code>wheel-variants</code> (<a
href="https://redirect.github.com/astral-sh/uv/pull/22512">#22512</a>)</li>
</ul>
<h3>Other changes</h3>
<ul>
<li>Update the bundled Mozilla root certificates via
<code>webpki-root-certs</code> 1.0.10 (<a
href="https://redirect.github.com/astral-sh/uv/pull/22760">#22760</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/uv/commit/46776a95d6686ac0160c10611327ce491e6b202f"><code>46776a9</code></a>
Use checked-in Python metadata for trampoline checks (<a
href="https://redirect.github.com/astral-sh/uv/issues/22811">#22811</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/5babfa90b420ea12a611554f8cbbb41d433534be"><code>5babfa9</code></a>
Use checked-in Python download metadata for releases (<a
href="https://redirect.github.com/astral-sh/uv/issues/22810">#22810</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/06fbc976b1462d56bc83609e1c26250740e9c656"><code>06fbc97</code></a>
Reject Cargo.lock changes from forks (<a
href="https://redirect.github.com/astral-sh/uv/issues/22381">#22381</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/b3c01fd5745ab459863b83380dbd79621073a797"><code>b3c01fd</code></a>
Prepare 0.13.5 (<a
href="https://redirect.github.com/astral-sh/uv/issues/22809">#22809</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/a02d446bcddd8a00c98084709b6bdccbc9a8208f"><code>a02d446</code></a>
Update to include Python 3.15.1, 3.14.9, 3.13.17 (<a
href="https://redirect.github.com/astral-sh/uv/issues/22802">#22802</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/0ee565f1d755dfa5d7ba78db287ed342ec3e4d4b"><code>0ee565f</code></a>
Reproduce local-version marker rendering panic (<a
href="https://redirect.github.com/astral-sh/uv/issues/22807">#22807</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/e3810e1e6daf06095ac58a27dd422eb095ea4ed6"><code>e3810e1</code></a>
Infer platform markers from Android wheel tags (<a
href="https://redirect.github.com/astral-sh/uv/issues/22806">#22806</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/75698b1ed23dd410421de4656304077351641fa7"><code>75698b1</code></a>
Update rustup in Docker builders (<a
href="https://redirect.github.com/astral-sh/uv/issues/22803">#22803</a>)</li>
<li><a
href="https://github.com/astral-sh/uv/commit/1765e51d67f43299d8fbd37b302b773a80c8499d"><code>1765e51</code></a>
Replace actions/attest-build-provenance action with actions/attest
v4.2.2 (<a
href="https://redirect.github.com/astral-sh/uv/issues/2">#2</a>...</li>
<li><a
href="https://github.com/astral-sh/uv/commit/0abe802a1a07417f5163e57549c93c132cd7db3f"><code>0abe802</code></a>
Update Rust crate test-log to v0.2.21 (<a
href="https://redirect.github.com/astral-sh/uv/issues/19721">#19721</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/uv/compare/0.12.24...0.13.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=astral-sh/uv&package-manager=docker&previous-version=0.12.24&new-version=0.13.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-23 14:39:23 -04:00

307 lines
13 KiB
Docker

ARG TARGETPLATFORM
ARG TARGETARCH
ARG BUILDPLATFORM
ARG BUILDARCH
# Frontend build stage.
FROM --platform=$BUILDPLATFORM node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1 AS frontend-builder
# Helpful debug output to see what platforms BuildKit thinks it's using
RUN echo "BUILDPLATFORM=$BUILDPLATFORM BUILDARCH=$BUILDARCH TARGETPLATFORM=$TARGETPLATFORM TARGETARCH=$TARGETARCH"
WORKDIR /frontend
# Copy frontend package files
COPY src/frontend/package*.json ./
# Install dependencies (cache mount for faster rebuilds)
RUN --mount=type=cache,target=/root/.npm \
npm ci
# Copy frontend source
COPY src/frontend/ ./
# Build the frontend
RUN npm run build
# uv is a build-time tool only, so it is mounted into the RUNs that need it rather
# than copied into the image. A COPY here would land ~24 MB in a `base` layer that
# every published image inherits, and a later `rm` cannot take it back out again --
# a RUN adds a layer, it does not rewrite the one underneath.
FROM ghcr.io/astral-sh/uv:0.13.5@sha256:32a7bb39b496babbd997b0a39a4d66c126bdaf5661b302bf6f06abda8deca6ff AS uv
# Use python-slim as the base image
FROM python:3.14.8-slim@sha256:a2b82f3c48559aa0a8446d9af49826b6e2b2016f4cd2afabfe6013ec53729170 AS base
# Set shell to bash with pipefail option
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Consistent environment variables grouped together
ENV DEBIAN_FRONTEND=noninteractive \
DOCKERMODE=true \
UV_LINK_MODE=copy \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONIOENCODING=UTF-8 \
NAME=Shelfmark \
PATH=/app/.venv/bin:$PATH \
PYTHONPATH=/app \
# PUID/PGID will be handled by entrypoint script, but TZ/Locale are still needed
LANG=en_US.UTF-8 \
LANGUAGE=en_US:en \
LC_ALL=en_US.UTF-8
# Set ARG for build-time expansion (FLASK_PORT), ENV for runtime access
ENV FLASK_PORT=8084
# Configure locale, timezone, and perform initial cleanup in a single layer
RUN apt-get update && \
apt-get install -y --no-install-recommends \
# No C toolchain: every compiled dependency (cffi, gevent, greenlet,
# zope-interface) ships a cp314 manylinux wheel for amd64 and arm64, and
# the sdist-only ones are pure Python. The toolchain was 290MB of the
# runtime image, and python3-dev pulled Debian's libpython3.13 into a 3.14
# image. If a dependency ever needs compiling, build it in a separate stage.
# For locale
locales tzdata \
# For healthcheck
curl \
# For entrypoint
dumb-init \
# For debug
zip iputils-ping \
# For user switching
gosu \
# --- Tor support (activated via USING_TOR=true) ---
tor \
supervisor \
iptables \
# --- WireGuard support (activated via USING_WIREGUARD=true) ---
wireguard-tools \
iproute2 \
procps \
ca-certificates && \
# Configure iptables alternatives for tor.sh compatibility
update-alternatives --set iptables /usr/sbin/iptables-legacy && \
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy && \
# Cleanup APT cache *after* all installs in this layer
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* && \
# Default to UTC timezone but will be overridden by the entrypoint script
ln -snf /usr/share/zoneinfo/UTC /etc/localtime && echo UTC > /etc/timezone && \
# Configure locale
sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
locale-gen en_US.UTF-8 && \
echo "LC_ALL=en_US.UTF-8" >> /etc/environment && \
echo "LANG=en_US.UTF-8" > /etc/locale.conf
# Create a fixed runtime user/group so hardened Docker/Kubernetes deployments
# can start the container directly as a non-root user with a passwd entry.
RUN groupadd -g 1000 shelfmark && \
useradd -u 1000 -g shelfmark -d /home/shelfmark -s /usr/sbin/nologin shelfmark && \
mkdir -p /home/shelfmark && \
chown 1000:1000 /home/shelfmark
# Set working directory
WORKDIR /app
# Install core Python dependencies first for better layer caching
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=from=uv,source=/uv,target=/usr/local/bin/uv \
uv sync --locked --no-default-groups
# Runtime dependencies are installed into /app/.venv during the build. Remove the
# base image's system pip so stale installer CVEs do not ship in the final image.
RUN rm -rf \
/usr/local/bin/pip \
/usr/local/bin/pip3 \
/usr/local/bin/pip3.* \
/usr/local/lib/python*/site-packages/pip \
/usr/local/lib/python*/site-packages/pip-*.dist-info
# The application code is deliberately NOT copied here. `base` is shared by the
# final stages, so a COPY of the source at this point invalidates every layer
# built on top of it -- the Chromium install, the browser dependency sync and
# the SeleniumBase driver download -- on any source change. Each stage copies
# the source as its last step instead, so a code-only rebuild rewrites one small
# layer and every expensive layer is reused. `[tool.uv] package = false` is what
# makes this safe: no `uv sync` needs the project source.
# Expose the application port
EXPOSE ${FLASK_PORT}
# Add healthcheck for container status
# Uses /api/health which doesn't require authentication.
# curl needs -f so an HTTP error status fails the probe instead of passing it:
# plain `curl -s` exits 0 on a 500, which reported a broken app as healthy.
# timeout stays well under interval so a hung probe cannot occupy a whole cycle.
# --start-interval matches the daemon default (5s), made explicit so startup
# probing does not depend on that default staying put.
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --start-interval=5s --retries=3 \
CMD curl -fsS http://localhost:${FLASK_PORT}/api/health > /dev/null || exit 1
# Use dumb-init as the entrypoint to handle signals properly
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
FROM base AS shelfmark
# --- Chromium (PINNED to 155.0.8059.79) ---
# Debian's chromium 150.0.7871.46-1~deb13u1 security update (trixie-security,
# 2026-07-05) stopped opening the DevTools remote-debugging TCP port that the
# SeleniumBase Pure-CDP driver connects through (/json/version), so every
# internal bypass died with "Pure CDP browser startup failed" and 149.0.7827.196
# was pinned (#1105). Later Debian builds open the port again: 155.0.8059.79
# starts Pure CDP with the bypasser's own arguments. Still installed from
# snapshot.debian.org, so the build stays reproducible once trixie-security
# moves past this version. Bump CHROMIUM_VERSION and CHROMIUM_SNAPSHOT together,
# and check that the bypasser still starts before relaxing the pin.
# Chrome 144+ requires --enable-unsafe-swiftshader for WebGL in Docker.
# This flag is set in internal_bypasser.py _get_browser_args()
ARG CHROMIUM_VERSION=155.0.8059.79-1~deb13u1
ARG CHROMIUM_SNAPSHOT=20261016T000000Z
RUN echo "deb [check-valid-until=no] https://snapshot.debian.org/archive/debian-security/${CHROMIUM_SNAPSHOT}/ trixie-security main" \
> /etc/apt/sources.list.d/chromium-pin-snapshot.list && \
apt-get update -o Acquire::Retries=5 && \
apt-get install -y --no-install-recommends -o Acquire::Retries=5 \
# For dumb display
xvfb \
# For screen recording
ffmpeg \
chromium=${CHROMIUM_VERSION} \
chromium-common=${CHROMIUM_VERSION} \
# For tkinter (pyautogui)
python3-tk \
# For RAR extraction
unrar-free && \
# Keep apt from "upgrading" chromium past the pin inside derived images
printf 'Package: chromium chromium-common\nPin: version %s\nPin-Priority: 1001\n' "${CHROMIUM_VERSION}" \
> /etc/apt/preferences.d/chromium-pin && \
rm /etc/apt/sources.list.d/chromium-pin-snapshot.list && \
# Create symlink so rarfile library can find unrar
ln -sf /usr/bin/unrar-free /usr/bin/unrar && \
# Cleanup APT cache
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Install the browser automation stack used by the full image
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=from=uv,source=/uv,target=/usr/local/bin/uv \
uv sync --locked --no-default-groups --extra browser
# Deterministically resolve the Xlib namespace collision.
# pyautogui/mouseinfo pull the stale `python3-xlib` (0.15, 2014), while the
# `--extra browser` set pulls `python-xlib` (0.33). Both packages install into
# the same top-level `Xlib/` namespace, so whichever lands last wins. When the
# 2014 build wins, `Xlib.X` is missing `FamilyServerInterpreted`, which the
# SeleniumBase Pure-CDP driver requires at browser startup -> every bypass fails
# with "module 'Xlib.X' has no attribute 'FamilyServerInterpreted'" and no
# Cloudflare/DDoS-Guard protected download can complete. Drop the stale package
# and force python-xlib 0.33 to own the namespace. pyautogui runs fine against
# 0.33 (superset API).
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=from=uv,source=/uv,target=/usr/local/bin/uv \
uv pip uninstall --python /app/.venv/bin/python python3-xlib && \
uv pip install --python /app/.venv/bin/python --reinstall python-xlib==0.33 && \
/app/.venv/bin/python -c "import Xlib.X; assert hasattr(Xlib.X, 'FamilyServerInterpreted'), 'Xlib.X.FamilyServerInterpreted missing after fix'; print('Xlib namespace OK:', Xlib.__version__)"
# The venv's own pip goes too. uv seeded a copy into /app/.venv and nothing
# installs at runtime. setuptools deliberately STAYS: several deps still import
# pkg_resources on the hot path.
RUN rm -rf /app/.venv/bin/pip /app/.venv/bin/pip3 /app/.venv/bin/pip3.* \
/app/.venv/lib/python*/site-packages/pip \
/app/.venv/lib/python*/site-packages/pip-*.dist-info
# Keep SeleniumBase's bundled driver cache writable for the fixed non-root user.
RUN SELENIUMBASE_DRIVERS_DIR=$(/app/.venv/bin/python -c "import pathlib, seleniumbase; print(pathlib.Path(seleniumbase.__file__).resolve().parent / 'drivers')") && \
chown -R 1000:1000 "${SELENIUMBASE_DRIVERS_DIR}" && \
chmod -R u+rwX,go+rX "${SELENIUMBASE_DRIVERS_DIR}" && \
if [ -f "${SELENIUMBASE_DRIVERS_DIR}/uc_driver" ]; then chmod +x "${SELENIUMBASE_DRIVERS_DIR}/uc_driver"; fi
# Grant read/execute permissions to others
RUN chmod -R o+rx /usr/bin/chromium
# --- Application code: last, so every expensive layer above stays cached ---
COPY . .
COPY --from=frontend-builder /frontend/dist /app/frontend-dist
# The frontend SOURCE cannot be excluded via .dockerignore — that file applies
# to every stage and frontend-builder needs src/. Only the built dist (copied
# above) is served at runtime, so drop the source here.
RUN rm -rf /app/src
# Image-owned runtime paths for the fixed non-root user. Root/PUID mode still
# re-homes ownership at startup when needed.
RUN mkdir -p \
/config \
/books \
/var/log/shelfmark \
/tmp/shelfmark/seleniumbase/downloaded_files \
/tmp/shelfmark/seleniumbase/archived_files && \
rm -rf /app/downloaded_files /app/archived_files && \
ln -s /tmp/shelfmark/seleniumbase/downloaded_files /app/downloaded_files && \
ln -s /tmp/shelfmark/seleniumbase/archived_files /app/archived_files && \
chown -R 1000:1000 /config /books /home/shelfmark /tmp/shelfmark /var/log/shelfmark && \
chmod -R a+rX /app && \
chmod +x /app/entrypoint.sh /app/tor.sh /app/wireguard.sh /app/genDebug.sh
# Default command to run the application entrypoint script
# Version stamp last. These carry the commit sha, so they change on every
# build and everything below them rebuilds. Kept here, the dependency and
# browser layers above stay valid and a pull only fetches what changed.
ARG BUILD_VERSION
ENV BUILD_VERSION=${BUILD_VERSION}
ARG RELEASE_VERSION
ENV RELEASE_VERSION=${RELEASE_VERSION}
CMD ["/app/entrypoint.sh"]
FROM base AS shelfmark-lite
ENV USING_EXTERNAL_BYPASSER=true
# --- Application code: last, so every expensive layer above stays cached ---
COPY . .
COPY --from=frontend-builder /frontend/dist /app/frontend-dist
# The frontend SOURCE cannot be excluded via .dockerignore — that file applies
# to every stage and frontend-builder needs src/. Only the built dist (copied
# above) is served at runtime, so drop the source here.
RUN rm -rf /app/src
# Image-owned runtime paths for the fixed non-root user. Root/PUID mode still
# re-homes ownership at startup when needed.
RUN mkdir -p \
/config \
/books \
/var/log/shelfmark \
/tmp/shelfmark/seleniumbase/downloaded_files \
/tmp/shelfmark/seleniumbase/archived_files && \
rm -rf /app/downloaded_files /app/archived_files && \
ln -s /tmp/shelfmark/seleniumbase/downloaded_files /app/downloaded_files && \
ln -s /tmp/shelfmark/seleniumbase/archived_files /app/archived_files && \
chown -R 1000:1000 /config /books /home/shelfmark /tmp/shelfmark /var/log/shelfmark && \
chmod -R a+rX /app && \
chmod +x /app/entrypoint.sh /app/tor.sh /app/wireguard.sh /app/genDebug.sh
# Version stamp last. These carry the commit sha, so they change on every
# build and everything below them rebuilds. Kept here, the dependency and
# browser layers above stay valid and a pull only fetches what changed.
ARG BUILD_VERSION
ENV BUILD_VERSION=${BUILD_VERSION}
ARG RELEASE_VERSION
ENV RELEASE_VERSION=${RELEASE_VERSION}
CMD ["/app/entrypoint.sh"]