Category: Next.js

  • 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.