Fetch headless Chromium at container startup instead of build time
Build and push server image / test (push) Successful in 45s
Build and push server image / build-and-push (push) Successful in 4m17s
Build and push server image / deploy (push) Failing after 1m42s

build-and-push failed on the last deploy: chromium-headless-shell's
single ~181MB binary can't be split across Docker layers the way this
project's pip/npm installs were (those are many independently-
installable smaller packages; this is one file), and confirmed-failed
to push past the registry's per-layer size limit.

Moves the `playwright install chromium-headless-shell` step from the
Dockerfile to start.sh, caching into PLAYWRIGHT_BROWSERS_PATH on the
/data volume -- only the very first boot on a fresh volume downloads
it, every boot after that is a no-op check. The image itself no longer
grows by ~262MB, so nothing new gets pushed to the registry at all.
This commit is contained in:
2026-07-31 02:18:36 +00:00
parent 8ea1c53ec3
commit c6dad191fb
3 changed files with 40 additions and 23 deletions
+10 -4
View File
@@ -283,10 +283,16 @@ widgets on the same frame without a Floyd-Steinberg seam at the boundary
(see that module's docstring for why ordered dithering doesn't have this
problem and Floyd-Steinberg does) -- no `Frame`-level dithering setting
was needed. Playwright/Chromium is a real, heavyweight runtime dependency
imported lazily only when a weather widget actually uses this style, and
its Docker packaging has a known likely-blocking image-size problem not
yet resolved (see `server/Dockerfile`'s own comment) -- treat this style
as unshipped/local-only until that's sorted out.
imported lazily only when a weather widget actually uses this style.
Its browser binary is fetched by `start.sh` at container startup rather
than baked into the image (see `server/Dockerfile`'s own comment) --
a single ~181MB `chrome-headless-shell` binary can't be split across
Docker layers the way this project's pip/npm installs were, and
confirmed-failed to push to the registry as a build-time layer; cached
on the `/data` volume (`PLAYWRIGHT_BROWSERS_PATH`) so only the very
first boot on a fresh volume actually downloads it. Still real-panel-
unverified (see this widget's own render_style rollout notes/PR) --
treat "modern" style as experimental regardless of deploy status.
`current`/`hourly`/`daily` share one configured location
(`city_label`/`city_latitude`/`city_longitude`, set via `POST .../
+18 -19
View File
@@ -36,8 +36,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
tzdata fontconfig fonts-dejavu-core nodejs npm \
&& rm -rf /var/lib/apt/lists/*
# EXPERIMENTAL, likely unmergeable as-is -- see below. System libs a
# headless Chromium needs (app/html_render.py, the weather widget's
# EXPERIMENTAL. System libs a headless Chromium needs (app/html_render.py, the weather widget's
# opt-in "modern" render style), trimmed from Playwright's own full
# `install-deps chromium` list to just what a headless (no Xvfb),
# Latin-text-plus-emoji use case needs: dropped xvfb (only needed for a
@@ -74,23 +73,23 @@ RUN pip install --no-cache-dir pypdfium2==5.12.1
RUN pip install --no-cache-dir playwright==1.61.0
RUN pip install --no-cache-dir -r requirements.txt
# KNOWN LIKELY BLOCKER, not resolved by pulling this into its own layer:
# `playwright install chromium-headless-shell` unpacks to ~262MB, and its
# single `chrome-headless-shell` binary alone (measured: 181MB) is one
# file -- unlike the pip/npm splits above (independently-installable
# smaller packages moved into their own layers), a single 181MB file
# can't be divided across multiple <100MB Docker layers by any ordinary
# COPY/RUN restructuring; the whole file lands in whichever layer's diff
# contains it. This almost certainly exceeds the same Cloudflare single-
# blob/layer limit that forced the pip/npm splits elsewhere in this file
# (see their comments) -- an actual push to this project's registry
# hasn't been attempted (would require pushing to `main`, which triggers
# deploy) to confirm, but there is no reason to expect a single 181MB
# blob to fit where combined ~113MB of many small wheels didn't. Needs a
# real resolution (a registry without this limit, hosting the browser
# binary outside the image, etc.) before this branch can actually ship --
# tracked as open, not silently assumed away.
RUN playwright install chromium-headless-shell
# The headless Chromium binary itself is deliberately NOT installed
# here at build time. `playwright install chromium-headless-shell`
# unpacks to ~262MB, and its single `chrome-headless-shell` binary alone
# (measured: 181MB) is one file -- unlike the pip/npm splits above
# (independently-installable smaller packages moved into their own
# layers), a single 181MB file can't be divided across multiple <100MB
# Docker layers by any ordinary COPY/RUN restructuring; the whole file
# lands in whichever layer's diff contains it, which confirmed-failed
# to push to this project's registry (the same Cloudflare single-blob/
# layer limit that forced the pip/npm splits elsewhere in this file --
# see their comments). Fix: start.sh downloads it at container startup
# instead, cached on the /data volume (PLAYWRIGHT_BROWSERS_PATH below)
# so it survives restarts/redeploys and only ever downloads once per
# volume, not once per image layer. Trade-off: first boot on a fresh
# volume needs network access to Playwright's CDN -- true of Immich/
# weather API access too, so not a new requirement for this server.
ENV PLAYWRIGHT_BROWSERS_PATH=/data/.playwright-browsers
# render-service/'s dependencies installed as several separate layers
# rather than one `npm install` covering all of them -- a from-scratch
+12
View File
@@ -5,6 +5,18 @@
# Then execs uvicorn as the foreground/PID 1 process so it receives
# Docker's stop signal directly.
#
# Before either: fetch headless Chromium (app/html_render.py, the
# weather widget's opt-in "modern" render style) into
# PLAYWRIGHT_BROWSERS_PATH (set in the Dockerfile to a path on the /data
# volume) if it isn't already cached there -- see the Dockerfile's own
# comment for why this happens at startup instead of build time. Only
# the very first boot on a fresh volume actually downloads anything;
# every boot after that is a no-op ls check.
if [ -z "$(ls -A "$PLAYWRIGHT_BROWSERS_PATH" 2>/dev/null)" ]; then
echo "Fetching headless Chromium into $PLAYWRIGHT_BROWSERS_PATH (first boot on this volume)..." >&2
playwright install chromium-headless-shell
fi
#
# Wrapped in a restart loop, not a bare `node ... &`: a bare background
# process that crashes stays dead for good, with nothing to bring it
# back -- turning any single render crash (a not-yet-found jsdom/