Skip to content

Instantly share code, notes, and snippets.

@ruslan-oliinyk
Last active September 20, 2024 20:30
Show Gist options
  • Save ruslan-oliinyk/7c5e550ddef196aad891d0bfa6e87294 to your computer and use it in GitHub Desktop.
Save ruslan-oliinyk/7c5e550ddef196aad891d0bfa6e87294 to your computer and use it in GitHub Desktop.
Deploy Rails app with Kamal

Deploying a Rails app to cloud services using Docker and Kamal (a library for deploying apps containerized with Docker).

https://kamal-deploy.org/

1. Create a new app

rails new APP_NAME -d postgresql -j esbuild -c bootstrap --skip-test

cd APP_NAME

git add .

git commit -m 'Init app'

2. Implement basic functionality

rails g scaffold Post title content:text

rails db:create

rails db:migrate

3. Edit routes, set root path in routes.rb

# Defines the root path route ("/")
root "posts#index"

4. Run the app and check if everything works

# Start Rails
bin/dev

git add .

git commit -m 'Implement a basic blog functionality'

5. Init Kamal

# Add Kamal gem to Gemfile
bundle add kamal

kamal init

This command will add several new files like .env, config/deploy.yml and .kamal/hooks folder with hook samples.

We don't need the .kamal/hooks folder, so we can delete it or add to the .gitignore!

6. Get you IP address

This can be any cloud service you prefer, such as DigitalOcean, AWS, CoogleCloud, etc.

7. Register container registry

This can be DockerHub, DigitalOcean CR, AWS ECR, GCP Artifact Registry etc.

8. Update your .env file with the correct data

# .env
KAMAL_REGISTRY_PASSWORD=change-this
RAILS_MASTER_KEY=use-your-master-key
POSTGRES_PASSWORD=set-posgtres-password

9. Update your database.yml use the ENVs

production:
  <<: *default
  database: your_app_name_production
  username: your_app_name
  password: <%= ENV["POSTGRES_PASSWORD"] %>
  host: <%= ENV["DB_HOST"] %>

10. If you don't have an SSL certificate, update config/environments/production.rb

config.force_ssl = false

11. Config deploy.yml file

# Name of your application. Used to uniquely configure containers.
service: your-app-name

# Name of the container image.
image: your-container-registry-nickname/your-app-name

# Deploy to these servers.
servers:
  web:
    hosts:
      - SERVER_IP_ADDRESS

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

  # Always use an access token rather than real password when possible.
  password:
    - KAMAL_REGISTRY_PASSWORD

# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
env:
 clear:
   DB_HOST: SERVER_IP_ADDRESS
   RAILS_ENV: production
 secret:
   - RAILS_MASTER_KEY
   - POSTGRES_PASSWORD


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

# Configure builder setup.
# builder:
#   args:
#     RUBY_VERSION: 3.2.0
#   secrets:
#     - GITHUB_TOKEN
#   remote:
#     arch: amd64
#     host: ssh://app@192.168.0.1

# Use accessory services (secrets come from .env).
accessories:
 db:
   image: postgres:15
   host: SERVER_IP_ADDRESS
   port: 5432
   env:
     clear:
       POSTGRES_USER: 'your_app_name'
       POSTGRES_DB: 'your_app_name_production'
     secret:
       - POSTGRES_PASSWORD
   directories:
     - data:/var/lib/postgresql/data

#   redis:
#     image: redis:7.0
#     host: 192.168.0.2
#     port: 6379
#     directories:
#       - data:/data

# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
# traefik:
#   args:
#     accesslog: true
#     accesslog.format: json

# Configure a custom healthcheck (default is /up on port 3000)
# healthcheck:
#   path: /healthz
#   port: 4000

# 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.
#
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
# See https://github.com/basecamp/kamal/issues/626 for details
#
# asset_path: /rails/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

# Configure the role used to determine the primary_host. This host takes
# deploy locks, runs health checks during the deploy, and follow logs, etc.
#
# Caution: there's no support for role renaming yet, so be careful to cleanup
#          the previous role on the deployed hosts.
# primary_role: web

# Controls if we abort when see a role with no hosts. Disabling this may be
# useful for more complex deploy configurations.
#
# allow_empty_roles: false

The PostgreSQL image requires some environment variables to be set, namely POSTGRES_USER, POSTGRES_DB, and POSTGRES_PASSWORD

# config/deploy.yml
...
postgres:
  ...
  env:
    clear:
      POSTGRES_USER: "project"
      POSTGRES_DB: "project_production"
    secret:
      - POSTGRES_PASSWORD
  ...

The official Docker image will use these variables to create the database for you and you'll be able to connect to it with the following URL:

"postgres://POSTGRES_USER:POSTGRES_PASSWORD@DB_HOST/POSTGRES_DB"

Your DB_HOST will either be the IP address or the internal service name on the local Docker private network.

It's important to use ENV variables POSTGRES_USER, POSTGRES_DB and POSTGRES_PASSWORD, don't change their names or use others, because Postrges uses these variables when configuring the database!

git add .

git commit -m 'Kamal init'

That's it.

12. Now you’re ready to deploy to the servers

# For the first run
kamal setup

# When the server is configured
kamal deploy

List of other Kamal commands: https://kamal-deploy.org/docs/commands/view-all-commands/

Voila! All the servers are now serving the app on port 80 💪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment