dotenv-cli: running any command with the variables from your .env file
A cli to load dotenv files
At a glance
- What is it?
- dotenv-cli is a global executable that loads one or more .env files, merges them in a defined order and runs a command with the resulting environment. It exists mainly so that the same script works in PowerShell and CMD as well as in a Unix shell, and it wraps four small dependencies.
- Who is it for?
- Adopt dotenv-cli when your team runs on Windows as well as Unix and you want one way to load .env files before a command, or when you need the -c cascade to pick the right file set per environment. Skip it if your scripts are Unix only, since a plain variable assignment prefix already does the job there with one less process in the path.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 135 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One wrapper command instead of a variable prefix per platform
dotenv-cli is a global executable that loads .env files and runs a command with the resulting environment. The usage line is short.
$ dotenv -- <command with arguments>That loads the variables from the .env file in the current working directory and then runs the command using the new set of environment variables. There is also a shorthand when the command takes no arguments of its own.
$ dotenv <command>The reason this tool exists at all is stated in the README's own explanation of the -v flag: the standard approach of setting a variable inline before a command does not work on Windows. On a Unix shell you write VARIABLE=somevalue followed by the command, and that syntax is not available in PowerShell or CMD. So the primary value of the tool is portability of the invocation, not cleverness in parsing. The second value is that it gives you somewhere to put multi-file, per-environment and variable-expansion behaviour without writing a wrapper script for each project, and it does that in one command that reads naturally in an npm script or a Makefile target.
Multiple env files and the first one wins rule
A different .env file can be selected with the -e flag, and the README is precise that this replaces loading the .env file rather than adding to it.
$ dotenv -e .env2 -- <command with arguments>Several files can be given, and they are processed in order, but a variable is only set if it has not already been set. The precedence the README describes is therefore: existing environment variables win over the first file, and the first file wins over the second file. That is a first-wins merge, not the last-wins behaviour that many dotenv loaders use, and it is the detail most likely to surprise someone. If you rely on a shell variable that happens to have the same name as a key in your file, the shell variable survives and your file is ignored for that key. The order of the flags on the command line is what decides which file is first, so the command that reads most naturally is not always the one with the precedence you want.
Cascading env files by environment name
Applications that load several .env files depending on the environment usually work with a set like .env, .env.local, .env.development and .env.development.local. dotenv-cli supports that shape with the -c flag, and the README gives both forms.
- `-c` loads `.env` and `.env.local`
- `-c test` loads `.env`, `.env.local`, `.env.test`, and `.env.test.local`So a bare -c picks the base pair, and -c with a name adds the pair for that environment. The flag composes with -e, and the documented example cascades env files that live one folder up in the directory tree.
dotenv -e ../.env -cThe README links to issue 37 for more on the cascading behaviour, which is a hint that the details here have been worked through rather than simply shipped. The combination of -e and -c is the useful one for a monorepo, where a shared file lives higher up the tree and a local override lives in the package directory.
Setting variables inline and reading them back out
A variable can be set directly on the command line with -v, and several can be given.
$ dotenv -v VARIABLE=somevalue -- <command with arguments>$ dotenv -v VARIABLE1=somevalue1 -v VARIABLE2=somevalue2 -- <command with arguments>Variables set this way have higher priority than those from env files, which is what you want for a one-off override in a CI job. The reason this exists is cross-platform again, since the -v form works everywhere. To inspect a value rather than set one, the -p flag prints it.
$ dotenv -p NODE_ENVThat is a small feature with real utility, since it answers the question of what the loader actually resolved, which is the first thing to check when a command behaves differently from what the file suggests. For debugging, the --debug flag outputs the .env files that would be processed and then exits, which is the quickest way to confirm which files a cascade is picking up. And the -o flag overrides variables that have already been set on your machine with the values from your .env file, which inverts the default precedence.
dotenv -e .env.test -o -- jestExpansion happens in the file, not in your shell
dotenv-cli supports expanding env variables inside .env files, following the dotenv-expand package. The README gives this example file.
IP=127.0.0.1
PORT=1234
APP_URL=http://${IP}:${PORT}With that file, process.env.APP_URL resolves to http://127.0.0.1:1234. Values that should not be expanded, such as a password written as PASSWORD="pas$word", can be protected by the --no-expand flag.
dotenv --no-expand <command>The more interesting limitation is what happens when you expect expansion in the command itself. The README is explicit that this does not happen and explains why: your shell will first interpret your command before passing it to dotenv-cli, so a variable like SAY_HI set in the file is already substituted in the command line, which is why dotenv-cli cannot make the expansion you expect. The documented workarounds are two. The first is to hand the command to bash with the variable inside single quotes, so the shell passes it through literally and bash performs the expansion in the correct environment.
$ dotenv -- bash -c 'echo "$SAY_HI"'The second is to put the script in a subscript, for example an npm script that calls dotenv and then calls another npm script.
{
"scripts": {
"_print-stuff": "echo $STUFF",
"print-stuff": "dotenv -- npm run _print-stuff"
}
}The README notes that this second pattern expects a project with a package.json and that dotenv-cli should then be installed locally with npm install -D dotenv-cli.
Passing flags through to the inner command
Argument handling has one rule and one gotcha. The rule is that flags intended for the inner command go after a double dash, and the README's example is a Maven invocation whose own flags would otherwise be swallowed.
$ dotenv -- mvn exec:java -Dexec.args="-g -f"With a different env file the same form applies.
$ dotenv -e .my-env -- mvn exec:java -Dexec.args="-g -f"The gotcha is the ordering of the flags. The README says that if you want to pass flags to the inner command you use -- after all the flags to dotenv-cli, which means the separator comes after -e, -v, -c, -p and the rest. The examples follow that order consistently, with the inner command last. The package.json makes the implementation plain, since the bin entry maps the name dotenv to cli.js and the dependency list shows cross-spawn for launching the child process, dotenv for parsing, dotenv-expand for the variable expansion and minimist for argument parsing. That is four dependencies, all small and well known, and the top level of the repository is correspondingly small, with cli.js, package.json, a yarn lock and the README.
Version 11, quiet by default, and the maintenance picture
The release history explains the recent behaviour changes. v11.0.0 is titled Bump dotenv-expand to ^12 and is dated 2025-10-28, v10.0.0 is bump dotenv-expand from 2025-07-29, and v9.0.0 is Bump dotenv and be quiet by default from 2025-07-22. The dependency list in the current package.json confirms the direction, with dotenv at ^17.1.0, dotenv-expand at ^12.0.0, cross-spawn at ^7.0.6 and minimist at ^1.2.6. The library is on dotenv 17 while the tool itself is at version 11, which tells you the two version lines move independently. The be quiet by default change is worth noting if you have scripts that parse dotenv-cli output, since extra lines on stdout used to be safe and now may not be. The last push to the repository was on 2026-06-10, and the repository is not archived, so the work has not stopped, but the gap between that push and the most recent tag in the list, v11.0.0 from 2025-10-28, is the kind of thing to check before you pin. The project is MIT licensed, the lint script runs standard, and the package declares yarn as its package manager.
A shell prefix, a Makefile target or a container environment instead
The alternatives are ordinary and worth weighing honestly. On Unix the shell prefix already does the job with no extra process, so dotenv-cli buys you portability and multi-file handling rather than basic functionality. A Makefile or npm script that exports variables before invoking the target does the same thing without a new dependency, at the cost of being shell specific in exactly the same way the prefix is. A container based approach, where the environment is baked into an image or passed with docker run --env-file, is the other common answer, and it moves the configuration into the deployment layer where some teams want it, but it changes the shape of local development. dotenv-cli's position is narrow and specific, a portable shim that keeps the .env file convention intact across platforms and adds the cascade and expansion behaviour that convention implies. If your whole team is on Unix and each project has one .env file, the prefix wins on simplicity. If anyone on your team runs PowerShell, or you have four environments per project, this becomes the shorter answer, and the dependency count is small enough that adding it is cheap.
Editorial conclusion
Adopt dotenv-cli when your team runs on Windows as well as Unix and you want one way to load .env files before a command, or when you need the -c cascade to pick the right file set per environment. Skip it if your scripts are Unix only, since a plain variable assignment prefix already does the job there with one less process in the path. Verify first that your shell is not expanding your variables before dotenv-cli sees them, since the README shows that the expected expansion does not happen without an extra shell layer, and check the --no-expand flag if any value contains a literal dollar sign.
Frequently asked questions
How do I install dotenv-cli and run a command with .env variables?
Install it globally with npm install -g dotenv-cli, yarn global add dotenv-cli, or pnpm add -g dotenv-cli. Then run dotenv -- followed by the command, which loads .env from the current directory and executes the command with those variables set.
How do I load different env files for different environments with dotenv-cli?
Use the -c flag, where a bare -c loads .env and .env.local, and -c test loads .env, .env.local, .env.test and .env.test.local. The flag can be combined with -e to point at files elsewhere, for example dotenv -e ../.env -c.
Why does dotenv not expand a variable when I use it in the command?
Because your shell interprets the command before dotenv-cli receives it, so the variable is substituted too early. The README suggests wrapping the command in bash -c with single quotes, or running a separate npm script that dotenv invokes, to get the expansion you expect.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/entropitor-dotenv-cli)Was this useful?