FROM python:3.12-slim WORKDIR /app # tzdata/fonts in their own layer, kept separate from the much larger # Node.js/npm layers below -- see those layers' own comments for why # they're split up the way they are. tzdata: python:3.12-slim doesn't # include it by default, so the zoneinfo database backing the web UI's # "Timezone" setting (used by "Quiet hours") would have no named zones # to resolve without this -- ZoneInfo() would raise for anything other # than "UTC". fontconfig/fonts-dejavu-core: whiteboard mode's # render-service/ (own README there) needs something to render # whiteboard text with. RUN apt-get update && apt-get install -y --no-install-recommends \ tzdata fontconfig fonts-dejavu-core \ && rm -rf /var/lib/apt/lists/* # Node.js: whiteboard frame mode's render-service/ runs as a second # process in this same container rather than a separate compose service # -- it's a lightweight, stateless, localhost-only sidecar with nothing # worth independently scaling or restarting. NodeSource's setup script is # used instead of Debian bookworm's own apt Node package, which is both # older than jsdom's minimum (20.19+) and inconsistently available. # curl/gnupg are only needed to add and fetch NodeSource's repo -- purged # again in this same RUN (not a later one; Docker layers are immutable, # so removing them in a *different* instruction wouldn't shrink this # one's actual pushed size) so their bytes don't end up in the image at # all, only nodejs's. RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates gnupg \ && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && apt-get purge -y --auto-remove curl gnupg \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # render-service/'s dependencies installed as several separate layers # rather than one `npm install` covering all of them -- a from-scratch # push of this image once hit Cloudflare's payload-size limit on a # single blob/layer upload (the registry sits behind it), and splitting # a big layer into several smaller ones is the direct fix for exactly # that failure mode, independent of anything about the registry itself. # --no-save: package.json already fully declares these (with the exact # same version pins used here) as the single source of truth for what # this service depends on -- these calls are just about *when* each one # gets installed for layer-size reasons, not re-deciding what's needed. COPY render-service/package.json ./render-service/package.json WORKDIR /app/render-service RUN npm install --omit=dev --no-save express@^5.2.1 && npm cache clean --force RUN npm install --omit=dev --no-save jsdom@^29.1.1 && npm cache clean --force RUN npm install --omit=dev --no-save @excalidraw/utils@0.1.3-test32 && npm cache clean --force RUN npm install --omit=dev --no-save @resvg/resvg-js@2.6.2 && npm cache clean --force WORKDIR /app COPY render-service/server.js ./render-service/server.js COPY app ./app COPY start.sh . RUN chmod +x start.sh EXPOSE 8420 CMD ["./start.sh"]