
September 12, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
You switch from a Laravel 13 app to a legacy WordPress site and your shell still exports PHP 8.5 and Node 26. The wrong binary runs, Composer fails, and Vite builds against the wrong toolchain. Per-Project Environments with direnv fix that by loading a project’s variables, PATH entries, and tool versions the moment you enter its directory. I use this pattern daily across multiple client repos on Ubuntu, alongside environment variable hygiene and Git-based deploy workflows. This guide shows install steps, real .envrc files for PHP and Laravel, team setup, and the gotchas I hit in production-adjacent local work.
.envrc file in each repo; direnv loads and unloads those settings when you enter or leave the folder, keeping PHP, Node, database URLs, and secrets scoped to one project at a time.What are per-project environments with direnv and why should you use them?
direnv is a small shell extension that watches directory changes. When you cd into a folder containing an .envrc file, it evaluates that file and exports the variables it defines. When you leave, it reverses those changes. Your global shell profile stays clean.
That matters because modern stacks rarely share one toolchain. A Laravel 13 project needs PHP 8.3 or higher. A WooCommerce shop may run PHP 8.2. A Symfony 8.1 app may need PHP 8.4.1. Node versions differ too: Vite 8.x on one repo, an older build on another. Without per-project scoping, you rely on memory or brittle shell aliases.
On real client projects I often juggle legal-tech Laravel portals, WooCommerce stores, and sister sites on a shared Deployer pipeline. Each repo expects different APP_ENV, database names, and PHP binaries. direnv removes the “did I remember to run export?” step before php artisan migrate.
direnv is not a full virtual machine. It does not replace Docker Compose for Laravel when you need MySQL, Redis, and queue workers in containers. It complements containers by setting connection strings and local binary paths before you run Artisan or npm.
Think of three layers:
- Shell profile — user-wide defaults in
~/.bashrcor~/.zshrc. - direnv
.envrc— project-scoped exports and PATH changes. .envfile — application secrets read by Laravel, Symfony, or WordPress at runtime.
direnv sits between your shell and your app. It prepares the environment so php artisan serve and composer install hit the right binaries. Your framework still reads .env for database credentials. Many teams use both files together without conflict.
How do you install and configure direnv on Linux or macOS?
Installation is straightforward on Ubuntu 22/24, the platform I use for most server and local dev work. Package managers ship stable builds; the official project docs at direnv.net cover additional platforms.
Install the binary
On Ubuntu or Debian:
sudo apt update
sudo apt install direnv On macOS with Homebrew:
brew install direnv Verify the install:
direnv version
# direnv v2.34.x or newer Hook direnv into your shell
direnv must hook into your shell on startup. Add one line to the end of your profile file.
For Bash (~/.bashrc):
eval "$(direnv hook bash)" For Zsh (~/.zshrc):
eval "$(direnv hook zsh)" Reload the shell or open a new terminal tab. Without this hook, .envrc files are ignored silently. That is the most common install mistake I see.
Allow your first project
direnv refuses to run unknown .envrc files until you explicitly trust them. This prevents malicious repos from exporting harmful variables.
- Create a test directory and add a minimal
.envrc. cdinto it; direnv prints a warning and a suggested allow command.- Run
direnv allowin that directory once. - Leave and re-enter; the variables should load without prompts.
For teams, document the allow step in your README. New clones always need one direnv allow per machine. After that, automatic loading works on every visit.
How do you write an .envrc file for Laravel and PHP projects?
The .envrc file is plain shell code. direnv provides helper functions like layout, dotenv, and use that keep files readable. Below are patterns I use on Laravel 12 and 13 projects with PHP 8.3 or 8.4 and Node 26 LTS for Vite 8.x builds.
Basic Laravel .envrc
# .envrc — Laravel local dev
export APP_ENV=local
# Pin PHP when multiple versions exist (updateasdf, phpenv, or manual path)
export PATH="/usr/bin/php8.3/bin:$PATH"
# Load .env into direnv too (optional; Laravel reads .env separately)
dotenv_if_exists
# Node for Vite
use node 26
# Project-specific paths
export COMPOSER_MEMORY_LIMIT=-1
path_add PATH "./vendor/bin" Commit .envrc to Git. Never commit secrets inside it. Keep passwords in .env, which stays gitignored. If you need to reference a private value, load it from a local-only file:
dotenv_if_exists .env.local PHP version pinning with layout and use
If you use asdf or mise for runtime management, direnv integrates cleanly:
# .envrc
use asdf
layout node
The use asdf directive reads .tool-versions in the repo root. That file might contain:
php 8.3.12
nodejs 26.0.0
When you enter the directory, direnv activates those versions. This pairs well with Vite config for Laravel projects where Node and PHP must align with CI.
Database and service URLs for local stacks
When MySQL 9.7 or MariaDB 12.3 runs on localhost, point Laravel’s test runner at a dedicated database:
export DB_CONNECTION=mysql
export DB_HOST=127.0.0.1
export DB_PORT=3306
export DB_DATABASE=myapp_local
export DB_USERNAME=dev
export DB_PASSWORD=dev
# Override only for PHPUnit/Pest in this shell
export DB_DATABASE_TEST=myapp_test Pair this with database migrations in team environments so every developer hits the same schema naming rules. direnv does not run migrations; it only ensures the connection env is correct before you run them.
Monorepo and nested layouts
Some repos nest apps under apps/api and apps/web. Use source_up to inherit a parent .envrc:
# apps/api/.envrc
source_up
export APP_NAME="My API" The parent .envrc at the monorepo root sets shared Node and PHP versions. Child folders add app-specific overrides. This avoids duplicating ten lines across four packages.
Symfony and WordPress variants
Symfony 8.1 apps often rely on APP_ENV and APP_DEBUG at the shell level for console commands. A minimal Symfony .envrc:
export APP_ENV=dev
export APP_DEBUG=1
export SYMFONY_PHP="/usr/bin/php8.4"
path_add PATH "./bin" WordPress 7.1 local setups may export WP_ENV and path to WP-CLI:
export WP_ENV=development
path_add PATH "$HOME/.composer/vendor/bin" For deeper Symfony config patterns, see Symfony environment config for multi-environment apps.
How does direnv compare to Docker, dev containers, and manual exports?
Teams ask whether direnv replaces containers. In practice you pick the lightest tool that solves the reproducibility gap you actually have.
| Approach | Best for | Setup cost | Reproduces OS packages | Switch speed |
|---|---|---|---|---|
| direnv + .envrc | PATH, env vars, runtime version pins on host | Low — one file per repo | No | Instant on cd |
| Docker Compose | Full stack: PHP, MySQL, Redis, queues | Medium — images and compose file | Yes, inside containers | Slower — container start |
| Dev containers | IDE-integrated, identical dev boxes | Medium–high | Yes | Medium |
Manual export | Single-project solo dev | None | No | Error-prone |
| Vagrant | Legacy VM parity, full OS match | High — VM provisioning | Yes | Slow boot |
My default for Laravel work on Ubuntu: direnv on the host for PHP, Composer, and Node; Docker Compose when the project needs services the host should not run globally. That hybrid keeps laptop fans quiet while still giving Redis and MySQL isolated ports. Read dev containers for reproducible development and Vagrant for reproducible dev environments when you need full parity with production Linux versions managed through Linux system administration practices.
CI pipelines do not use direnv by default. GitLab CI and GitHub Actions set variables in YAML. Mirror your .envrc exports as documented env keys so local and CI stay aligned. I document the mapping in project wikis and cross-link GitLab CI/CD for PHP projects for the pipeline side.
How do teams share and secure direnv configuration?
Per-project environments only help when the whole team commits the same baseline. Treat .envrc like application code: reviewed in pull requests, versioned, and tested on a fresh clone.
What to commit versus gitignore
- Commit:
.envrc,.envrc.example,.tool-versions, and non-secret defaults. - Gitignore:
.env,.env.local, and any file holding API keys or DB passwords. - Document: required system packages, Docker services, and the one-time
direnv allowstep.
Provide an example file new hires can copy:
# .envrc.example — copy to .envrc and customize
export APP_ENV=local
use node 26
dotenv_if_exists .env.local
path_add PATH "./vendor/bin" When onboarding someone to a booking platform like Adventure Third Pole Trek, clear env docs cut setup from hours to minutes. The same applies to legal-tech portals where document upload paths must match across machines.
Editor and IDE integration
VS Code and PhpStorm terminals inherit direnv exports if they launch as login shells. If your IDE terminal ignores direnv, open the integrated terminal after cd into the project from a hooked shell, or configure the IDE to source the same profile. Validate with:
which php
php -v
echo $APP_ENV Use the JSON formatter or regex tester from the same browser session; they are unrelated to direnv but handy when debugging env-driven API config during local work.
Security considerations
The allow mechanism exists because .envrc runs arbitrary shell code. Never allow unknown repos blindly. After pulling changes that modify .envrc, direnv prompts for re-allow because the file hash changed. That is a feature, not noise.
Do not store production credentials in .envrc. Production secrets belong in the server environment or your deploy tool’s encrypted store. For staging parity, read how to set up a staging environment that mirrors production and keep production URLs out of committed files.
What are common direnv mistakes and how do you fix them?
Most failures look like “it works in my terminal but not yours.” These fixes cover the patterns I see on client handoffs and multi-repo maintenance days.
direnv: command not found after install
The hook line is missing or placed before completion plugins that overwrite it. Put eval "$(direnv hook bash)" at the very end of ~/.bashrc. Open a new terminal; do not rely on source in a subshell that skips profile files.
Variables do not unload when leaving the directory
A long-running process or subshell may retain old values. direnv unloads when the shell’s current working directory changes. Background jobs started before cd keep their inherited env. Restart queue workers and dev servers after switching projects.
Conflict between dotenv and Laravel .env
dotenv in direnv exports variables into your shell. Laravel also loads .env into PHP. Duplicated keys can confuse debugging. I prefer dotenv_if_exists only for local overrides like .env.local, not the main Laravel file. Laravel’s own docs on configuration at laravel.com describe runtime loading; keep shell and app layers distinct in your mental model.
Wrong PHP binary despite .envrc
Another tool may prepend PATH later in your profile. Run which -a php to see ordering. Move conflicting managers below the direnv hook or set an absolute path in .envrc:
export PATH="/usr/bin/php8.3:$PATH"
# or
layout php On servers with PHP 8.3, 8.4, and 8.5 side-by-side, explicit paths beat assumptions. That mirrors how I configure PHP-FPM on Ubuntu production boxes.
CI/local drift
If CI uses PHP 8.2 but your .envrc pins 8.3, tests pass locally and fail in the pipeline. Pin the same minor version in .tool-versions, .envrc, and .gitlab-ci.yml. For infrastructure-level env separation, compare with Terraform workspaces and environments and managing multiple environments in IaC — different layer, same discipline.
Run direnv status inside the project for a concise report: loaded or not, which .envrc file, and hash state. That single command saves long threads in team chat.
Key Takeaways
- Install direnv, add the shell hook once, then run
direnv allowper repo to activate per-project environments with direnv. - Commit
.envrcand.tool-versions; keep secrets in gitignored.envor.env.localfiles only. - Pair direnv with Docker Compose when you need MySQL, Redis, or mail catchers — not everything belongs on the host.
- Pin PHP and Node versions explicitly; Laravel 13 needs PHP 8.3+, and Vite 8.x expects a current Node 26 LTS toolchain.
- Mirror local
.envrcexports in CI variables so pipelines and laptops run the same minor versions. - Re-run
direnv allowafter trusted teammates change.envrc; the hash guard is intentional security.
People Also Ask
Does direnv work with fish shell?
Yes. Install direnv, then run direnv hook fish | source in your fish config. The same .envrc file works across Bash, Zsh, and fish with no changes. Teams mixed on shells can share one committed config.
Can direnv load variables from a .env file automatically?
Yes. Use dotenv or dotenv_if_exists inside .envrc to export keys from a dotenv file into your shell. Laravel still reads .env through PHP; use direnv’s dotenv helpers mainly for shell-only tools like custom scripts or Make targets.
Is direnv safe to use on client projects with secrets?
Safe when you follow the allow workflow and never commit secrets. Treat .envrc as executable code subject to review. Store passwords and API keys in gitignored env files or your password manager, and export them locally through .env.local loaded by direnv.
How is direnv different from using nvm or asdf alone?
Version managers switch runtimes when you invoke them or when configured globally. direnv ties those switches to directory entry and exit automatically. Combining use asdf in .envrc with asdf’s per-project .tool-versions gives you hands-free changes every time you cd.
Build consistent local setups across every repo
Per-Project Environments with direnv cost almost nothing to adopt and pay off the first time you stop running the wrong PHP binary against a production-bound Laravel app. Start with one active repo: add the hook, write a ten-line .envrc, document the allow step, and align CI variables next. If you want help standardising dev environments across a multi-app portfolio or a legal-tech platform, see custom software development services, browse the project portfolio, or contact us to talk through your stack on kokil.com.np.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

