Environment Variables and Configuration
Prerequisites: HTTP requests and APIs
Configuration is anything that changes between where your program runs — your laptop, a test server, production: database addresses, API keys, feature switches. It shouldn't be written into the code.
Environment variables are the standard way to supply it. They're
set outside the program, and Ruby reads them through the ENV hash:
ENV["APP_ENV"] = "development" # normally set outside the program
p ENV["APP_ENV"]
p ENV["MISSING_VAR"]
p ENV.fetch("PORT", "3000")
p Integer(ENV.fetch("PORT", "3000"))
"development"
nil
"3000"
3000
ENV["X"]returnsnilif the variable isn't set.ENV.fetch("X", default)gives a fallback.ENV.fetch("X")with no default raisesKeyError— use it for settings the program can't run without, so it fails immediately with a clear message instead of misbehaving later.- Values are always strings; convert numbers and booleans yourself.
Setting a variable when running a script:
API_TOKEN=abc123 ruby app.rb # macOS / Linux
$env:API_TOKEN="abc123"; ruby app.rb # Windows PowerShell
.env files in development
Typing variables every time gets tedious. The dotenv gem loads them
from a .env file:
# .env
API_TOKEN=abc123
DATABASE_URL=sqlite://dev.db
require "dotenv/load"
token = ENV.fetch("API_TOKEN")
Never commit .env — add it to .gitignore, and commit a
.env.example with placeholder values instead, so others know which
variables to set.
Collect configuration in one place
Config = Data.define(:port, :api_token, :debug) do
def self.from_env(env = ENV)
new(
port: Integer(env.fetch("PORT", "3000")),
api_token: env.fetch("API_TOKEN"),
debug: env.fetch("DEBUG", "false") == "true",
)
end
end
config = Config.from_env({ "API_TOKEN" => "abc", "DEBUG" => "true" })
p config
#<data Config port=3000, api_token="abc", debug=true>
Passing env as an argument (defaulting to ENV) makes the method
easy to test with a plain hash.
Common mistakes
- Committing secrets to version control. Once pushed, assume they're leaked and rotate them.
- Treating
ENV["DEBUG"]as a boolean."false"is a non-empty string, so it's truthy.