Develop and Deploy Next.js App Using Docker Compose (Traefik/Docker Hub)

Before Docker, the way to deploy a node app usually requires uploading/downloading latest files using git or file transfer protocol then re-run the app in cloud. With Docker we can build docker image from local development then upload to Docker Hub. With Docker Compose setup in cloud, we can easily fetch latest image and re-run the container. This architecture can be scaled with more complicated CI/CD framework and Kubernetes clusters for large scale application and zero downtime deployment.

Install Docker Desktop

Simply download and install Docker Desktop from: https://www.docker.com/products/docker-desktop/

Register account (if not already)

A Docker Hub account is required in order to upload images.

Login to Docker user CLI

Run below command to login in to Docker for using docker push command.

Docker login

Initiate sample Next.js app

Official Next.js doc has sample app with docker:

npx create-next-app --example with-docker nextjs-docker

After going through prompts, Dockerfile should be already inside project folder.

Add build&upload script inside package.json

Official Next.js doc has instructions for building and running Next.js with Docker, however, let’s do it more intuitively. Add below script to package.json

{
  ...
  "scripts": {
    "upload:docker": "docker build -t your_username/nextjs-docker . && docker push your_username/nextjs-docker"
  }
  ...
}

Replace nextjs-docker with your preferred app’s name.

Build docker image and upload

Simply run below command:

npm run upload:docker

This will build docker image then upload to Docker Hub with latest tag.

Github and Github Action

In case github is being used as source control, above step can be integrated with Github Action and trigger on commit or PR merge.

Deploy docker image to cloud instance with Docker Compose and Traefik

Prerequisite

  • Linux server
  • Docker Engine installed with Docker Compose plugin

Setup Traefic (skip if already done)

Let’s make a folder for Traefic:

mkdir ~/traefik && cd ~/traefik

Next we need to create network for Traefik to communicate with other containers, and it should be exposed externally.

docker create network traefik

Now, let’s create docker-compose.yml

vi docker-compose.yml
networks:
  traefik:
    external: true

volumes:
  traefik-certificates:

services:
  traefik:
    image: "traefik:latest"
    command:
      - "--log.level=DEBUG"
      - "--accesslog=true"
      - "--api.dashboard=true"
      - "--api.insecure=true"
      - "--ping=true"
      - "--ping.entrypoint=ping"
      - "--entryPoints.ping.address=:8082"
      - "--entryPoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entryPoints.websecure.address=:443"
      - "--providers.docker=true"
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.exposedByDefault=false"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      # For requesting dev cert (if prod cert has issue during development)
      # - "--certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@bill-min.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/etc/traefik/acme/acme.json"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - traefik-certificates:/etc/traefik/acme
    networks:
      - traefik
    ports:
      - "80:80"
      - "443:443"
    healthcheck:
      test: ["CMD", "wget", "http://localhost:8082/ping","--spider"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.dashboard.rule=Host(`traefik.your-domain.com`)"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
      - "traefik.http.routers.dashboard.middlewares=authtraefik"
      - "traefik.http.middlewares.authtraefik.basicauth.users=your_username:{SHA}your_hash"
    restart: unless-stopped

Above config also adds Traefik Dashboard and https with letsencrypt.

Replace your-domain with your actual domain name and setup correct DNS.

Replace your_username and {SHA}your_hash with ones generated from https://hostingcanada.org/htpasswd-generator/

Finally, lets start Traefik container.

docker compose up -d

Verify the container is running successfully

docker ps

You can also check Traefik dashboard by visiting https://traefik.your-domain.com. (you will need to enter the username and password used when generating htpasswd)

Run uploaded Next.js docker image

First let’s add a folder:

mkdir ~/nextjs-docker && cd ~/nextjs-docker

Then add docker-compose.yml:

networks:
  traefik:
    external: true

services:
  app:
    image: your_username/nextjs-docker:latest
    ports:
      - 3000:3000
    networks:
      - traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.nextjs.rule=Host(`www.your-domain.com`)"
      - "traefik.http.routers.nextjs.service=nextjs"
      - "traefik.http.routers.nextjs.entrypoints=websecure"
      - "traefik.http.services.nextjs.loadbalancer.server.port=3000"
      - "traefik.http.services.nextjs.loadbalancer.passhostheader=true"
      - "traefik.http.routers.nextjs.tls=true"
      - "traefik.http.routers.nextjs.tls.certresolver=letsencrypt"
      - "traefik.http.routers.nextjs.middlewares=compresstraefik"
      - "traefik.http.middlewares.compresstraefik.compress=true"
      - "traefik.docker.network=traefik"
    restart: unless-stopped

Note that, the Next.js app need to run inside same network as Traefic. Also replace your-domain with real domain.

Run below to start service:

docker compose up -d

Verify by visiting www.your-domain.com see if everything works properly.

Develop and update service with latest change

Try change something in your local Next.js app, and run upload script again (or using Github Action):

npm run upload:docker

Then go to your cloud instance and run below:

docker pull your_username/nextjs-docker:latest && docker compose -f ~/nextjs-docker/docker-compose.yml up -d

Lastly, verify the change by visiting www.your-domain.com.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *