
How to deploy the Next.js app to a VPS server
As mentioned in the previous post, I built a new blog post website with Next.js. Now I'd like to deploy it to a VPS server.
A few things come to my mind,
- how to build the app
- how to push the built package
- how to boot the app in VPS
- how to expose the app in VPS
- automation
Let me walk you through one by one.
Build Next.js app
Basically following the Next.js self-hosting deployment docs, I went for a docker building as it provides cross-platform consistency; the only thing worth to mention here is that not forget to change the output in the next.config.js
to standalone
.
Push the docker image
docker build --platform linux/amd64 -t ghcr.io/xavierchow/xblog:0.0.1 .
After the building step above, now I have a docker image which can be run anywhere with docker environment. Here I use the Github package https://gchr.io
as the docker registry.
Caveats:
- You need to replace the tag name(
ghcr.io/xavierchow/xblog
), but the prefixghcr.io
has to be kept as part of your tag name. - As my VPS is linux amd64 but I'm on OSX for local development so I specify the platform parameter when run building, you probably need to change it according to your case.
Then I can push it to github registry, remember it needs to run docker login
before docker push
.
docker login
docker push ghcr.io/xavierchow/xblog:0.0.1
We can see the package has been pushed to github as follows,
Fetch the pacakge and boot it
Now I can pull the image from my VPS server with
docker pull ghcr.io/xavierchow/xblog:0.0.1
and run it as follows,
docker run -p 3000:3000 -e MARKDOWN_FOLDER=/app/myposts/ \
-v /blog_posts:/app/myposts \
-v /blog_posts/images:/app/public/image ghcr.io/xavierchow/xblog:0.0.1
The environment variable MARKDOWN_FOLDER
is used to mount the folder containing the markdonw files.
Now the app is running with port 3000 on my server, to make it accessible via standard http protocol(i.e. port 80), I use nginx as the reverse proxy.
The following is the nginx config file app.conf
, you can see I forward the /blog
to the localhost 3000 port.
server {
listen 80;
listen [::]:80;
location /blog {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
Automation
Last but not least, the app is still under development and it gets updates from time to time, I need a pipeline to avoid manual commands like above steps. Github action is a good choice, I added a workflow here, so whenever I push a new git tag, it runs the building and pushes the image to the registry.
On the server side, I have two services need to take care, nginx and the blog app. So I use docker compose for an easier management.
services:
nginx:
build: ./nginx_home
container_name: nginx
ports:
- "80:80"
networks:
- webapp
restart: always
blog:
image: ghcr.io/xavierchow/xblog:0.0.1
container_name: xblog
ports:
- "3000:3000"
environment:
MARKDOWN_FOLDER: /app/myposts/
volumes:
- /root/blog_posts/posts:/app/myposts
- /root/blog_posts/images:/app/public/images
networks:
- webapp
restart: always
networks:
webapp: null
Under the nginx_home
directory, I have the nginx Dockerfile and the aforementioned app.conf
.
FROM nginx
COPY app.conf /etc/nginx/conf.d/app.conf
Bear in mind, as everything is inside docker, the 'localhost' in the app.conf needs to be replaced with the container name.
server {
...
location /blog {
proxy_pass http://xblog:3000;
...
}
As you see the finally flow only needs 2 manual steps.
Of couse, it could be further automated by other approaches like webhook so I don't need to update the docker-compose.yaml on server and re-run the docker compose
but I think it's good for now.