Skip to content

Distroless + sharp

A minimal Next.js app that uses sharp (the dep next/image server-side optimization pulls in), compiled to a single binary and shipped on gcr.io/distroless/cc-debian12. Final image ~80MB.

This is the recipe and the e2e regression test — CI builds and boots it on every PR.

The full working source is in examples/sharp/ in the repo.

# Stage 1: Build the binary
FROM oven/bun:1.3.14 AS builder
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun install --no-save
COPY . .
# Install fontconfig + fonts in the builder so we can copy the
# artifacts into the distroless runner. The build itself doesn't
# need them, but fc-cache populates /var/cache/fontconfig with a
# fresh font cache the runner can use directly.
RUN apt-get update \
&& apt-get install -y --no-install-recommends fontconfig fonts-dejavu-core \
&& fc-cache -f \
&& rm -rf /var/lib/apt/lists/*
RUN bun run build
# Stage 2: Run the compiled binary
FROM gcr.io/distroless/cc-debian12:nonroot AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV HOSTNAME="0.0.0.0"
# Self-contained binary
COPY --from=builder --chown=nonroot:nonroot /app/server ./
# Fontconfig + fonts + the C libs libvips dlopens for text rendering.
# cc-debian12 already has libstdc++ and libgcc; these complete the
# set.
COPY --from=builder /etc/fonts /etc/fonts
COPY --from=builder /usr/share/fontconfig /usr/share/fontconfig
COPY --from=builder /usr/share/fonts /usr/share/fonts
COPY --from=builder /var/cache/fontconfig /var/cache/fontconfig
COPY --from=builder /usr/lib/x86_64-linux-gnu/libfontconfig.so* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libfreetype.so* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libexpat.so* /usr/lib/x86_64-linux-gnu/
COPY --from=builder /usr/lib/x86_64-linux-gnu/libpng16.so* /usr/lib/x86_64-linux-gnu/
EXPOSE 3000
CMD ["./server"]
Block Why
oven/bun:1.3.14 builder Builds the binary. Debian-based so apt-get works.
apt-get install fontconfig fonts-dejavu-core libvips needs fontconfig + a font face for text rendering (watermarks, captions). Skip if you only do non-text sharp ops.
fc-cache -f Populates /var/cache/fontconfig so the runner doesn’t have to rebuild the cache on first text-render.
bun run build next build with the adapter → ./server.
gcr.io/distroless/cc-debian12:nonroot Has glibc + libstdc++ + libgcc (the cc variant). The plain base variant lacks libstdc++ and sharp won’t load.
COPY /etc/fonts /usr/share/fontconfig ... /var/cache/fontconfig All the data fontconfig expects. Drop if you don’t render text.
COPY libfontconfig + libfreetype + libexpat + libpng16 Runtime libs libvips dlopens. Same caveat as fonts.

Drop the apt-get install fontconfig fonts-dejavu-core, the fc-cache line, and all the COPY /etc/fonts-and-friends blocks. Shrinks the image by ~5MB.

Swap fonts-dejavu-core for whatever package provides the font your watermark text uses:

  • fonts-liberation (Arial/Helvetica/Times metric-compatible)
  • fonts-noto-core (Noto fonts, broad Unicode coverage)
  • fonts-roboto

Set NBC_TARGET=bun-linux-arm64 on the build. See Cross-compilation.

  • alpine + bun runtime instead. If you want to skip compilation altogether: use oven/bun:1.3.14-alpine, apk add fontconfig ttf-dejavu, COPY .next/standalone ., CMD ["bun", "apps/web/server.js"]. Larger image (~150MB), no compile step, same resolution behavior because it’s the standard bun runtime.

  • Monorepo layout with this same recipe — see Monorepo + sharp.