Deploy a Rails App with Kamal
Deploy a Rails app with an encrypted .env.vault file with Kamal.
Find a complete code example on GitHub for this guide.
Initial setup
Install Rails.
gem install rails
Create a new Rails project.
rails new kamal-example
Edit config/routes.rb
and set root to www#index
.
config/routes.rb
Rails.application.routes.draw do
get "up" => "rails/health#show", as: :rails_health_check
root "www#index"
end
Create app/controllers/www_controller.rb
.
app/controllers/www_controller.rb
class WwwController < ApplicationController
def index
end
end
Create app/views/www/index.html.erb
.
app/views/www/index.html.erb
Hello <%= ENV["HELLO"] %>.
Commit those changes safely to code. Next, we'll set up Kamal.
Set up Kamal
Install Kamal.
gem install kamal
Initialize Kamal.
kamal init
Edit your config/deploy.yml
file.
config/deploy.yml
# Name of your application. Used to uniquely configure containers.
service: kamal-example
# Name of the container image.
image: yourdockerorg/kamal-example
# Deploy to these servers. Replace with your server(s) ip address(es)
servers:
- 5.78.32.32 # your_server_ip
# 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_username_on_docker_hub
# 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: 192.168.0.2
# secret:
# - RAILS_MASTER_KEY
env:
secret:
- RAILS_MASTER_KEY
# https://dev.to/adrienpoly/deploying-a-rails-app-with-mrsk-on-hetzner-a-beginners-guide-39kp
volumes:
- "storage:/rails/storage"
Make sure you can ssh to your server.
ssh root@your_server_ip
Then modify your .env
file, setting your KAMAL_REGISTRY_PASSWORD
to your docker registry's password and your RAILS_MASTER_KEY
. Find your RAILS_MASTER_KEY at config/master.key
.
.env
KAMAL_REGISTRY_PASSWORD=your-docker-registry-password
RAILS_MASTER_KEY=11011070047a1cf7bacd3f7fd6bacd9b
Turn off force_ssl
.
config/environments/production.rb
Rails.application.configure do
...
# Assume all access to the app is happening through a SSL-terminating reverse proxy.
# Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies.
# config.assume_ssl = true
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = false
...
end
Then run kamal setup
.
kamal setup
This will bootstrap your server with everything it needs - docker and your Rails application.
Visit your server's ip address.
Your app will say 'Hello .'
at your_server_ip as it doesn't have a way to access the environment variable yet. Let’s do that next.
Install dotenv-vault-rails
Add dotenv-vault-rails
to the top of your Gemfile.
Gemfile
source "https://rubygems.org"
ruby "3.1.3"
gem "dotenv-vault-rails", require: "dotenv-vault/rails-now"
gem "rails", "~> 7.1.1"
...
bundle install
Add HELLO=World
to your .env
file.
.env
KAMAL_REGISTRY_PASSWORD=your-docker-registry-password
RAILS_MASTER_KEY=11011070047a1cf7bacd3f7fd6bacd9b
HELLO=World
Try running it locally.
bin/rails server
=> Booting Puma
=> Rails 7.1.1 application starting in development
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
It should say Hello World
.
Great! ENV
now has the keys and values you defined in your .env
file. That covers local development. Let's solve for production next.
Build .env.vault
Push your latest .env
file changes and edit your production secrets. Learn more about syncing
npx dotenv-vault@latest push
npx dotenv-vault@latest open production
Use the UI to configure those secrets per environment.
Then build your encrypted .env.vault
file.
npx dotenv-vault@latest build
Its contents should look something like this.
.env.vault
#/-------------------.env.vault---------------------/
#/ cloud-agnostic vaulting standard /
#/ [how it works](https://dotenv.org/env-vault) /
#/--------------------------------------------------/
# development
DOTENV_VAULT_DEVELOPMENT="/HqNgQWsf6Oh6XB9pI/CGkdgCe6d4/vWZHgP50RRoDTzkzPQk/xOaQs="
DOTENV_VAULT_DEVELOPMENT_VERSION=2
# production
DOTENV_VAULT_PRODUCTION="x26PuIKQ/xZ5eKrYomKngM+dO/9v1vxhwslE/zjHdg3l+H6q6PheB5GVDVIbZg=="
DOTENV_VAULT_PRODUCTION_VERSION=2
Set DOTENV_KEY
Fetch your production DOTENV_KEY
.
npx dotenv-vault@latest keys production
# outputs: dotenv://:[email protected]/vault/.env.vault?environment=production
Set DOTENV_KEY
for Kamal's .env
file.
.env
KAMAL_REGISTRY_PASSWORD=your-docker-registry-password
RAILS_MASTER_KEY=11011070047a1cf7bacd3f7fd6bacd9b
HELLO=World
DOTENV_KEY="dotenv://:[email protected]/vault/.env.vault?environment=production"
And DOTENV_KEY
to Kamal's config/deploy.yml
env.secret
section.
config/deploy.yml
...
env:
secret:
- RAILS_MASTER_KEY
- DOTENV_KEY
...
Push those ENV
changes to Kamal.
kamal env push
Deploy Kamal
Commit those changes safely to code and deploy with Kamal.
kamal deploy
That's it! On build and deploy, your .env.vault
file will be decrypted and its production secrets injected as environment variables – just in time.
You succesfully used the new .env.vault standard to encrypt and deploy your secrets. This is much safer than scattering your secrets across multiple third-party platforms and tools. Whenever you need to add or change a secret, just rebuild your .env.vault file and redeploy.