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
.
Leave a Reply