4

I have a nextjs project that I wish to run using Docker and nginx.

I wish to use nginx that connects to nextjs behind the scenes (only nginx can talk to nextjs, user needs to talk to nginx to talk to nextjs).

Assuming it's standard nextjs project structure and the dockerfile content (provided below), Is there a way to use nginx in docker with nextjs?

I'm aware I can use Docker-compose. But I'd like to keep it under one docker image. Since I plan to push the image to heroku web hosting.

NOTE: I'm using Server Side Rendering

dockerfile

# Base on offical Node.js Alpine image
FROM node:latest as builder

# Set working directory
WORKDIR /usr/app

# install node-prune (https://github.com/tj/node-prune)
RUN curl -sfL https://install.goreleaser.com/github.com/tj/node-prune.sh | bash -s -- -b /usr/local/bin

# Copy package.json and package-lock.json before other files
# Utilise Docker cache to save re-installing dependencies if unchanged
COPY package.json ./
COPY yarn.lock ./

# Install dependencies
RUN yarn install --frozen-lockfile

# Copy all files
COPY ./ ./

# Build app
RUN yarn build

# remove development dependencies
RUN yarn install --production

# run node prune. Reduce node_modules size
RUN /usr/local/bin/node-prune

####################################################### 

FROM node:alpine

WORKDIR /usr/app

# COPY package.json next.config.js .env* ./
# COPY --from=builder /usr/app/public ./public
COPY --from=builder /usr/app/.next ./.next
COPY --from=builder /usr/app/node_modules ./node_modules

EXPOSE 3000

CMD ["node_modules/.bin/next", "start"]

dockerfile inspired by https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile.multistage

Edit: nginx default.conf

upstream nextjs_upstream {
  server nextjs:3000;

  # We could add additional servers here for load-balancing
}

server {
  listen 80 default_server;

  server_name _;

  server_tokens off;

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_set_header Host $host;
  proxy_cache_bypass $http_upgrade;

  location / {
    proxy_pass http://nextjs_upstream;
  }
}
Clumsy-Coder
  • 542
  • 1
  • 7
  • 20
  • I don't know about nextjs, but if it does stuff on the server side and you want to hide it behind nginx, then let nextjs listen on localhost only on an internal port that you don't export from the docker image and let only nginx listen to the exported port and forwad to the internal port. See https://stackoverflow.com/questions/5009324/node-js-nginx-what-now?rq=1 – Tomáš Pospíšek Dec 16 '20 at 21:55
  • That's what I've looking at. Run NextJS at port 3000, have nginx port forward to NextJS. But I don't know how to do it in Docker. – Clumsy-Coder Dec 16 '20 at 22:45
  • Since in your example above nginx is running on port 80, you have to `EXPOSE 80`. Then when you run the container, you need to tell docker where to bind that port 80 to. F.ex. `docker run -p 80:80 ` would tell docker to make the exposed port 80 (on to docker container) available at port 80 (on the host). – Tomáš Pospíšek Dec 18 '20 at 09:19

2 Answers2

2

In order to be able to use Nginx and NextJS together in a single Docker container without using Docker-Compose, you need to use Supervisord

Supervisor is a client/server system that allows its users to control a number of processes on UNIX-like operating systems.

The issue wasn't nginx config or the dockerfile. It was running both nginx and nextjs when starting the container. Since I couldn't find a way to run both, using supervisord was the tool I needed.

The following will be needed for it to work

Dockerfile

# Base on offical Node.js Alpine image
FROM node:latest as builder

# Set working directory
WORKDIR /usr/app

# Copy package.json and package-lock.json before other files
# Utilise Docker cache to save re-installing dependencies if unchanged
COPY package.json ./
COPY yarn.lock ./

# Install dependencies
RUN yarn install --frozen-lockfile

# Copy all files
COPY ./ ./

# Build app
RUN yarn build

# remove development dependencies
RUN yarn install --production

####################################################### 

FROM nginx:alpine

WORKDIR /usr/app

RUN apk add nodejs-current npm supervisor
RUN mkdir mkdir -p /var/log/supervisor && mkdir -p /etc/supervisor/conf.d

# Remove any existing config files
RUN rm /etc/nginx/conf.d/*

# Copy nginx config files
# *.conf files in conf.d/ dir get included in main config
COPY ./.nginx/default.conf /etc/nginx/conf.d/

# COPY package.json next.config.js .env* ./
# COPY --from=builder /usr/app/public ./public
COPY --from=builder /usr/app/.next ./.next
COPY --from=builder /usr/app/node_modules ./node_modules

# supervisor base configuration
ADD supervisor.conf /etc/supervisor.conf

# replace $PORT in nginx config (provided by executior) and start supervisord (run nextjs and nginx)
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf && \
  supervisord -c /etc/supervisor.conf

supervisor.conf

[supervisord]
nodaemon=true

[program:nextjs]
directory=/usr/app
command=node_modules/.bin/next start
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
autorestart=true

[program:nginx]
command=nginx -g 'daemon off;'
killasgroup=true
stopasgroup=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log
stderr_logfile=/var/log/supervisor/%(program_name)s.log
autorestart=true

nginx config (default.conf)

upstream nextjs_upstream {
  server localhost:3000;

  # We could add additional servers here for load-balancing
}

server {
  listen $PORT default_server;

  server_name _;

  server_tokens off;

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection 'upgrade';
  proxy_set_header Host $host;
  proxy_cache_bypass $http_upgrade;

  location / {
    proxy_pass http://nextjs_upstream;
  }
}

NOTE: using nginx as a reverse proxy. NextJS will be running on port 3000. The user won't be able to reach it directly. It has to go through nginx.


Building docker image

docker build -t nextjs-img -f ./dockerfile .

Running docker container

docker run --rm -e 'PORT=80' -p 8080:80 -d --name nextjs-img nextjs-img:latest

Go to localhost:8080

Clumsy-Coder
  • 542
  • 1
  • 7
  • 20
0

You can use docker-compose to run Nginx and your NextJS app in Docker container, then have a bridge network between those containers.

then in nginx conf:

server {
    listen       80;
    listen       443 ssl;

    server_name localhost [dns].com;

    ssl_certificate certs/cert.pem;
    ssl_certificate_key certs/cert.key;

    location / {
        proxy_pass http://nextApplication; // name based on your docker-compose file
        proxy_http_version 1.1;
        proxy_read_timeout      90;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host [dns].com;
        proxy_cache_bypass $http_upgrade;
    }
}

I didn't use upstream script, loadbalancer is on top of nginx (at the cloud provider level)

Darryl RN
  • 7,432
  • 4
  • 26
  • 46