Hot reloading Next.js with Docker in development

Using Docker may seem like overkill for a local Next.js project, and indeed, perhaps it is. However there as some unique requirements to get hot reloading working in a Docker development environment which are worthy of note. I hope this configuration will be useful either in isolation, or when integrating a Next.js app into a larger service architecture.

Prerequisites:

  • Docker installed on your local machine

Step 1. Create a Dockerfile

In the root of your Next.js project, create your Dockerfile. See the inline comments for a description of each step:

# Dockerfile

# Use node alpine as it's a small node image
FROM node:alpine

# Create the directory on the node image 
# where our Next.js app will live
RUN mkdir -p /app

# Set /app as the working directory
WORKDIR /app

# Copy package.json and package-lock.json
# to the /app working directory
COPY package*.json /app

# Install dependencies in /app
RUN yarn install

# Copy the rest of our Next.js folder into /app
COPY . /app

# Ensure port 3000 is accessible to our system
EXPOSE 3000

# Run yarn dev, as we would via the command line 
CMD ["yarn", "dev"]

Step 2. Create docker-compose.yml

Again in the root of your Next.js project, create docker-compose.yml:

# docker-compose.yml

version: "3"

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: web
    restart: always
    volumes:
      - ./:/app
      - /app/node_modules
      - /app/.next
    ports:
      - 3000:3000

This is a very simple compose file, as we only have one service. The key point is the volumes attribute. By referencing the root of our app (./:/app), our node modules folder (/app/node_modules), and Next.js (/app/.next) we ensure our Next.js app can 'see' the changes we make through our Docker image, and therefore trigger hot reloading when we change a file.

Step 3. Update next.config.js

The final change for hot reloading is to add webpack middleware so we poll for changes. Update your next.config.js file to include the following:

// next.config.js

module.exports = {
  webpackDevMiddleware: config => {
    config.watchOptions = {
      poll: 1000,
      aggregateTimeout: 300,
    }
    return config
  },
  ...
}

Step 4. Build your image

Now you're ready to build your Docker image! Run compose with the --build switch and in a minute or so you should have a fresh node image running your Next.js app in all it's containerised glory (thanks to Docker's caching mechanisms, subsequent builds are a lot faster).

$ docker-compose up -d --build

Fire up localhost:3000 in your browser, and any changes you make will be instantly reflected. No need to docker-compose down / up every time. Pretty sweet.

References

Many thanks to these posts whose authors pointed me in the right direction:

 
James Chambers
Good morning. I'm James.

I send a twice-monthly newsletter about building indie software products. It's called Build Notes, and you can sign up below.

© 2014-2021 James Chambers