Back to blog

11/26/2023

Running Next.js Standalone App With Sharp Inside Docker

Next.js can be built in "standalone" mode, where all the files needed for running an app in production are built and moved into .next/standalone directory. After doing just that, I had trouble getting sharp to work, which is used by Next.js for image optimization. There's even a log messages warning you that Sharp is missing in production.

Simply trying to install sharp, locally and for CI on GitHub Actions, has lead me down a rabbit hole. From following Vercel's official Next.js in Docker example, which is incorrect (they automatically install sharp for you on their platform), trying to get libvips to compile, trying different Docker base images and different libraries, glibc and musl, skimming tons of GitHub issues and discussions, rewriting my Dockerfile 5 times...

Long story short, here's the partial Dockerfile for my current working solution. This is for my pnpm/turborepo-based monorepo. To be fair, my actual Dockerfile is bigger, which is at least partially the reason why I was struggling.

FROM node:20.10-bullseye-slim as base# Install dependencies only when neededFROM base AS depsWORKDIR /workspace# This is necessary to run sharpRUN npm install -g --arch=x64 --platform=linux --libc=glibc [email protected]# Other dependencies...# Production image, copy all the files and run Next.jsFROM base AS runnerWORKDIR /workspaceENV NODE_ENV production# ENV NEXT_TELEMETRY_DISABLED 1# Path to global installation of sharpENV NEXT_SHARP_PATH=/usr/local/lib/node_modules/sharp# Copy dependenciesCOPY --from=deps --chown=nextjs:nodejs /usr/local/lib/node_modules/sharp /usr/local/lib/node_modules/sharpRUN addgroup --system --gid 1001 nodejs && \    adduser --system --uid 1001 nextjs# Set the correct permission for prerender cacheRUN mkdir .next && \    chown nextjs:nodejs .next && \    mkdir -p ./apps/site/.next && \    chown nextjs:nodejs ./apps/site/.next# Copy Next.js built artifactsCOPY --chown=nextjs:nodejs ./apps/site/.next/standalone ./COPY --chown=nextjs:nodejs ./apps/site/.next/static ./.next/staticCOPY --chown=nextjs:nodejs ./apps/site/.next/static ./apps/site/.next/static# Include public directoryCOPY ./apps/site/public ./apps/site/publicUSER nextjsEXPOSE 3000ENV PORT 3000# Set hostname to localhostENV HOSTNAME "0.0.0.0"# Run the appCMD ["node", "apps/site/server.js"]

The first change was to use sharp@next, a pre-release version of sharp (Enhancement: Major overhaul of installation). This made it easier to build on GitHub Actions. Weirdly, it works without needing to install any extra dependencies on the action's Ubuntu-based runner.

I used to write giant Dockerfiles that build the entire project inside a container, but not doing that anymore. Creating Docker images is generally easier by building the project on the host or CI server (had pretty good experience with building and publishing using GitHub Actions), then copying artifacts to the image. Installing all the dependencies inside a Dockerfile is tedious, and I prefer to avoid it.

I had to install sharp globally. Because of how pnpm works, it requires you to install sharp manually (see this and this). That didn't work for me, so I installed it with npm and copied it to the production stage from /usr/local/lib/node_modules.

All of this seems to work, for now.

Follow me on X/Twitter!

Subscribe to our newsletter

Join our newsletter for regular updates. No spam ever.