bkeepers/dotenv
A Ruby gem to load environment variables from `.env`.
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 5mo ago
- ✓18 active contributors
- ✓MIT licensed
Show 4 more →Show less
- ✓CI configured
- ✓Tests present
- ⚠Slowing — last commit 5mo ago
- ⚠Concentrated ownership — top contributor handles 77% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/bkeepers/dotenv)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/bkeepers/dotenv on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: bkeepers/dotenv
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/bkeepers/dotenv shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 5mo ago
- 18 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Slowing — last commit 5mo ago
- ⚠ Concentrated ownership — top contributor handles 77% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live bkeepers/dotenv
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/bkeepers/dotenv.
What it runs against: a local clone of bkeepers/dotenv — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in bkeepers/dotenv | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 187 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of bkeepers/dotenv. If you don't
# have one yet, run these first:
#
# git clone https://github.com/bkeepers/dotenv.git
# cd dotenv
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of bkeepers/dotenv and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "bkeepers/dotenv(\\.git)?\\b" \\
&& ok "origin remote is bkeepers/dotenv" \\
|| miss "origin remote is not bkeepers/dotenv (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "lib/dotenv.rb" \\
&& ok "lib/dotenv.rb" \\
|| miss "missing critical file: lib/dotenv.rb"
test -f "lib/dotenv/parser.rb" \\
&& ok "lib/dotenv/parser.rb" \\
|| miss "missing critical file: lib/dotenv/parser.rb"
test -f "lib/dotenv/environment.rb" \\
&& ok "lib/dotenv/environment.rb" \\
|| miss "missing critical file: lib/dotenv/environment.rb"
test -f "lib/dotenv/load.rb" \\
&& ok "lib/dotenv/load.rb" \\
|| miss "missing critical file: lib/dotenv/load.rb"
test -f "lib/dotenv-rails.rb" \\
&& ok "lib/dotenv-rails.rb" \\
|| miss "missing critical file: lib/dotenv-rails.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 187 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~157d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/bkeepers/dotenv"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
dotenv is a Ruby gem that loads environment variables from a .env file into the ENV hash during application bootstrap. It solves the problem of managing environment-specific configuration (API keys, database URLs, secrets) without committing sensitive data to version control, following the twelve-factor app methodology. Core capability: parse .env files with support for variable substitution, multi-file loading, and Rails auto-integration. Modular structure: lib/dotenv.rb is the main entry point; lib/dotenv/parser.rb handles .env syntax parsing; lib/dotenv/environment.rb manages the ENV hash interface; lib/dotenv/rails.rb and lib/dotenv-rails.rb provide Rails auto-loading hooks; lib/dotenv/substitutions/ contains variable and command expansion logic; lib/dotenv/cli.rb provides the bin/dotenv command-line tool; tests mirror this structure under spec/dotenv/.
👥Who it's for
Ruby developers building Rails, Sinatra, or plain Ruby applications who need to manage environment variables across development, test, and CI environments without manual setup. Particularly useful for teams running multiple projects on shared machines or CI servers.
🌱Maturity & risk
Production-ready and actively maintained. The codebase is well-established with comprehensive test coverage across spec/dotenv/ including parser, CLI, Rails integration, and diff utilities. CI is configured via .github/workflows/ci.yml. Recent maintenance visible through version management and Rails compatibility updates.
Low risk for core functionality but moderate risk around single-maintainer dependency (bkeepers as primary owner listed in OWNERS file). Breaking changes between major versions (e.g., version 3.0 autorestore feature) suggest careful upgrade planning needed. Minimal external gem dependencies visible in gemspec-driven setup.
Active areas of work
Active maintenance of Rails integration and autorestore features (version 3.0+). Workflow files show automated CI and release pipelines. Dependabot configured for dependency updates. No specific PR/milestone data visible in file list, but structure suggests ongoing compatibility work with Ruby/Rails ecosystem.
🚀Get running
git clone https://github.com/bkeepers/dotenv.git
cd dotenv
bundle install
bundle exec rspec
Daily commands:
# Run tests
bundle exec rspec
# Run with Guard for watch mode (Guardfile present)
bundle exec guard
# Use CLI tool directly
bundle exec dotenv -f .env run 'echo $YOUR_VAR'
# Run Rake tasks with dotenv loaded
rake -I lib -r dotenv/tasks mytask
🗺️Map of the codebase
lib/dotenv.rb— Main entry point and public API; defines load(), load! and core functionality exposed to users.lib/dotenv/parser.rb— Core parsing logic that converts .env file syntax into key-value pairs; handles all syntax edge cases.lib/dotenv/environment.rb— Central abstraction representing a loaded .env file; manages variable storage and interpolation logic.lib/dotenv/load.rb— Handles the actual file I/O and ENV mutation; bridges parser output to Ruby's ENV hash.lib/dotenv-rails.rb— Rails integration entry point; orchestrates loading during Rails initialization and environment setup.lib/dotenv/rails.rb— Rails Railtie that hooks into Rails boot sequence and manages reloading behavior.lib/dotenv/substitutions/variable.rb— Implements variable interpolation ($VAR and ${VAR}) used in .env value expansion.
🛠️How to make changes
Add a new variable substitution type (e.g. expand !REVERSE syntax)
- Create new file in lib/dotenv/substitutions/ (e.g. reverse.rb) implementing a class with call(value, env) method (
lib/dotenv/substitutions/reverse.rb) - Require the new substitution in lib/dotenv/environment.rb in the substitutions array around line 80 (
lib/dotenv/environment.rb) - Add parser spec test in spec/dotenv/parser_spec.rb to validate syntax is recognized (
spec/dotenv/parser_spec.rb) - Add environment test to verify substitution in spec/dotenv/environment_spec.rb (
spec/dotenv/environment_spec.rb)
Add a new CLI command
- Add command handler method to DotenvCLI class in lib/dotenv/cli.rb (
lib/dotenv/cli.rb) - Register command in the parser/dispatcher section of cli.rb around line 30-50 (
lib/dotenv/cli.rb) - Add test cases to spec/dotenv/cli_spec.rb for new command behavior (
spec/dotenv/cli_spec.rb) - Update README.md with usage documentation for the new command (
README.md)
Add support for a new .env file format variant
- Extend tokenizer logic in lib/dotenv/parser.rb parse() method to recognize new syntax patterns (
lib/dotenv/parser.rb) - Add test fixture file in spec/fixtures/ with examples of the new format (
spec/fixtures/newformat.env) - Add test case to spec/dotenv/parser_spec.rb covering parse() with the new fixture (
spec/dotenv/parser_spec.rb) - Verify round-trip behavior (load, interpolate, store) in spec/dotenv/environment_spec.rb (
spec/dotenv/environment_spec.rb)
Add a Rails hook or configuration option
- Define configuration accessor in the Dotenv module at top of lib/dotenv.rb or create lib/dotenv/configuration.rb (
lib/dotenv.rb) - Use configuration in Railtie or rails.rb to change load behavior at initialization time (
lib/dotenv/rails.rb) - Add test case in spec/dotenv/rails_spec.rb to verify configuration is applied during Rails boot (
spec/dotenv/rails_spec.rb)
🔧Why these technologies
- Ruby Regexp-based parser — Dotenv needs lightweight, no-dependency parsing of shell-like syntax; Regexp provides sufficient precision for .env format without external gems.
- Rails Railtie integration — Hooks into Rails initialization lifecycle to load .env before environment is locked; avoids require-order issues and respects environment detection.
- ENV mutation via Hash assignment — ENV is global mutable state by design in Ruby; dotenv directly modifies it during boot, making variables available immediately to all code.
- File-based configuration discovery — Simplest approach: .env file in project root requires no tooling setup; allows dev environments without database or secrets manager access.
⚖️Trade-offs already made
-
Load .env at Rails boot time, not lazy on access
- Why: Ensures all code sees variables consistently; avoids race conditions and initialization order bugs.
- Consequence: Adds ~100–200ms to Rails startup; cannot reload without app restart or explicit Dotenv.reload!
-
Support variable substitution ($VAR and $(cmd)) in values
- Why: Allows DRY configuration and conditional values based on environment.
- Consequence: Adds parsing complexity and potential security surface (shell command expansion must be explicit and documented).
-
Plain-text .env files (no encryption)
- Why: Lowest friction for development; .env is meant for local dev config, not production secrets.
- Consequence: Developers must never commit .env to version control; production secrets must use other mechanisms (env vars, vaults).
-
Use .env.development, .env.test, .env.local naming convention
- Why: Mirrors Rails environments and allows machine-local overrides without VCS pollution.
- Consequence: Developers must understand multiple files and load order; risk of forgotten .env.local values breaking CI.
🚫Non-goals (don't propose these)
- Does not encrypt or sign .env files; designed for development only
- Does not validate variable format or types; treats all values as strings
- Does not provide secret rotation or versioning
- Does not integrate with third-party vaults (AWS Secrets Manager, HashiCorp Vault, etc.)
- Does not handle real-time file watching or hot-reload in production
- Does not support distributed configuration or multi-server sync
🪤Traps & gotchas
No surprising required env vars or external services. Key gotchas: (1) First value set wins — when loading multiple files or ENV vars already exist, dotenv won't overwrite unless overwrite: true passed, which can be unintuitive. (2) Rails autorestore disabled by default if climate_control or ice_age gems are present — check config.dotenv.autorestore in test environment config. (3) Command substitution via $(...) requires shell access and can be a security vector if .env comes from untrusted sources. (4) The .env.local files are environment-specific but loaded in addition to base .env, not as overrides — load order matters (base → environment-specific → local).
🏗️Architecture
💡Concepts to learn
- Environment Variable Substitution — dotenv supports
${VAR}and$(command)syntax in .env files for composing variables from other values, essential for DRY configuration management - File Load Order and Precedence — Understanding that dotenv loads multiple files in order and first-set-wins prevents configuration bugs where environment variables don't override as expected
- Railtie Pattern — dotenv uses Rails Railtie hooks (in lib/dotenv/rails.rb) for auto-loading; understanding this pattern is key to how the gem integrates with Rails lifecycle
- Test Suite State Restoration (Autorestore) — dotenv 3.0+ automatically saves and restores ENV state between tests via callbacks, preventing test pollution — a non-obvious feature that affects test isolation
- Twelve-Factor App Configuration — dotenv enforces the twelve-factor principle of storing config in environment variables separate from code, the core design philosophy behind the gem
- Lexical Parsing and Tokenization — The parser.rb module implements custom tokenization to handle .env syntax edge cases (quoted strings, escape sequences, comments), not using a generic YAML/JSON parser
- ENV Hash Diff Tracking — lib/dotenv/diff.rb tracks which variables were added/modified by dotenv vs. pre-existing, enabling selective restoration without affecting system ENV vars
🔗Related repos
motdotla/dotenv— The JavaScript/Node.js original inspiration and parallel implementation of the same dotenv pattern across ecosystemsthoughtbot/climate_control— Alternative Ruby gem for ENV management with reversible modifications, often used in tests as a drop-in replacementrails/rails— Primary integration target — Rails loads dotenv automatically via the Railtie hook in lib/dotenv/rails.rbpresidentbeef/brakeman— Security scanner that flags hardcoded credentials, complementary tool for teams using dotenv to manage secrets safelydpep/ice_age_rb— Alternative ENV state management gem for tests that disables dotenv autorestore when present, as mentioned in README
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive integration tests for dotenv-rails auto-loading behavior
The repo has spec/dotenv/rails_spec.rb but lacks integration tests verifying that .env files are correctly auto-loaded in different Rails environments (development, test, production) and that the load order respects .env.development.local, .env.test, etc. This is critical since dotenv-rails is a separate gem in dotenv.gemspec and lib/dotenv-rails.rb with its own entry point.
- [ ] Create spec/integration/rails_autoload_spec.rb to test Rails environment-specific loading
- [ ] Test that .env, .env.{RAILS_ENV}, and .env.{RAILS_ENV}.local are loaded in correct precedence order
- [ ] Add tests for lib/dotenv/rails-now.rb behavior to ensure eager-loading works correctly
- [ ] Verify autorestore functionality in spec/fixtures with test/autorestore_test.rb scenarios
Add missing CLI tests for edge cases in lib/dotenv/cli.rb
spec/dotenv/cli_spec.rb exists but the CLI implementation supports various flags and options. Missing tests for error handling, file not found scenarios, and validation of the bin/dotenv executable behavior with malformed .env files or permission errors.
- [ ] Add tests in spec/dotenv/cli_spec.rb for non-existent file paths with proper error messages
- [ ] Test --help and --version flags to ensure bin/dotenv outputs correct information
- [ ] Add tests for parsing errors (invalid syntax) with meaningful error output
- [ ] Test file permission scenarios and graceful degradation
Expand spec/dotenv/parser_spec.rb to cover all substitution scenarios
The parser supports variable substitution (lib/dotenv/substitutions/variable.rb) and command substitution (lib/dotenv/substitutions/command.rb), but spec/dotenv/parser_spec.rb likely lacks comprehensive coverage for edge cases like nested substitutions, escaped delimiters, and fallback syntax.
- [ ] Add tests for variable substitution with ${VAR:-default} syntax
- [ ] Test command substitution with $(command) and edge cases like quotes in commands
- [ ] Add tests for nested/chained substitutions and escape sequences
- [ ] Test parser behavior with the fixture files (spec/fixtures/quoted.env, exported.env, yaml.env) to ensure all formats are fully covered
🌿Good first issues
- Add test coverage for
.env.localloading behavior:spec/dotenv/has separate test files for.env.developmentand.env.testbut no dedicated spec for how.localvariants interact with base files and environment precedence. - Document the
Dotenv.modifyAPI with examples: README covers.load,.save, and.restorebut themodify(hash) { ... }helper for scoped ENV changes lacks usage documentation despite being mentioned in README snippet. - Extend CLI to support
--checkflag:lib/dotenv/cli.rblacks a validation mode to check if.envfile syntax is valid without executing commands — useful in CI pipelines.
⭐Top contributors
Click to expand
Top contributors
- @bkeepers — 77 commits
- @i7an — 3 commits
- @grosser — 2 commits
- @mark-young-atg — 2 commits
- @olleolleolle — 2 commits
📝Recent commits
Click to expand
Recent commits
34156bf— Prepare for 3.2.0 release (bkeepers)ab47820— Merge pull request #531 from grosser/grosser/warn (bkeepers)fae6120— Merge branch 'main' into grosser/warn (bkeepers)4f510f4— Merge pull request #532 from grosser/grosser/fixes (bkeepers)959e1da— Merge pull request #539 from bkeepers/dependabot/github_actions/actions/checkout-6 (bkeepers)041451e— Update spec message (bkeepers)b300f26— Bump actions/checkout from 4 to 6 (dependabot[bot])5f4ca01— Merge branch 'main' into grosser/warn (bkeepers)209dca4— Merge pull request #540 from i7an/handle-parentheses (bkeepers)48c4956— Merge branch 'main' into handle-parentheses (bkeepers)
🔒Security observations
The dotenv gem has a reasonable security posture for a configuration management library. The primary concerns are: (1) potential command injection risks in the substitution feature that warrants code review, (2) test fixtures using realistic .env patterns that could mislead developers, and (3) standard best practices around parser robustness and disclosure policies. No hardcoded secrets were detected in visible files, and the project appears well-maintained with CI/CD workflows. The library's purpose of managing environment variables is inherently security-sensitive, so careful input handling and clear documentation of limitations are critical.
- Medium · Potential Command Injection in Substitutions —
lib/dotenv/substitutions/command.rb. The file 'lib/dotenv/substitutions/command.rb' suggests the library supports command substitution in .env files. This could allow arbitrary command execution if user-controlled input is processed without proper validation or sanitization. Fix: Review command substitution implementation to ensure proper input validation, escape sequences, and sandboxing. Consider restricting allowed commands or disabling this feature by default. - Medium · Environment Variable Exposure in Version Control —
spec/fixtures/.env*. Multiple .env fixture files exist in the spec/fixtures directory (e.g., spec/fixtures/.env, spec/fixtures/.env.development, spec/fixtures/.env.local). While these are test fixtures, the presence of actual environment configuration patterns could serve as a template for developers to accidentally commit real .env files with secrets. Fix: Ensure .env and .env.*.local files are in .gitignore. Consider renaming test fixtures to .env.example or .env.sample to avoid confusion. Document best practices for .env handling. - Low · Limited Input Validation in Parser —
lib/dotenv/parser.rb. The 'lib/dotenv/parser.rb' file parses .env file contents. Without visible review of the parsing logic, there's a potential risk of malformed input causing unexpected behavior or crashes. Fix: Ensure the parser has comprehensive error handling for malformed input, edge cases (very long lines, special characters), and follows secure parsing practices. Add fuzzing tests. - Low · Missing Security Policy Documentation —
Repository root. No visible SECURITY.md or security policy file found in the repository structure to guide security researchers on responsible disclosure. Fix: Create a SECURITY.md file with responsible disclosure guidelines and security contact information.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.