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 loginInitiate sample Next.js app
Official Next.js doc has sample app with docker:
npx create-next-app --example with-docker nextjs-dockerAfter 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:dockerThis 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 ~/traefikNext we need to create network for Traefik to communicate with other containers, and it should be exposed externally.
docker create network traefikNow, let’s create docker-compose.yml
vi docker-compose.ymlnetworks:
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-stoppedAbove 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 -dVerify the container is running successfully
docker psYou 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-dockerThen 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-stoppedNote 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 -dVerify 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:dockerThen go to your cloud instance and run below:
docker pull your_username/nextjs-docker:latest && docker compose -f ~/nextjs-docker/docker-compose.yml up -dLastly, verify the change by visiting www.your-domain.com.