This is a simple Rails 7.2 application built based on Rails "blog app" guide. We will be using this app to learn how to deploy a Rails app to VPS with Kamal.
Sources:
Pre-requisites:
- Ruby 3.4.8
- installed and configured git
- installed and configured ssh
- ssh-key without passphrase
- docker installed and updated
-
Find or create an SSH key without a passphrase
https://linuxize.com/post/how-to-setup-passwordless-ssh-login/#setup-ssh-passwordless-login
-
Go to
kamal.cklos.foo -
Click on
Sign upbutton -
Enter signup code form slides, username, password, PUBLIC part of SSH key
-
Test your SSH connection to assigned servers
-
Generate a new application
gem update rails rails new kamal-workshops-new -
Add Posts scaffold (optional)
bin/rails generate scaffold Posts title:string bin/rails db:migrateset
root "posts#index"inconfig/routes.rb -
Edit
config/deploy.ymlimage: <docker_username- e.g. kamal_gh_1>/kamal_workshops_new servers: web: - 000.000.000.000 <1st IP address> proxy: ssl: true host: <app.123456.xyz> registry: username: <docker_username - e.g. kamal_gh_1> -
Set KAMAL_REGISTRY_PASSWORD environment variable in terminal
export KAMAL_REGISTRY_PASSWORD=<docker_token> -
Uncomment
KAMAL_REGISTRY_PASSWORDandRAILS_MASTER_KEYin.kamal/secretsKAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD RAILS_MASTER_KEY=$(cat config/master.key) -
Commit changes
-
Deploy with
kamal setup -
Go to
app.123456.xyzYou have deployed your application.
git clone git@github.com:visualitypl/kamal-workshops-app.git
git clone https://github.com/visualitypl/kamal-workshops-app.git
Example files for this task:
gem "kamal", require: false
bundle install
kamal init
We will need to set:
<% require "dotenv"; Dotenv.load(".env") %>at the top of the file- service (name of the app: blog-space)
- image (#{Docker Hub username from the companion app}/#{name of the app})
- servers (IP from the companion app)
- proxy (hostname)
- builder (arch and connection string)
- registry (username: #{Docker Hub username from the companion app})
- env (clear, secret)
- accessories
We will need to set:
- SECRET_KEY_BASE (generate secret key base with
rails secret) - KAMAL_REGISTRY_PASSWORD (Docker Hub token from the companion app)
- POSTGRES_PASSWORD (pick any password)
- REDIS_PASSWORD (pick any password)
- REDIS_URL (dotenv will substitute password in string: "redis://:${REDIS_PASSWORD}@blog-space-redis:6379/0")
We will need to tell kamal how to access the secrets. Read from the local envvars:
SECRET_KEY_BASE=$SECRET_KEY_BASE KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD POSTGRES_PASSWORD=$POSTGRES_PASSWORD REDIS_PASSWORD=$REDIS_PASSWORD REDIS_URL=$REDIS_URL
To install docker, start postgres and redis containers, kamal-proxy and deploy the app we need to run:
kamal server bootstrap
kamal env push
kamal accessory boot postgres && kamal accessory boot redis
# or kamal accessory boot all
kamal deployAlternatively, you can run a command that calls all the above:
kamal setupTo check the result, run:
kamal audit
kamal detailsVisit the app at hostname you set in the proxy.
Example files for this task:
Create config/deploy.staging.yml.
We only need to override values from config/deploy.yml that are different for staging.
servers:
web:
hosts:
- 999.999.999.999 # <2nd IP address>
worker:
hosts:
- 999.999.999.999 # <2nd IP address>
proxy:
host: staging.123456.xyz
env:
clear:
RAILS_ENV: "staging"
POSTGRES_DB: "blog_space_staging"
accessories:
postgres:
host: 999.999.999.999 # <2nd IP address>
redis:
host: 999.999.999.999 # <2nd IP address>Create .env.staging and add:
- KAMAL_REGISTRY_PASSWORD (Docker Hub token from the companion app)
- SECRET_KEY_BASE (generate secret key base with
rails secret) - POSTGRES_PASSWORD (pick any password)
- REDIS_PASSWORD (pick any password)
- REDIS_URL (substitute password in string: "redis://:$REDIS_PASSWORD@blog-space-redis:6379/0")
To use kamal with staging destination, we need to pass -d staging flag to all commands.
Like before, we need to set up docker on hosts, deploy postgres and redis accessories and deploy the app.
kamal setup -d stagingkamal server bootstrap -d staging
kamal env push -d staging
kamal accessory boot all -d staging
kamal deploy -d stagingTo check the result, run:
kamal audit -d staging
kamal details -d stagingVisit the app.
We will break the app by generating a migration that will fail.
bundle exec rails generate migration addAuthorToArticleclass AddAuthorToArticle < ActiveRecord::Migration[7.2]
def change
add_column :articles, :author, :string, null: false
end
endCommit the changes and push them to the server.
git add -A
git commit -m "Add author to article"
kamal deployInspect the output of the deploy command and notice that the app have not been deployed to the server.
Fix the migration by adding a default value to the author column. BUT at the same time break something else.
class AddAuthorToArticle < ActiveRecord::Migration[7.2]
def change
add_column :articles, :author, :string, null: false, default: ""
end
endComment the "delete_barons_comments" route in config/routes.rb
# post "delete_barons_comments", on: :memberCommit the changes and push them to the server.
This time the migration were applied to the database. But "the core functionality" of the app is lost.
Rollback to a previous version of the app:
# to find the version run:
kamal audit
kamal rollback <VERSION>Remove the migration that we added previously.
This task has no solution here ;)