# 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://package-mirror.liara.ir/repository/npm/ 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 COPY scripts/check-image-runtime.mjs ./scripts/check-image-runtime.mjs RUN node scripts/check-image-runtime.mjs --require-source FROM dependencies AS verification COPY . . RUN npm run lint RUN npm run typecheck RUN --mount=type=cache,id=portfolio-next-node22,target=/app/.next/cache,sharing=locked \ npm run build RUN npm test 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=verification --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=verification --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=verification --chown=nextjs:nodejs /app/public ./public COPY --from=image-build /opt/vips/lib /opt/vips/lib # Explicitly retain the custom binding even if Next's file tracer misses it. COPY --from=dependencies --chown=nextjs:nodejs /app/node_modules/sharp/src/build/Release/*.node ./node_modules/sharp/src/build/Release/ COPY scripts/check-image-runtime.mjs ./scripts/check-image-runtime.mjs RUN node scripts/check-image-runtime.mjs --require-source 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"]