110 lines
4.9 KiB
Docker
110 lines
4.9 KiB
Docker
# Multi-stage Dockerfile.
|
|
#
|
|
# Why two stages
|
|
# ──────────────
|
|
# `canvas` is a native-binding npm package. It needs Cairo/Pango/etc.
|
|
# at runtime, and python3+make+g++ at install-time to compile its
|
|
# native bindings (no prebuilt binary covers the alpine musl+napi+linux
|
|
# combination at this canvas version; prebuild-install falls back to
|
|
# node-gyp, which needs the compile toolchain).
|
|
#
|
|
# The naive single-stage approach would either:
|
|
# (a) keep python3+make+g++ in the final image — inflates the image
|
|
# by ~200MB of build tools nothing uses at runtime, or
|
|
# (b) try to install --omit=dev in a runtime-only image — fails
|
|
# because canvas tries to compile and has no python (this was
|
|
# the bug fixed in this revision).
|
|
#
|
|
# Two-stage solves both: install + compile + prune dev deps in the
|
|
# builder (which has the toolchain), then COPY the resulting
|
|
# node_modules into a slim runtime that only carries the C runtime
|
|
# libs canvas needs at execution time.
|
|
|
|
# ─────────────────────────────────────────────────────────────────
|
|
# Stage 1: builder
|
|
# Installs all deps (including dev), compiles native bindings,
|
|
# builds the Vite bundle, then prunes dev deps so node_modules is
|
|
# production-ready before we copy it.
|
|
# ─────────────────────────────────────────────────────────────────
|
|
FROM node:20-alpine AS builder
|
|
|
|
# Native-binding build prerequisites:
|
|
# • python3 / make / g++ — node-gyp toolchain for compiling canvas
|
|
# • cairo-dev / pango-dev / etc. — headers canvas links against
|
|
# These stay in the builder stage only; the runtime stage carries
|
|
# the matching runtime libraries (no -dev suffix) without the
|
|
# toolchain.
|
|
RUN apk add --no-cache \
|
|
cairo-dev pango-dev libjpeg-turbo-dev giflib-dev librsvg-dev pixman-dev \
|
|
python3 make g++
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy package manifests first so docker can cache the npm install
|
|
# layer when only application source changes.
|
|
COPY package*.json ./
|
|
RUN npm install
|
|
|
|
COPY . .
|
|
|
|
# Pull Google Fonts into ./fonts/ so they end up in the runtime image.
|
|
# If the build host has no network this step will produce missing-variant
|
|
# warnings; the build still succeeds and the server falls back to system
|
|
# fonts.
|
|
RUN npm run fetch-fonts || echo "Font fetch had warnings; continuing with whatever was downloaded."
|
|
|
|
RUN npm run build
|
|
|
|
# Drop dev dependencies (vitest, playwright, eslint, etc.) so the
|
|
# node_modules we copy to runtime is production-only. This is the
|
|
# step that was missing before — without it, the runtime stage had
|
|
# to re-run `npm install` from scratch, which would re-trigger
|
|
# canvas's native compile in an image that doesn't have the
|
|
# toolchain. By pruning here, the runtime stage just copies the
|
|
# already-compiled directory.
|
|
RUN npm prune --production
|
|
|
|
# ─────────────────────────────────────────────────────────────────
|
|
# Stage 2: runtime
|
|
# Carries only the runtime C libraries canvas links against plus
|
|
# the fonts the export pipeline needs. node_modules is copied
|
|
# whole from the builder; no `npm install` happens here.
|
|
# ─────────────────────────────────────────────────────────────────
|
|
FROM node:20-alpine
|
|
|
|
# Cairo + Pango are required by node-canvas at runtime.
|
|
# The font packages cover the proprietary template families (Impact,
|
|
# Times, Courier, Arial) via free equivalents that fontconfig will
|
|
# alias, plus emoji rendering for sticker exports.
|
|
RUN apk add --no-cache \
|
|
cairo pango libjpeg-turbo giflib librsvg pixman \
|
|
ttf-liberation ttf-dejavu font-noto font-noto-emoji \
|
|
fontconfig
|
|
|
|
WORKDIR /app
|
|
|
|
# Bring the production node_modules over from the builder rather
|
|
# than installing fresh. This carries the already-compiled canvas
|
|
# .node binary without needing python/make/g++ in this stage.
|
|
COPY --from=builder /app/node_modules ./node_modules
|
|
COPY --from=builder /app/package*.json ./
|
|
|
|
# Application files. server.js is the entry point; dist/ is the
|
|
# Vite build output served as static; fonts/ is the result of
|
|
# fetch-fonts above.
|
|
COPY server.js ./
|
|
COPY --from=builder /app/dist ./dist
|
|
COPY --from=builder /app/fonts ./fonts
|
|
RUN mkdir -p /app/uploads /app/exports
|
|
|
|
# Refresh fontconfig's cache so the newly-copied custom fonts are
|
|
# discoverable.
|
|
RUN fc-cache -f /app/fonts || true
|
|
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/api/health || exit 1
|
|
|
|
EXPOSE 3001
|
|
ENV NODE_ENV=production
|
|
CMD ["node", "server.js"]
|