Deploy your Preprod and Production Rails Application using Kamal

In most of the projects I contributed to, the deployment workflow looked like this :

We got a Preprod application that lets us experiment and do some QA. Preprod is an application that I can break without impacting users. Once I know that my Preprod application is working correctly, I can then deploy my application to the Production environment and expose new features to users.
Usually, I used Heroku for both Preprod and Prod. But… come on, we’re in 2024 right ? Heroku is old school, now we’re using Kamal 😎
Today I’ll share with you how I manage the deployment of multienvironment application using Kamal 2 in a step-by-step guide.
I won’t go that deep on how to configure kamal, I’ll just share the specificities of handling 2 environments deployment. If you need more details about a certain part, I linked useful resources that helped me and will help you for sure in the conclusion.
Prepare your Preprod environment
Before toying with Kamal, we need to create the Preprod environment.
To do so, I only need to create a new file in my environments folder
$ cp config/environments/production.rb config/environments/preprod.rb
I want my Preprod environment to be as close as possible to my Prod environment, so I won’t edit the file.
Now that Rails knows my Preprod environment, I need to create a master.key
for this environment
$ rails credentials:edit -e preprod
One last thing, we need a dedicated Dockerfile to handle this specific environment when building the Docker image.
For the sake of this article, I will take the default Dockerfile provided by Rails when you’re using rails new
, and create a dedicated one for Preprod environment :
$ cp Dockerfile Dockerfile.preprod
The only thing I will edit will be the ENV variables set by Dockerfile.preprod
:
# Dockerfile.preprod L20
ENV RAILS_ENV="preprod" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
That’s it, now we’re good to explore Kamal !!
Your folder architecture will look like this :
.
├── Dockerfile
├── Dockerfile.preprod
└── config
├── credentials
│ ├── preprod.key
│ └── preprod.yml.enc
├── credentials.yml.enc
├── environments
│ ├── preprod.rb
│ └── production.rb
└── master.key
Install Kamal
Kamal is not a gem that you add to your Gemfile, but you can install it using gem
:
$ gem install kamal
Once it’s done, you have access to Kamal. You can use the following command to bootstrap your Kamal configuration :
$ kamal init
kamal init
will create a config/deploy.yml
file and a .kamal
folder containing a hooks directory and a secrets
file
Setup Prod
I will keep my config/deploy.yml
as simple as possible, here is what I write :
# config/deploy.yml
service: my-app
image: docker-username/my-app
servers:
web:
- 190.0.0.0
proxy:
ssl: true
host: mydomain.com
app_port: 3000
registry:
username: docker-username
password:
- KAMAL_REGISTRY_PASSWORD
builder:
arch: arm64
local: true
dockerfile: Dockerfile.production
context: "."
env:
secret:
- RAILS_MASTER_KEY
ssh:
user: ubuntu
Let’s dive into each parameter :
- service: The name of your application
- image: The name of the image you will use for your registry (I’ll use a public docker registry here)
- servers: The addresses of the servers you will deploy your application. Here I have a single host for my production. I can also add a dedicated process for jobs handling if I need to process job.
- proxy: handle SSL certificate and point to the right domain name
- registry: where your Docker image will be pushed / pulled
- builder: Use Dockerfile.production and build it locally when deploying to the server. I use arm64 architecture because I’m using a macBook.
- env: It’s a way of passing secrets to the machine. We’ll talk about it just after
- ssh: Kamal will connect to the servers using the
ubuntu
user
To provide secrets, Kamal introduces it own secrets mechanism. You need to edit .kamal/secrets
to add the RAILS_MASTER_KEY
environment variable.
# .kamal/secrets
RAILS_MASTER_KEY=$(cat config/master.key)
KAMAL_REGISTRY_PASSWORD=mysuperpassword
Once it’s done, you’re good to either setup your server or deploy your application.
$ kamal setup
$ kamal deploy # if you want to deploy only
And now your application is running in production and fully accessible at your domain 🎉
At this stage, your folder architecture will look like this :
.
├── .kamal
│ └── secrets
├── Dockerfile
├── Dockerfile.preprod
└── config
├── credentials
│ ├── preprod.key
│ └── preprod.yml.enc
├── credentials.yml.enc
├── deploy.yml
├── environments
│ ├── preprod.rb
│ └── production.rb
└── master.key
Let’s see how to setup Preprod deployment now !
Setup Preprod
To configure Preprod deployment, we need to replicate what we did for Production, and essentially add .preprod
everywhere:
$ cp .kamal/secrets .kamal/secrets.preprod
$ cp config/deploy.yml config/deploy.preprod.yml
And then in the config/deploy.preprod.yml
:
# config/deploy.preprod.yml
image: docker-username/my-app-preprod
servers:
web:
- 190.0.0.1
proxy:
host: preprod.mydomain.com
builder:
dockerfile: Dockerfile.preprod
And … that’s it.
Kamal will read the “primary” config (deploy.yml
) as a fallback for missing value in our deploy.preprod.yml
. We’re just saying to Kamal that it must take the Dockerfile.preprod
and set the SSL certificate will point to preprod.mydomain.com
The last thing to do before deploying is to edit the secrets :
# .kamal/secrets.preprod
RAILS_MASTER_KEY=$(cat config/credentials/preprod.key)
KAMAL_REGISTRY_PASSWORD=mysuperpassword
And then you’re good to go :
$ kamal setup -d preprod
$ kamal deploy -d preprod # if you want to deploy only
Your application can be accessed at preprod.mydomain.com
!
Your folder architecture will look like this :
.
├── .kamal
│ ├── secrets
│ └── secrets.preprod
├── Dockerfile
├── Dockerfile.preprod
└── config
├── credentials
│ ├── preprod.key
│ └── preprod.yml.enc
├── credentials.yml.enc
├── deploy.preprod.yml
├── deploy.yml
├── environments
│ ├── preprod.rb
│ └── production.rb
└── master.key
Ressources
Conclusion
I took a lot of shortcuts here, but the point is that it’s very easy and painless to deploy multienvironment applications using Kamal.
So far, I only have good experience using Kamal. I've never had any difficult stages while debugging / monitoring, so it’s a very positive thing.