So you've built a Next.js app, and it's ready to be deployed... but where do you host it? If you're looking to steer clear of the big cloud providers for reasons of cost, privacy, or simply because you want more control, then this blog post is for you.
I'm going to walk you through the process of self-hosting your Next.js app on a Hetzner VM using Kamal. Of course, you can use any VM provider you like, but I'm going to use Hetzner as an example because I've been using them for years and I'm very happy with their service.
What is Kamal?
Before we get started, let's talk about Kamal. Kamal is a simple CLI tool developed by the folks over at 37 Signals that makes it easy to deploy your apps. It provides a simple configuration file that you can use to define your app's environment, and it takes care of the rest.
We're going to use a subset of Kamal's features to deploy our Next.js app. If you want to learn more about Kamal, check out their documentation.
Prerequisites
Before we get started, we need a few things. First, we need to install Kamal on our machine. Second, we need to create a new Next.js app. Finally, we need to Dockerize our Next.js app. Finally, we need a docker registry to store our Docker images, I'm going to use Docker Hub for this tutorial, but you can use any registry you like.
Install Kamal
To install Kamal, run the following command:
Create a new Next.js app
To create a new Next.js app, run the following command:
Setting up health checks
Before we can deploy our Next.js app, we need to set up health checks for it. This is necessary because Kamal uses health checks to determine whether our app is running or not. If our app is not running, Kamal will restart it automatically.
We need to define a custom endpoint that returns a 200
status code when our app is running and a 500
status code when it's not. To do that, open the app/up/route.ts
file in the root of your project and add the following lines to it:
Over here, we are returning a 200
status code all the time. Ideally, we should check our app's health and return a 500
status code if it's not healthy. But for the sake of this tutorial, we're going to keep things simple.
Dockerzing our Next.js app
Kamal works with any app that can be run in a Docker container. So, before we can deploy our Next.js app, we need to Dockerize it.
First, open the next.config.js
file in the root of your project and add the following lines to it:
Next, let's create a .dockerignore
file in the root of our project and add the following lines to it. This will ensure that we don't include any unnecessary files in our Docker image and avoid some nasty surprises when we deploy our app.
Next, let's create a Dockerfile
at the root of our project and add the following lines to it.
This Dockerfile is based on this example from the Next.js docs. It's a bit more complicated than the one you might be used to, but it's necessary to ensure that our app runs correctly in a Docker container.
Let me explain what's going on here:
-
We're using the
node:20-alpine
image as our base image. This is a very small image that contains only the bare minimum to run Node.js apps. -
We have four stages in our Dockerfile:
base
,deps
,builder
, andrunner
. Thebase
is our base image that we use for all the other stages. Thedeps
stage is where we install our app's dependencies. Thebuilder
stage is where we build our app. Finally, therunner
stage is where we run our app. -
Finally, we have a
CMD
instruction that tells Docker how to run our app. In this case, we're telling Docker to runnode server.js
.
Building our Docker image
Now that we have our Dockerfile, we can build our Docker image by running the following command:
To test that our Docker image works correctly, we can run the following command:
If everything went well, you should see the following output:
If you open your browser and navigate to http://localhost:3000
, you should see the default Next.js page or your app if you've already built it.
Setting up our VM
Ok, we got our Docker image working locally, now it's time to deploy it to our VM. To do that, we need to set up our VM first.
This is simple, head over to Hetzner or your cloud provider of choice and create a new VM. I'm going to create an Ubuntu 20.04
VM with 2GB
of RAM and 2VCPU
which will cost me a whopping 3.85€
per month.
Make sure to configure your VM with your SSH key so that you can SSH into it later, and you're good to go.
Ah don't forget to copy your VM's IP address, we'll need it later. For the sake of this tutorial, let's assume that our VM's IP address is 37.37.37.37
.
Setting up Kamal
I'm going to assume you've been following along and have Kamal
installed on your machine. If not, go ahead and install it now. Once you've done that, run the following command to initialize Kamal:
This will create a config/deploy.yml
file, a .env
file if you don't already have one, a Dockerfile
if you don't already have one, and a .kamal
directory where we can define hooks that will be executed before and after our app is deployed.
Open the .env
file and add your Docker Hub username and password to it:
Next, update your config/deploy.yml
file to look like this:
Setting up our VM
That's it for Kamal, we're done with the configuration. This is where things get very simple, all thanks to Kamal. All we need to do is to run the following command:
This will SSH into our VM and set up everything that we need to deploy our app. Give it a few seconds to finish, and you're done.
Deploying our app
Last but not least, we need to deploy our app. To do that, run the following command:
Give it a few seconds to finish, and you're done. You can now open your browser and navigate to your VM's IP address on port 80
to see your app running. If you've followed along, you should see the default Next.js page or your app if you've already built it.
Setting up a custom domain
There are a few ways to set up a custom domain for your app. You can configure the built-in Traefik reverse proxy to handle this for you, or you can use a third-party service like Cloudflare to handle this for you.
I usually default to Cloudflare because it's very easy to set up and provides a ton of other useful features like DNSSEC, DDoS protection, and more.
To set up a custom domain using Cloudflare, head over to Cloudflare and create an account if you don't already have one. Once you've done that, add your domain to Cloudflare, head over to your domain registrar and update your domain's nameservers to point to Cloudflare's nameservers. This will allow Cloudflare to handle all the DNS requests for your domain.
Finally, head over to your Cloudflare dashboard and add a new A
record for your domain. Set the Name
field to @
and the IPv4 address
field to your VM's IP address. This will tell Cloudflare to route all requests for your domain to your VM. Make sure to set the Proxy status
to Proxied
so that Cloudflare can handle all the requests for your domain.
Give it a few minutes for the changes to propagate, and you're done. You can now open your browser and navigate to your domain to see your app running.
Conclusion
That's it, we're done. We've successfully deployed our Next.js app to a Hetzner VM using Kamal. I hope you found this tutorial useful, and I hope it helped you get started with self-hosting your apps.
If you're interested in learning more about Kamal, check out their documentation. They have a ton of useful information on how to use Kamal to deploy your apps.
Until next time, happy coding!
Interested in LogSnag?