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.
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.
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-alpineimage 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:
baseis our base image that we use for all the other stages. The
depsstage is where we install our app's dependencies. The
builderstage is where we build our app. Finally, the
runnerstage is where we run our app.
Finally, we have a
CMDinstruction that tells Docker how to run our app. In this case, we're telling Docker to run
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
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.
.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.
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?