We often use Dotenv in Rails for managing environment variables in development. But both Kamal and Dotenv works with .env
files by default. Let’s see how to solve this conflict.
Environment conflicts
We often rely on an .env
file in development when using Dotenv. This is a direct opposite for Kamal that also loads the same .env
file, but for deployment. As an example we might rely on RAILS_MASTER_KEY
in an .env
file which will differ from development and production. To figure out the right action to take we have to understand what these tools do with the env files in the first place.
Dotenv environments
Dotenv loads several env files by default by their priority. This depends on the environment:
# for development
.env.development.local
.env.local
.env.development
.env
# for test
.env.test.local
.env.test
.env
# for production
.env.production.local
.env.local
.env.production
.env
An .env.development
will have a higher priority and will be loaded before .env
. But Dotenv doesn’t just blindly load these files. It skips the values it already finds, so .env
file won’t override the other files that came before it.
Kamal environments
Kamal works with the .env
file by default but also supports other environments called destinations. Destinations are essentially all production environments, but might still require different variables.
Destinations have their own config and env file. A production could have an associated config/deploy.production.yml
config file and .env.production
environment file.
Kamal merges this environment file with .env
together to produce the final set of variables for a destination.
Solution
A simple fix is providing .env.development
file for development while keeping the common .env
file. Then optionally also introduce a Kamal’s production target so .env
will always contain only safe values.
But it’s not ideal. As long as .env
is used for production by Kamal it’s easy to make a mistake by including a production value and misusing it in development.
Instead I advice to completely separate these tools environments.
First we make sure to load Dotenv only in development and test:
# Gemfile
gem "dotenv", groups: [:development, :test]
And then we remove the .env
file from Dotenv default files:
# config/application.rb
require_relative "boot"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
Dotenv::Rails.files.delete(".env")
...
Dotenv will load .env.development
for shared development values and .env.test
for shared test values ignoring .env
alltogether.
This also means we don’t have to change anything on the Kamal side. We can optinally introduce a production destination, but we are not forced to.