Kamal 2

Episode #477 by Teacher's Avatar David Kimura

Summary

In this episode we look at deploying a Rails 8 beta application to a Digital Ocean droplet. We'll also look at a scenario of deploying Action Cable broadcasts and another with recurring background jobs.
rails 8.0 deploy kamal 22:55

Chapters

  • Introduction (0:00)
  • Creating Digital Ocean Droplet (1:24)
  • DNS Changes (2:48)
  • Creating a new Rails app (3:31)
  • Triggering our first deployment (4:17)
  • Generating Scaffold and ActionText (6:48)
  • Broadcasting updates (9:06)
  • Background Recurring Jobs (12:35)
  • Pre-Final Thoughts (14:14)
  • Looking at the SSL Certificate (15:37)
  • Rails Diff (16:35)
  • Looking at Thruster (18:02)
  • Looking at the Dockerfile (18:21)
  • Notes about Kamal 1 to Kamal 2 (20:08)
  • Notes about the deploy.yml (21:45)
  • Final Thoughts (22:17)

Resources

Download Source Code

Summary

# Terminal
gem install rails --pre
kamal setup
kamal deploy
kamal config
rails action_text:install
rails g scaffold post title content:rich_text
rails g job update_post_title

# config/deploy.yml
# Name of your application. Used to uniquely configure containers.
service: app1

# Name of the container image.
image: kobaltz/app1

# Deploy to these servers.
servers:
  web:
    - 134.122.126.42
  # job:
  #   hosts:
  #     - 134.122.126.42
  #   cmd: bin/jobs

# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server).
# If using something like Cloudflare, it is recommended to set encryption mode
# in Cloudflare's SSL/TLS setting to "Full" to enable end-to-end encryption.
proxy:
  ssl: true
  host: app.talkrails.com,www.talkrails.com
  # kamal-proxy connects to your container over port 80, use `app_port` to specify a different port.
  # app_port: 3000

# Credentials for your image host.
registry:
  # Specify the registry server, if you're not using Docker Hub
  # server: registry.digitalocean.com / ghcr.io / ...
  username: kobaltz

  # Always use an access token rather than real password (pulled from .kamal/secrets).
  password:
    - KAMAL_REGISTRY_PASSWORD

# Configure builder setup.
builder:
  arch: amd64

# Inject ENV variables into containers (secrets come from .kamal/secrets).
#
# env:
#   clear:
#     DB_HOST: 192.168.0.2
#   secret:
#     - RAILS_MASTER_KEY

# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
#
# aliases:
#   shell: app exec --interactive --reuse "bash"

# Use a different ssh user than root
#
# ssh:
#   user: app

# Use a persistent storage volume.
#
# volumes:
#   - "app_storage:/app/storage"

# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
#
# asset_path: /app/public/assets

# Configure rolling deploys by setting a wait time between batches of restarts.
#
# boot:
#   limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
#   wait: 2

# Use accessory services (secrets come from .kamal/secrets).
#
# accessories:
#   db:
#     image: mysql:8.0
#     host: 192.168.0.2
#     port: 3306
#     env:
#       clear:
#         MYSQL_ROOT_HOST: '%'
#       secret:
#         - MYSQL_ROOT_PASSWORD
#     files:
#       - config/mysql/production.cnf:/etc/mysql/my.cnf
#       - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
#     directories:
#       - data:/var/lib/mysql
#   redis:
#     image: redis:7.0
#     host: 192.168.0.2
#     port: 6379
#     directories:
#       - data:/data

# app/models/post.rb
class Post < ApplicationRecord
  has_rich_text :content
  broadcasts
end

# app/views/posts/show.html.erb
<%= turbo_stream_from @post %>

# config/environments/production.rb
Rails.application.routes.default_url_options = { host: "app.talkrails.com", protocol: :https }

# app/jobs/update_post_title_job.rb
class UpdatePostTitleJob < ApplicationJob
  queue_as :default

  def perform
    post = Post.all.sample
    post.title = SecureRandom.hex
    post.save
  end
end

# config/recurring.yml
production:
  random_title:
    class: UpdatePostTitleJob
    queue: background
    schedule: every 1 seconds