Dockerising Your Next.js App, No Fuss!

Share

  • Share on X
  • Share on LinkedIn
  • Share on Facebook
Black background with the Docker logo (a white whale) above the large white text 'NEXT.js', symbolising the concept of Dockerising a Next.js application.

Building a web application is like creating a masterpiece, but deploying it can feel like a whole different art form. 🎨 You've built a killer Next.js app, and now you're ready to show it to the world. You could use a managed platform like Vercel or Netlify, which are fantastic for quick deployments. But what if you want to be a bit more hands-on? What if you want to give your app its own cosy little home that you control completely? Enter Docker.

Containerisation with Docker has become the go-to for deploying web applications, and for good reason. It wraps your app and all its dependencies into a neat, self-contained package. This means no more "but it worked on my machine!" moments because the environment is consistent everywhere, from your laptop to the production server. It's like giving your app its own dedicated, self-contained environment.


Next.js's "Standalone" Superpower: Slimming Down for Docker!

Before we get into the nitty-gritty of the Dockerfile, there's a little secret weapon in Next.js that makes this whole process way easier: the output: 'standalone' option in your next.config.ts. It's a game-changer!

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* other config options here */
  output: "standalone",
};

export default nextConfig;

Introduced in Next.js 12, this feature is the Marie Kondo of deployment. When you enable it, Next.js tidies up your app and creates a tiny .next/standalone folder containing only the essential files needed to run your app in production, including a minimal node_modules and a server.js file. This means you don't have to lug your entire, gargantuan node_modules directory around. The result? A much lighter, self-contained package that's basically a perfect fit for a Docker container.


Docker & Next.js: The Multi-Stage Build Tango 💃

Crafting a robust Docker image for a Next.js app is a crucial step towards creating efficient and scalable web applications. We'll be using a multi-stage build approach, which is the gold standard for Docker. Think of it as a fancy cooking show: we'll have separate stations for prepping ingredients, mixing, and finally, plating the dish, but we'll only serve the final, delicious product.

This approach separates the messy build environment from the lean, clean production environment. The result is a lightweight and secure final image that won't clutter up your server. We'll break down a typical Dockerfile for a Next.js app, explaining each stage so you can containerise your own projects like a pro.


The Build Stages

Our Dockerfile is a symphony of separate acts, each with a specific purpose. We'll create temporary images to handle specific tasks, then copy only the essential bits to the final production image. This results in a small, optimised image.

.

Stage 1: The base Image

FROM node:22-alpine AS base

ARG PORT=3000

This is the foundation, our starting point. We're using a lean, mean node:22-alpine image, which is based on the famously tiny Alpine Linux. We'll tag it as base so everyone knows where the party started. The ARG PORT=3000 line sets a default PORT value, like a customisable sticker on your container.

Stage 2: The deps Image (Dependency Installation)

FROM base AS deps

RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package.json yarn.lock* ./
RUN yarn --frozen-lockfile

This stage starts from our base image and is dedicated to one thing: getting our dependencies in order. We install libc6-compat to ensure our Node environment plays nicely on Alpine, and WORKDIR /app sets our virtual office. The COPY command is key: it copies only the files needed for dependency installation. This is a crucial caching trick that saves you time if your dependencies don't change. Finally, yarn --frozen-lockfile ensures a reproducible build, which is a fancy way of saying it guarantees the exact same result every time.

Stage 3: The builder Image (Application Build)

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1
ENV ENV=production

RUN yarn build

Our next act, the builder stage, is where the main event happens. We're not reinstalling dependencies; instead, we're smartly grabbing the node_modules folder from the deps stage. We then copy in all of our source code (COPY . .). We set a couple of environment variables (ENV) to politely tell Next.js to stop phoning home and ensure our app knows it’s time to get serious. Finally, yarn build kicks off the Next.js compilation.

Stage 4: The runner Image (Production)

FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV PORT=$PORT
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE $PORT

ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

This is the grand finale, the final image we've been working towards! It starts from the lean base image to ensure our final product is as small and secure as possible.

We set more environment variables, including NODE_ENV=production for high-performance mode. The addgroup and adduser commands are a huge security win: we're creating a new, non-root user (nextjs) to run our app, which means it won't have more permissions than it needs.

The COPY commands grab our essentials from the builder stage, and the --chown flag ensures these files are owned by our new, non-root user. The USER nextjs command locks it down, ensuring everything after this point runs with limited permissions.

EXPOSE $PORT is a memo for Docker, letting it know which port to listen on, and ENV HOSTNAME="0.0.0.0" is a quirky detail that ensures Next.js listens on all network interfaces.

Finally, CMD ["node", "server.js"] is the command that gets your Next.js app up and running.


To see the complete Dockerfile and more important context, check out this GitHub repository:

nextjs-docker-image

.


Parting Words... for Now! 👋

See? Containerising your Next.js app doesn't have to be scary. By embracing multi-stage builds and Next.js's standalone output, you can create a lightweight, secure, and reproducible deployment. Now you've got the power to take your app anywhere, from your local machine to a cloud server, and know it will behave exactly the same way. So go forth and containerise—your future self will thank you!

Share

  • Share on X
  • Share on LinkedIn
  • Share on Facebook
Dockerising Your Next.js App, No Fuss! | Rich in Media