- Jace's Blog
- Posts
- Rails, sqlite, Hetzner, Kamal2
Rails, sqlite, Hetzner, Kamal2
This is a work in progress and isn’t complete.
Table of Contents
Okay, so I’m learning Ruby on Rails. I’m looking forward to working on something that doesn’t constantly shift and has been out there for quite a while. I also love the idea of all the generators.
That being said, if you’re working on rails app and you deploy there’s always a number of steps here. I’m going to try to spell out everything I did from the getting started page on ruby on rails to the kamal deploy comand.
Let’s get into it.
1. Getting Started
I started the Getting Started tutorial this week. I however hit some snags. So we’re going to skip it and follow what Indigo Tech Tutorials published. There’s a few prerequisites we got to meet first.
1.1. Installing Ruby on Rails
They have a page here, for my set up (building on Windows) Here’s my notes.
Install WSL for Ubuntu
wsl --install --distribution Ubuntu-24.04
Open it up, create a user, and password you will remember.
Install a few things
# install the packages needed
sudo apt install build-essential rustc libssl-dev
sudo apt install libyaml-dev zlib1g-dev libgmp-dev
# get mise to install ruby on rails 3
curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
source ~/.bashrc
# install ruby
mise use -g ruby@3
# verify ruby
ruby --version
# install rails
gem install rails
# verify rails
rails --version
1.2 Setting up your Rails app
You should be on your terminal in WSL.
Let’s make a new project we’ll call it rubystore
with Tailwind.
rails new rubystore -c tailwind
# this will build the project out
cd rubystore
code . #open in vs code
I press the ctrl+`
to open the terminal in the editor.
1.3. Creating a home page
Now let’s set up the controller
rails g controller pages home
You’ll want to edit the config/routes.rb
file to match this.
Rails.application.routes.draw do
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Render dynamic PWA files from app/views/pwa/* (remember to link manifest in application.html.erb)
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
# Defines the root path route ("/")
root "pages#home"
end
At anytime you can test this by running bin/dev
. Let’s do that and ensure you get the page was generated page.
1.4. Cleaning up the application styles
Open the /views/layouts/application.html.erb
and let’s remove the classes on the <main>
element.
1.5. Replacing the Home page content
Open the /views/pages/home.html.erb
, and look for a landing page template with tailwind, searching for free tailwind css components will find you some sites. I found this one called Landing Page by zoltanszogyenyi.
Copy the code and past it in that file. Run you server and check it out.
1.6. Setting up User Login with Devise
On the terminal add the devise and tailwind devise gems, and the user model and migrate that to the database.
bundle add devise tailwind_devise
rails g devise:install
rails g tailwind_devise:views
rails g devise User
rails db:migrate
Open the /views/pages/home.html.erb
and find the register and login links.
Update them with the following.
Login link should use the following hrefs
<a href="<%= new_user_registration_path %>">Register</a>
<a href="<%= new_user_session_path %>">Login</a>
First let’s edit the /views/layouts/application.html.erb
by adding this in in the body tag.
<%= render "layouts/navbar" %>
Find the header html content and cut it from the /views/pages/home.html.erb
, and paste it into a new partial file /views/layouts/_navbar.html.erb
You can test this out by registering. You won’t notice anything different after logging in. Let’s fix that.
1.8. Adding a dashboard after login
We’re going to create a new controller definition in /controllers/pages_controller.rb
It should look like this.
class PagesController < ApplicationController
def home
end
def dashboard
end
end
We need to now tell the routes to handle authenticated users by adding this authenticated logic.
Rails.application.routes.draw do
devise_for :users
get "pages/home"
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
authenticated :user do
root "pages#dashboard", as: :authenticated_user_root
end
# Defines the root path route ("/")
root "pages#home"
end
Lastly, before you can test this, you’ll need a dashboard page. Create one at /views/pages/dashboard.html.erb
. Like before find a tailwind component that makes sense. I’m using Dark Dashboard Example by pantazisoftware.
Okay now the login and register loads and lands on the dashboard.
1.9. Hiding Login and Register for logged in users
Edit the /views/layouts/_navbar.html.erb
partial.
We’re going to wrap those links we modified above with this snippet.
<% if current_user %>
<a
href="<%= destroy_user_session_path %>"
data-turbo-method="delete"
class="...">
Log out
</a>
<% else %>
<!--your links here-->
<% end %
Now that it’s all set up. Try it out and when you're ready let’s deploy it.
2. Deploying
2.1. Getting the things ready
You’ll need a few things to get started here.
A docker registry (I’m using hub.docker.com) with an access token
A SSH Key
A server (I’m using Hetzner) with a SSH Key
A folder on the server where we’ll store our sqlite and other storage stuff.
dovenvx to allow us to set up the secrets and environment variables easily.
deploy.yml - setting the things up
There’s a lot in this file, I’m just going to paste my uncommented version, you can read more on it on the official Kamal Configuration page.
Installing Kamal
On the terminal install kamal, then initialize it for the current project.
gem install kamal
kamal init # this may throw a message that Config already exists
# that's okay.
Configuring the deploy.yml
I want to lay out all the things you’ll need for this, but there’s a bunch. Instead I’m going to address each configuration.
service
The service is the name of your application. I’m going to just call mine rubystore
image
This is the docker image. You’ll need a docker registry to do this. I’m using dockerhub. You’ll need to create an access token on docker hub. Make sure it has read and write permissions. You can use another registry, I’m not going to set that up. This value should be you’re username/repo. Put this in the .env
file (create it if it doesn’t exist). You should have something like this
KAMAL_REGISTRY_PASSWORD=your_key_goeshere12345
servers
Here we need the IP address of the server we’re going to deploy to. Spin up a machine on Hetzner that is running Ubuntu and at least 1 GB of ram. Copy that IP address here.
proxy
Initially we’re going to skip this so comment this block out. You can do that by prepending a #
for each line here.
registry
Kamal uses docker hub by default, we are also going to use that. Set the username here. This block lets you connect to different registries.
env
The env block defines what environment variable get passed as secret or as clear text. Here we’re just going to pass the secret of RAILS_MASTER_KEY
. To send that the secrets for Ruby is odd to me yet. You’ll need edit the ./kamal/secret
and add RAILS_MASTER_KEY=$RAILS_MASTER_KEY
. I’ll write a little more about this for the kamal commands later.
Setting up .env
Now Kamal needs access to the KAMAL_REGISTRY_PASSWORD and RAILS_MASTER_KEY. Create a .env
file and put those in there. Getting your master key is as easy as opening ./config/master.key
. If you don’t have a file there run rails credientals:edit
and then just exit using :q
. Now you will have that master.key
file.
KAMAL_REGISTRY_PASSWORD=dckr_pat_asdf
RAILS_MASTER_KEY=asdfasdfasdf
Running kamal setup
Running kamal setup needs some details available to it. To set these we’ll just prepend the command with these environment variables. Kamal also needs to be on a machine that can connect to the server. That means setting up ssh on that server and this one, or typing in the ssh password.
A note, on windows you need to have docker desktop installed and integrated with the ubuntu wsl you are running. Microsoft WSL Docker page has some instructions on this.
kamal setup
Sometimes I get an error running this
INFO [fd944dda] Running docker login -u [REDACTED] -p [REDACTED] as jace@localhost
ERROR (SSHKit::Command::Failed): docker exit status: 32000
If you get this, your .env
or .kamal/secrets
file probably isn’t working properly. I found that I can run kamal secrets print
to see what secrets are available to kamal. This is great for debugging.
I ended up install dotenvx to correct this.
curl -sfS https://dotenvx.sh | sudo sh
Then updated the secrets file to have this for the KAMAL_REGISTRY_PASSWORD.
KAMAL_REGISTRY_PASSWORD=$(dotenvx get KAMAL_REGISTRY_PASSWORD --quiet -f .env)
This just worked for me! Sweet. However, when I try to register, I don’t get logged in.
Setting up a path for sqlite
Erik Minkel wrote a piece on how he set’s this up so the sqlite file doesn’t get destroyed on every deploy.
We’re going to copy his lead.
To that we need to ssh on the machine and create directories for the database and for storage. We’ll give proper permissions to those folders. We’ll add the volumes into our deploy.yml file.
We’ll need to ssh onto the server to create the folders
ssh root@yourserversipaddress
sudo mkdir /db
sudo mkdir /storage
sudo chown 1000:1000 /db /storage
Log off the server and edit the deploy.yml locally by adding
volumes:
- "/db:/rails/sqlite"
- "/storage:/rails/storage"
Now let’s add these changes to git, and redeploy.
git add .
git commit -m "added volumes"
kamal deploy
Testing over http on the ip address
Okay this is not where we want to leave things but I like to test things out.
To test out the site it should be up right now but if you try to auth, you’ll have issues. That’s because when signing up you get the following error on the console (visible by kamal logs
)
If you search for Processing by Devise::RegistrationsController#create
you’ll find ActionController::InvalidAuthenticityToken (HTTP Origin header (http://1.2.3.4) didn't match request.base_url (https://1.2.3.4))
Okay, let get around that. In the /config/environments/production.rb
there’s an config.force_ssh
which were going to change to false and a config.assume_ssl
which we’ll comment out.
Do another deploy, and success I’m able to register an account and log in on separate windows.
Adding a domain and SSL
First part of this is configuring your Domain Name Server to point to your server.
Then undo what we did before.
Open
/config/environments/production.rb
Update
config.force_ssl
to trueUncomment
config.assume_ssl
kamal proxy logs
kamal proxy reboot
Useful resources
I found “How To Deploy Rails 7 App With Kamal 2” by Indigo Tech Tutorials the most useful. He’s able to offer help too for a fee on his site, Indigo Tech Tutorials.
Reply