When we first deploy a small or medium-sized Axum app, the priority is to get it running as fast and as cheaply as possible but starting with a complex infrastructure for a project that is still not live or is not big enough is usually a waste of time and resources.
Tools or platforms like Kubernetes, AWS, Google Cloud, Terraform... they all have a learning curve and usually come with a high cost.
In this article, I will show you how to deploy to your own VPS, which is a great option for small or medium-sized projects. It’s cheap, easy to set up, and can be scaled up or down as needed. For this example, we will use Hetzner as our VPS provider, but you can use any VPS provider you like (DigitalOcean, Vultr, Linode...) as long as it supports SSH access.
What is Kamal?
Kamal is a deployment tool created by DHH, the creator of Ruby on Rails. As stated in their website:
Kamal offers zero-downtime deploys, rolling restarts, asset bridging, remote builds, accessory service management, and everything else you need to deploy and manage your web app in production with Docker. Originally built for Rails apps, Kamal will work with any type of web app that can be containerized.
Many people dismiss Kamal because it was originally built as a deployment tool for Rails apps. However, when inspected more closely, it’s a powerful tool that can be used for any type of web application that can be containerized. It’s a great option for small or medium-sized projects that need a simple, easy-to-use deployment tool, regardless of the framework or language used.
Quick note from the developer
You are looking at one of the articles on my personal blog, please support me by visiting it, thanks!
Before we start, let's talk about the scope we will cover
This is not an article about replacing Kubernetes or Terraform.
Kamal is not trying to solve the same problems as Kubernetes, and this post is not arguing that you should abandon complex platforms for large or highly distributed systems. Kubernetes, Terraform, and similar tools exist for good reasons and are the right choice in many scenarios.
This article focuses on a different problem: deploying a small or medium-sized web application in a simple, cost-effective way, without taking on unnecessary operational complexity before it’s actually needed.
Let's get started
For this article there will be some assumptions:
- You have a Hetzner account and have created a VPS. The cheapest server will work. At the time of writing, the cheapest option is the CX23, which costs less than $5/month and includes 4GB of RAM, 40GB of SSD, and 2 vCPU cores, more than enough for our tests.
- You have Docker installed. By default, Kamal uploads your Docker images to Docker Hub publicly. If you want to change to a different container registry or keep your images private on Docker Hub, you can find a guide here.
- You have a bare Axum app ready to deploy (or any other web application that can be containerized). I have an example here.
- The project you want to deploy has a valid Dockerfile. You can see an example here.
For the Axum app, any application will work as long as it exposes a health endpoint. We will use Kamal’s default ("/up"), but any other endpoint can be used if it’s correctly configured in the kamal.yml file.
Let’s start by running the following commands from the root of the project:
gem install kamal
kamal init
These commands create a config/deploy.yml file and a .kamal/secrets.yml file with the default configuration (we will ignore hooks for now).
Your .kamal/secrets.yml file can read secrets from environment variables or a password manager (it works with 1Password by default). For this example, we will use environment variables, but keeping your secrets in a password manager is the recommended approach. We will keep the image private on Docker Hub, so we’ll uncomment the following line in the file:
# Option 1: Read secrets from the environment
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
The fun starts in the config/deploy.yml file. Let’s start editing the parts we need for this example.
First, we need to define the service name, the image name, and the IP of the server we want to deploy to:
service: axum-post-example # -> The name of your service, just use the same name you have on your Cargo.toml file
image: my-docker-hub-user/axum-post-example # -> The username on Docker Hub and the name of the image that will be created and stored
servers:
web:
- 116.111.111.111 # -> The ip of the server created on your VPS platform
Then, we need to define the URL where the application will be deployed:
proxy:
ssl: true
host: example.marccamara.com # -> The url where you want your app to be deployed, remember to setup the DNS records
healthcheck:
path: /up # -> /up is the default endpoint, so no need to add this line
After that, we need to define where the newly created image will live, along with the password (if you want your images to be private):
registry:
username: my-docker-hub-user
password:
- KAMAL_REGISTRY_PASSWORD
And that's all!
Deploying
Now that everything is ready, we can do our first deployment by running:
kamal setup
Wait for the deployment to finish, then check if everything is working by visiting https://your-website.com/up.
Subsequent deployments can be done by running:
kamal deploy
As you may have noticed, Kamal has handled the SSL certificate and proxy configuration for us, which is pretty cool.
Finishing up
We’ve successfully deployed our application. While this setup looks simple, Kamal can do much more: it can deploy databases, deploy to multiple servers, perform zero-downtime deploys, and support multiple environments (for example, by adding a .config/deploy.staging.yml file), among other things.
I’m currently using Kamal for two different projects: one medium-sized setup with Postgres, Valkey and two servers in different locations, and this very personal page running on Leptos with caching.
I may talk about these setups in a future post, but for now, I hope you enjoyed this article and learned something new.
Top comments (0)