Deploy static pages with Kamal
Last week, i watched DHH presenting Rails 8 at Rails Conf.
In his presentation, he made a demo deploying two websites with Kamal which is a deployment tool that was introduced with Rails 7 and enhanced in Rails 8. I was really impressed by the simplicity of the deployment process and the ease of use of Kamal.
I decided to try it out and deploy my blog with Kamal.
Deploying this blog was not as easy as i thought it would be in the video, and so i decided to document the process to make it easier for my future me and anybody wandering around here.
This post will describe how to deploy this blog made with the static page generator Hugo.
What is kamal?
For Rails folks, Kamal is the equivalent of Capistrano but designed to deploy docker images. Kamal can be used not only to deploy rails app, but basically anything that need to be hosted on a VPS.
It's a deployment tool that will roughly do the following:
- Setup the remote machine (install docker / kamal proxy)
- Build the docker image of your project (in this case a static page generator aka blog)
- Push the image to a docker registry (docker hub by default, but it can be any registry)
- Pull the image from the registry on the remote machine
- [Optional] Create additional docker containers (e.g., a database) if specified in kamal accessories
- Run the projet's docker image on the remote machine
From there any requests will be handled by kamal proxy that will forward them to the proper docker container based on the url.
How to do?
Requirements
- A blog engine (hugo, jekyll, etc.)
- A VPS that you can access through SSH
- A domain name whose A record point to you VPS IP
- A local environment with Ruby and Docker installed
- A docker hub account to store your docker images
Setup
Install the kamal gem with gem install kamal
Now you can go in your blog folder and run the following command: kamal init
This will create the following folder structure:
.kamal
├── hooks
└── secrets
config
└── deploy.yml
Create a file named Dockerfile and copy paste the following content:
FROM klakegg/hugo as hugo
ADD . /src
RUN hugo --minify -s /src -d /out
FROM arm64v8/caddy
COPY --from=hugo /out /var/www/html
This file is the one that will be used by kamal to build the docker image.
This docker image will include caddy and hugo. Caddy acts as a proxy and will serve the HTML file from hugo when kamal proxy will forward the request to caddy.
Finaly copy the following kamal.yml file and update it with your own values:
# Name of your application. Used to uniquely configure containers.
service: blog
# Name of the container image.
image: {docker-hub-username}/{image-name}
# Deploy to these servers.
servers:
web:
hosts:
- {host-ip-address}
# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server).
# Set ssl: false if using something like Cloudflare to terminate SSL (but keep host!).
proxy:
ssl: true
host: {domain-name}
# kamal-proxy connects to your container over port 80, use `app_port` to specify a different port.
healthcheck:
path: /
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: {docker-hub-username}
# Always use an access token rather than real password (pulled from .kamal/secrets).
password:
- KAMAL_REGISTRY_PASSWORD
# Configure builder setup.
builder:
arch: {arm64 | amd64}
context: .
volumes:
- "/root/caddy:/etc/caddy"
On your local machine, you need to set the an environment variable with your docker registry password:
export KAMAL_REGISTRY_PASSWORD={docker-hub-password}
credentials are handled in the file .kamal/secrets, for the sake of simplicity, i decided to just go with credentials defined an environment variable for this post.
Now you're ready to deploy with kamal setup
This command will setup the remote machine (install docker and kamal proxy) then it will do the deployment.
When you want to deploy on a machine already configured with kamal you can just run kamal deploy
You can find about all the command provided by kamal here
Kamal skiff
When i looked into the matter, i found out that DHH already solved this problem 1 year ago and made a gem called kamal-skiff.
This gem exist to be able to deploy easily static page with kamal but has not been updated since last year. Thereforce it does not support kamal 2 and SSL certificate handling through kamal-proxy.