94 lines
3.9 KiB
Docker
94 lines
3.9 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
|
|
FROM node:22-alpine AS image-runtime
|
|
|
|
# Distribution libraries support baseline CPUs and dispatch optional SIMD at runtime.
|
|
RUN apk add --no-cache glib expat libjpeg-turbo libpng libwebp tiff lcms2 librsvg libstdc++
|
|
|
|
FROM image-runtime AS image-build
|
|
RUN apk add --no-cache python3 make g++ meson ninja pkgconf xz \
|
|
glib-dev expat-dev libjpeg-turbo-dev libpng-dev libwebp-dev tiff-dev lcms2-dev librsvg-dev
|
|
|
|
ADD --checksum=sha256:3c41e1d5458081bfa4a5bc54e116c46259c75c6760a18027764555632b9dda3e https://github.com/libvips/libvips/releases/download/v8.18.6/vips-8.18.6.tar.xz /tmp/vips.tar.xz
|
|
RUN if [ "$(uname -m)" = x86_64 ]; then export CFLAGS='-O2 -march=x86-64 -mtune=generic' CXXFLAGS='-O2 -march=x86-64 -mtune=generic'; fi \
|
|
&& mkdir /tmp/vips && tar -xJf /tmp/vips.tar.xz -C /tmp/vips --strip-components=1 \
|
|
&& meson setup /tmp/vips/build /tmp/vips --prefix=/opt/vips --libdir=lib \
|
|
--buildtype=release --auto-features=disabled \
|
|
-Dhighway=disabled -Dorc=disabled -Dcplusplus=true -Dexamples=false \
|
|
-Djpeg=enabled -Dpng=enabled -Dwebp=enabled -Dtiff=enabled -Dlcms=enabled -Drsvg=enabled -Dzlib=enabled \
|
|
&& meson compile -C /tmp/vips/build -j 2 \
|
|
&& meson install -C /tmp/vips/build
|
|
|
|
FROM image-build AS dependencies
|
|
|
|
WORKDIR /app
|
|
|
|
ARG NPM_REGISTRY=https://registry.npmjs.org/
|
|
ENV NEXT_TELEMETRY_DISABLED=1 \
|
|
PKG_CONFIG_PATH=/opt/vips/lib/pkgconfig \
|
|
LD_LIBRARY_PATH=/opt/vips/lib \
|
|
npm_config_nodedir=/usr/local
|
|
|
|
COPY package.json package-lock.json ./
|
|
RUN --mount=type=cache,id=portfolio-npm-node22,target=/root/.npm,sharing=locked \
|
|
npm ci \
|
|
--registry="${NPM_REGISTRY}" \
|
|
--replace-registry-host=registry.npmjs.org \
|
|
--include=optional
|
|
|
|
# Compile Sharp against our baseline libvips, not its x86-64-v2 prebuilt binary.
|
|
RUN if [ "$(uname -m)" = x86_64 ]; then export CFLAGS='-O2 -march=x86-64 -mtune=generic' CXXFLAGS='-O2 -march=x86-64 -mtune=generic'; fi \
|
|
&& npm explore sharp -- npm run build
|
|
|
|
|
|
FROM dependencies AS build
|
|
|
|
# Only application inputs invalidate compilation. Tests and docs stay outside.
|
|
COPY next.config.mjs tsconfig.json next-env.d.ts ./
|
|
COPY src ./src
|
|
COPY public ./public
|
|
COPY migrations ./migrations
|
|
COPY scripts/maintenance.ts scripts/build-maintenance.mjs scripts/prepare-standalone.mjs ./scripts/
|
|
RUN --mount=type=cache,id=portfolio-next-node22,target=/app/.next/cache,sharing=locked \
|
|
npm run build
|
|
|
|
# Carry the exact shared-library dependency closure from the working build stage.
|
|
# Development packages can provide libraries that are absent from image-runtime.
|
|
FROM dependencies AS native-runtime-libraries
|
|
RUN mkdir -p /runtime-libs \
|
|
&& (ldd /app/node_modules/sharp/src/build/Release/*.node /opt/vips/lib/libvips*.so* 2>/dev/null || true) \
|
|
| awk '/=> \// { print $3 } /^[[:space:]]*\// { print $1 }' \
|
|
| sort -u \
|
|
| while IFS= read -r library; do \
|
|
mkdir -p "/runtime-libs$(dirname "$library")"; \
|
|
cp -L "$library" "/runtime-libs$library"; \
|
|
done
|
|
|
|
FROM image-runtime AS runtime
|
|
|
|
WORKDIR /app
|
|
ENV NODE_ENV=production \
|
|
NEXT_TELEMETRY_DISABLED=1 \
|
|
HOSTNAME=0.0.0.0 \
|
|
PORT=3000 \
|
|
DATA_DIR=/data \
|
|
LD_LIBRARY_PATH=/opt/vips/lib
|
|
|
|
RUN addgroup --system --gid 1001 nodejs \
|
|
&& adduser --system --uid 1001 nextjs
|
|
|
|
COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./
|
|
COPY --from=image-build /opt/vips/lib /opt/vips/lib
|
|
# Preserve the package and its native dependency closure beyond Next's tracing.
|
|
COPY --from=dependencies --chown=nextjs:nodejs /app/node_modules/sharp/ ./node_modules/sharp/
|
|
COPY --from=native-runtime-libraries /runtime-libs/ /
|
|
RUN mkdir -p .next/cache /data && chown -R nextjs:nodejs .next/cache /data
|
|
|
|
USER nextjs
|
|
EXPOSE 3000
|
|
|
|
HEALTHCHECK --interval=2s --timeout=2s --start-period=3s --retries=30 \
|
|
CMD ["node", "-e", "fetch('http://127.0.0.1:3000/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
|
|
|
|
CMD ["node", "server.js"]
|