Kamal Rails on Hetzner Series Part 1: Deploy Rails application on Hetzner

Kamal is a modern deployment tool for Rails applications, announced by DHH in February 2023 and featured in his RailsWorld Keynote. With Heroku having removed their free tier, Kamal has gained significant traction, particularly for those interested in “leaving the cloud”. Most importantly, Kamal is free and open source, developed by the team at Basecamp who created Rails.

Using Kamal requires you to provision your own server. Popular options include Digital Ocean, AWS, and Hetzner. We’ll use Hetzner, which offers excellent value—you can run a complete Rails application for as little as €4 per month.

This is Part 1 of the Kamal Rails series. I’ll walk you through deploying a vanilla Rails application on Hetzner Cloud.

Prerequisites

  • Docker is installed on your local machine (ensure the version is compatible with your OS)
  • A Docker account created on Docker Hub
  • A Rails application using Rails 6.0 or later

If you don’t have a Rails application yet, create one using:

1
rails new kamal_demo -T -m https://raw.githubusercontent.com/joshio1/rails_application_template/main/application_template.rb
  • A Hetzner Cloud account ready to create projects and servers (account verification typically takes about a day)

Steps:

1. Create a VPS on Hetzner Cloud

  • Log in to Hetzner Cloud and create a new project

  • After creating the project, add a server by clicking “Add Server”

  • Select your server location and operating system (we’ll use Ubuntu)
  • Choose Shared vCPU (x86) and CPX11 (the first plan)

  • Select both IPv6 and IPv4
  • Add an SSH key to your server. Copy your public key to the clipboard:
1
pbcopy < ~/.ssh/id_rsa.pub
  • Paste the key in the SSH section on Hetzner Cloud and click “Create and Buy Now”
  • The server will be created in 1-2 minutes. You’ll receive an email from Hetzner. Verify the server is active and has an IPv4 address, then test SSH access:
1
ssh root@<ipv4_address_of_the_server>

2. Prepare Your Rails Application for Deployment

A Dockerfile is required because Kamal uses Docker images for deployment.

  • Ensure you have a health check route. Rails 7.1+ includes an /up route by default that indicates application health
  • If you don’t have this route, add it to your config/routes.rb file:
get '/up', to: ->(env) { [204, {}, ['']] }
  • With a Dockerfile and health check route in place, you’re ready to use Kamal.

3. Deploy with Kamal

  • Install Kamal:
1
gem install kamal

This installs Kamal 2, the latest version (version depends on your Ruby version).

  • Navigate to your Rails application and initialize Kamal:
1
2
cd kamal_demo
kamal init

This creates several files including config/deploy.yml, .kamal/hooks, and .kamal/secrets.

  • Configure environment variables:
    • Create a Docker Registry access token by logging into your Docker account and navigating to Account Settings > Security > New Access Token
    • Obtain your RAILS_MASTER_KEY, which decrypts your production credentials file (config/credentials/production.yml.enc)
      • If you don’t have a production credentials file, create one with: EDITOR=vim rails credentials:edit --environment production
    • There are three ways to manage these secrets in .kamal/secrets:
      1. Create a .env file and load it with direnv
      2. Use rails credentials:fetch kamal.registry_password (requires latest Rails)
      3. Use a third-party tool like 1Password with kamal secrets
    • For simplicity, we’ll use the first approach with a .env file
    • Use direnv to load environment variables from .env into your shell
    • Your .env file should look like:

      1
      2
      
      KAMAL_REGISTRY_PASSWORD=<our_docker_access_token>
      RAILS_MASTER_KEY=<our_rails_production_master_key>
      
  • The kamal init command also creates a config/deploy.yml file. This file contains the configuration needed to deploy your Rails application. Edit it to match the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
service: kamal_demo
image: joshio1/kamal_demo
servers:
  - <ipv4_address_of_Hetzner_server>
registry:
  username: joshio1
  password:
    - KAMAL_REGISTRY_PASSWORD
builder:
  arch: amd64
  secrets:
    - RAILS_MASTER_KEY
    - SECRET_KEY_BASE
env:
  secret:
    - RAILS_MASTER_KEY
  • Key details about the config/deploy.yml file:
    • joshio1 is your Docker username and kamal_demo is your Rails application name
    • Kamal automatically references KAMAL_REGISTRY_PASSWORD and RAILS_MASTER_KEY from your .env file
    • Replace <ipv4_address_of_hetzner_server> with your actual server IP
  • Set force_ssl = false in config/production.rb since we haven’t configured SSL certificates yet. We’ll enable HTTPS in a later part of this series.

After completing the configuration above, deploy your Rails application to the Hetzner server:

1
kamal setup

You should see output similar to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-> kamal_demo git:(main) ✗ kamal setup
  INFO [ffc073f1] Running /usr/bin/env mkdir -p .kamal on 5.161.42.10
  INFO [ffc073f1] Finished in 3.630 seconds with exit status 0 (successful).
  Acquiring the deploy lock...
  ...
  Ensure Docker is installed...
  ...
  Push env files...
  ...
  Log into image registry...
  ...
  Build and push app image...
  ...
  ...
  ...
    INFO [57d640dd] Finished in 0.527 seconds with exit status 0 (successful).
    INFO Container is healthy!
  ...
  ...
    INFO [36025396] Finished in 1.358 seconds with exit status 0 (successful).
    Finished all in 288.2 seconds
    Releasing the deploy lock...
    Finished all in 301.2 seconds
  • Once the container is healthy and all steps complete successfully, verify the deployment by visiting <SERVER_IP>/up in your browser. You should see a green health check screen.

  • If you encounter errors during kamal setup or don’t see the green screen, refer to the Important Points section below.

Next Part: Add Postgres to Your Rails Application

So far, we’ve deployed a basic Rails application with SQLite. In Part 2 of this Hetzner series, we’ll deploy a Rails application backed by Postgres.

Continue to Part 2: Add Postgres

NOTE:

  • If this article is out of date, please don’t hesitate to contact me on Twitter from this page and I’ll be happy to update it.
  • Listen to this podcast where DHH talks about Rails and Kamal.
  • If you would like to search for specific terms or concepts or names in Ruby/Rails podcasts, check out rubypodcatcher.com
This post is licensed under CC BY 4.0 by the author.