Notes to self

Avoiding environment conflicts with Kamal and Dotenv

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.

Check out my book
Learn how to use Kamal to deploy your web applications with Kamal Handbook. Visualize Kamal's concepts, understand Kamal's configuration, and deploy practical life examples.
by Josef Strzibny
RSS