Add skills/ folder with dotenv and dotenvx agent skills for AI coding agent discovery#940
Add skills/ folder with dotenv and dotenvx agent skills for AI coding agent discovery#940
Conversation
Co-authored-by: motdotla <3848+motdotla@users.noreply.github.com>
|
@copilot is this correct? how will skills.sh automatically find it. can you point me to their code or documentation that this will actually work before i publish |
Yes, it's correct. The From the vercel-labs/skills README under Skill Discovery:
So Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
|
hmm, maybe i should just put it under /skills though then, and then have multiple skills inside? like it would be nice to add a skill on how to do encryption using dotenvx, etc. if you were to break this into a folder with different skills what would you do? |
|
@copilot hmm, maybe i should just put it under /skills though then, and then have multiple skills inside? like it would be nice to add a skill on how to do encryption using dotenvx, etc. if you were to break this into a folder with different skills what would you do? |
Co-authored-by: motdotla <3848+motdotla@users.noreply.github.com>
Done in 7c080d1. The root
npx skills add motdotla/dotenv --skill dotenvxThe two skills cross-link each other so agents can navigate between them. More skills (e.g. a dedicated debugging or migration skill) can be added to |
|
@copilot hmm, shouldn't the skill files just be the same as each of their readmes though? the readmes are so good and complete: dotenvCreate a # .env
S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"And as early as possible in your application, import and configure dotenv: require('dotenv').config() // or import 'dotenv/config' if you're using ES6
...
console.log(process.env) // remove this after you've confirmed it is workingThat's it.
AdvancedES6Import with ES6: import 'dotenv/config'ES6 import if you need to set config options: import dotenv from 'dotenv'
dotenv.config({ path: '/custom/path/to/.env' })bunbun add dotenvyarnyarn add dotenvpnpmpnpm add dotenvMonoreposFor monorepos with a structure like # app/backend/.env
S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"Multiline ValuesIf you need multiline variables, for example private keys, those are now supported ( PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...
Kh9NV...
...
-----END RSA PRIVATE KEY-----"Alternatively, you can double quote strings and use the PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nKh9NV...\n-----END RSA PRIVATE KEY-----\n"CommentsComments may be added to your file on their own line or inline: # This is a comment
SECRET_KEY=YOURSECRETKEYGOESHERE # comment
SECRET_HASH="something-with-a-#-hash"Comments begin where a ParsingThe engine which parses the contents of your file containing environment variables is available to use. It accepts a String or Buffer and will return an Object with the parsed keys and values. const dotenv = require('dotenv')
const buf = Buffer.from('BASIC=basic')
const config = dotenv.parse(buf) // will return an object
console.log(typeof config, config) // object { BASIC : 'basic' }Preload
You can use the $ node -r dotenv/config your_script.jsThe configuration options below are supported as command line arguments in the format $ node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.env dotenv_config_debug=trueAdditionally, you can use environment variables to set configuration options. Command line arguments will precede these. $ DOTENV_CONFIG_<OPTION>=value node -r dotenv/config your_script.js$ DOTENV_CONFIG_ENCODING=latin1 DOTENV_CONFIG_DEBUG=true node -r dotenv/config your_script.js dotenv_config_path=/custom/path/to/.envVariable ExpansionUse dotenvx for variable expansion. Reference and expand variables already on your machine for use in your .env file. # .env
USERNAME="username"
DATABASE_URL="postgres://${USERNAME}@localhost/my_database"// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)$ dotenvx run --debug -- node index.js
[dotenvx@0.14.1] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_databaseCommand SubstitutionUse dotenvx for command substitution. Add the output of a command to one of your variables in your .env file. # .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)$ dotenvx run --debug -- node index.js
[dotenvx@0.14.1] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_databaseEncryptionUse dotenvx for encryption. Add encryption to your Multiple EnvironmentsUse dotenvx to manage multiple environments. Run any environment locally. Create a $ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f=.env.production -- node index.js
Hello production
> ^^or with multiple .env files $ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f=.env.local -f=.env -- node index.js
Hello localProductionUse dotenvx for production deploys. Create a $ echo "HELLO=production" > .env.productionEncrypt it. $ dotenvx encrypt -f .env.productionSet Commit your Dotenvx will decrypt and inject the secrets at runtime using SyncingUse dotenvx to sync your .env files. Encrypt them with This still subscribes to the twelve-factor app rules by generating a decryption key separate from code.
Agents
QuickstartInstall vestauth and initialize your agent. npm i -g vestauth
vestauth agent initYour agent vestauth agent curl -X POST https://as2.dotenvx.com/set -d '{"KEY":"value"}'And your agent vestauth agent curl "https://as2.dotenvx.com/get?key=KEY"That's it! This new primitive unlocks secrets access for agents without human-in-the-loop, oauth flows, or API keys. It's the future for agents.
FAQShould I have multiple `.env` files?We recommend creating one
Additionally, we recommend using dotenvx to encrypt and manage these. How do I use dotenv with `import`?Simply.. // index.mjs (ESM)
import 'dotenv/config' // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import
import express from 'express'A little background..
What does this mean in plain language? It means you would think the following would work but it won't.
class Client {
constructor (apiKey) {
console.log('apiKey', apiKey)
this.apiKey = apiKey
}
}
export default new Client(process.env.API_KEY)
// Note: this is INCORRECT and will not work
import * as dotenv from 'dotenv'
dotenv.config()
import errorReporter from './errorReporter.mjs' // process.env.API_KEY will be blank!
Instead, import 'dotenv/config'
import errorReporter from './errorReporter.mjs'Does that make sense? It's a bit unintuitive, but it is how importing of ES6 modules work. Here is a working example of this pitfall. There are two alternatives to this approach:
Can I customize/write plugins for dotenv?Yes! const dotenv = require('dotenv')
const variableExpansion = require('dotenv-expand')
const myEnv = dotenv.config()
variableExpansion(myEnv)What rules does the parsing engine follow?The parsing engine currently supports the following rules:
What about syncing and securing .env files?Use dotenvx to unlock syncing encrypted .env files over git. What if I accidentally commit my `.env` file to code?Remove it, remove git history and then install the git pre-commit hook to prevent this from ever happening again. What happens to environment variables that were already set?By default, we will never modify any environment variables that have already been set. In particular, if there is a variable in your If instead, you want to override require('dotenv').config({ override: true })How can I prevent committing my `.env` file to a Docker build?Use the docker prebuild hook. # Dockerfile
...
RUN curl -fsS https://dotenvx.sh/ | sh
...
RUN dotenvx prebuild
CMD ["dotenvx", "run", "--", "node", "index.js"]How come my environment variables are not showing up for React?Your React code is run in Webpack, where the If you are using If you are using other frameworks (e.g. Next.js, Gatsby...), you need to consult their documentation for how to inject environment variables into the client. Why is the `.env` file not loading my environment variables successfully?Most likely your Turn on debug mode and try again.. require('dotenv').config({ debug: true })You will receive a helpful error outputted to your console. Why am I getting the error `Module not found: Error: Can't resolve 'crypto|os|path'`?You are using dotenv on the front-end and have not included a polyfill. Webpack < 5 used to include these for you. Do the following: npm install node-polyfill-webpack-pluginConfigure your require('dotenv').config()
const path = require('path');
const webpack = require('webpack')
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new NodePolyfillPlugin(),
new webpack.DefinePlugin({
'process.env': {
HELLO: JSON.stringify(process.env.HELLO)
}
}),
]
};Alternatively, just use dotenv-webpack which does this and more behind the scenes for you.
DocsDotenv exposes four functions:
Config
const result = dotenv.config()
if (result.error) {
throw result.error
}
console.log(result.parsed)You can additionally, pass options to OptionspathDefault: Specify a custom path if your file containing environment variables is located elsewhere. require('dotenv').config({ path: '/custom/path/to/.env' })By default, Pass in multiple files as an array, and they will be parsed in order and combined with require('dotenv').config({ path: ['.env.local', '.env'] })quietDefault: Suppress runtime logging message. // index.js
require('dotenv').config({ quiet: false }) // change to true to suppress
console.log(`Hello ${process.env.HELLO}`)# .env
HELLO=World$ node index.js
[dotenv@17.0.0] injecting env (1) from .env
Hello WorldencodingDefault: Specify the encoding of your file containing environment variables. require('dotenv').config({ encoding: 'latin1' })debugDefault: Turn on logging to help debug why certain keys or values are not being set as you expect. require('dotenv').config({ debug: process.env.DEBUG })overrideDefault: Override any environment variables that have already been set on your machine with values from your .env file(s). If multiple files have been provided in require('dotenv').config({ override: true })processEnvDefault: Specify an object to write your environment variables to. Defaults to const myObject = {}
require('dotenv').config({ processEnv: myObject })
console.log(myObject) // values from .env
console.log(process.env) // this was not changed or written toParseThe engine which parses the contents of your file containing environment const dotenv = require('dotenv')
const buf = Buffer.from('BASIC=basic')
const config = dotenv.parse(buf) // will return an object
console.log(typeof config, config) // object { BASIC : 'basic' }OptionsdebugDefault: Turn on logging to help debug why certain keys or values are not being set as you expect. const dotenv = require('dotenv')
const buf = Buffer.from('hello world')
const opt = { debug: true }
const config = dotenv.parse(buf, opt)
// expect a debug message because the buffer is not in KEY=VAL formPopulateThe engine which populates the contents of your .env file to For example, customizing the source: const dotenv = require('dotenv')
const parsed = { HELLO: 'world' }
dotenv.populate(process.env, parsed)
console.log(process.env.HELLO) // worldFor example, customizing the source AND target: const dotenv = require('dotenv')
const parsed = { HELLO: 'universe' }
const target = { HELLO: 'world' } // empty object
dotenv.populate(target, parsed, { override: true, debug: true })
console.log(target) // { HELLO: 'universe' }optionsDebugDefault: Turn on logging to help debug why certain keys or values are not being populated as you expect. overrideDefault: Override any environment variables that have already been set.
CHANGELOGSee CHANGELOG.md
Who's using dotenv?These npm modules depend on it. Projects that expand it often use the keyword "dotenv" on npm. a secure dotenv–from the creator of
Quickstart
Install and use it in code just like npm install @dotenvx/dotenvx --save// index.js
require('@dotenvx/dotenvx').config()
// or import '@dotenvx/dotenvx/config' // for esm
console.log(`Hello ${process.env.HELLO}`)
or install globally - unlocks dotenv for any language, framework, or platform! with github releases 🐙curl -L -o dotenvx.tar.gz "https://github.com/dotenvx/dotenvx/releases/latest/download/dotenvx-$(uname -s)-$(uname -m).tar.gz"
tar -xzf dotenvx.tar.gz
./dotenvx help
or windows 🪟winget install dotenvx
dotenvx help
Run Anywhere$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ node index.js
Hello undefined # without dotenvx
$ dotenvx run -- node index.js
Hello World # with dotenvx
> :-DMore examples TypeScript 📘// package.json
{
"type": "module",
"dependencies": {
"chalk": "^5.3.0"
}
}// index.ts
import chalk from 'chalk'
console.log(chalk.blue(`Hello ${process.env.HELLO}`))$ npm install
$ echo "HELLO=World" > .env
$ dotenvx run -- npx tsx index.ts
Hello WorldDeno 🦕$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + Deno.env.get('HELLO'))" > index.ts
$ deno run --allow-env index.ts
Hello undefined
$ dotenvx run -- deno run --allow-env index.ts
Hello World
Bun 🥟$ echo "HELLO=Test" > .env.test
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ bun index.js
Hello undefined
$ dotenvx run -f .env.test -- bun index.js
Hello TestPython 🐍$ echo "HELLO=World" > .env
$ echo 'import os;print("Hello " + os.getenv("HELLO", ""))' > index.py
$ dotenvx run -- python3 index.py
Hello WorldPHP 🐘$ echo "HELLO=World" > .env
$ echo '<?php echo "Hello {$_SERVER["HELLO"]}\n";' > index.php
$ dotenvx run -- php index.php
Hello WorldRuby 💎$ echo "HELLO=World" > .env
$ echo 'puts "Hello #{ENV["HELLO"]}"' > index.rb
$ dotenvx run -- ruby index.rb
Hello WorldGo 🐹$ echo "HELLO=World" > .env
$ echo 'package main; import ("fmt"; "os"); func main() { fmt.Printf("Hello %s\n", os.Getenv("HELLO")) }' > main.go
$ dotenvx run -- go run main.go
Hello WorldRust 🦀$ echo "HELLO=World" > .env
$ echo 'fn main() {let hello = std::env::var("HELLO").unwrap_or("".to_string());println!("Hello {hello}");}' > src/main.rs
$ dotenvx run -- cargo run
Hello WorldJava ☕️$ echo "HELLO=World" > .env
$ echo 'public class Index { public static void main(String[] args) { System.out.println("Hello " + System.getenv("HELLO")); } }' > index.java
$ dotenvx run -- java index.java
Hello WorldClojure 🌿$ echo "HELLO=World" > .env
$ echo '(println "Hello" (System/getenv "HELLO"))' > index.clj
$ dotenvx run -- clojure -M index.clj
Hello WorldKotlin 📐$ echo "HELLO=World" > .env
$ echo 'fun main() { val hello = System.getenv("HELLO") ?: ""; println("Hello $hello") }' > index.kt
$ kotlinc index.kt -include-runtime -d index.jar
$ dotenvx run -- java -jar index.jar
Hello World.NET 🔵$ dotnet new console -n HelloWorld -o HelloWorld
$ cd HelloWorld
$ echo "HELLO=World" | Out-File -FilePath .env -Encoding utf8
$ echo 'Console.WriteLine($"Hello {Environment.GetEnvironmentVariable("HELLO")}");' > Program.cs
$ dotenvx run -- dotnet run
Hello WorldBash 🖥️$ echo "HELLO=World" > .env
$ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
Hello WorldFish 🐠$ echo "HELLO=World" > .env
$ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
Hello WorldCron ⏰# run every day at 8am
0 8 * * * dotenvx run -- /path/to/myscript.shFrameworks ▲$ dotenvx run -- next dev
$ dotenvx run -- npm start
$ dotenvx run -- bin/rails s
$ dotenvx run -- php artisan servesee framework guides Docker 🐳$ docker run -it --rm -v $(pwd):/app dotenv/dotenvx run -- node index.jsOr in any image: FROM node:latest
RUN echo "HELLO=World" > .env && echo "console.log('Hello ' + process.env.HELLO)" > index.js
RUN curl -fsS https://dotenvx.sh/install.sh | sh
CMD ["dotenvx", "run", "--", "echo", "Hello $HELLO"]see docker guide CI/CDs 🐙name: build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: curl -fsS https://dotenvx.sh/install.sh | sh
- run: dotenvx run -- node build.js
env:
DOTENV_KEY: ${{ secrets.DOTENV_KEY }}Platforms# heroku
heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx
# docker
RUN curl -fsS https://dotenvx.sh/install.sh | sh
# vercel
npm install @dotenvx/dotenvx --savesee platform guides Process Managers// pm2
"scripts": {
"start": "dotenvx run -- pm2-runtime start ecosystem.config.js --env production"
},npx# alternatively use npx
$ npx @dotenvx/dotenvx run -- node index.js
$ npx @dotenvx/dotenvx run -- next dev
$ npx @dotenvx/dotenvx run -- npm startnpm$ npm install @dotenvx/dotenvx --save{
"scripts": {
"start": "./node_modules/.bin/dotenvx run -- node index.js"
},
"dependencies": {
"@dotenvx/dotenvx": "^0.5.0"
}
}$ npm run start
> start
> ./node_modules/.bin/dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.production
Hello Worldasdf# use dotenvx with asdf
$ asdf plugin add dotenvx
$ asdf install dotenvx latestthank you @jgburet of Paris 🇫🇷 Git# use as a git submodule
$ git dotenvx run -- node index.js
$ git dotenvx run -- next dev
$ git dotenvx run -- npm startVariable ExpansionReference and expand variables already on your machine for use in your .env file. # .env
USERNAME="username"
DATABASE_URL="postgres://${USERNAME}@localhost/my_database"// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)$ dotenvx run --debug -- node index.js
[dotenvx@0.14.1] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_databaseCommand SubstitutionAdd the output of a command to one of your variables in your .env file. # .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)$ dotenvx run --debug -- node index.js
[dotenvx@0.14.1] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
Multiple Environments
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.production
Hello production
> ^^More examples multiple `.env` files$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ dotenvx run -f .env.local -f .env -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local,.env
Hello localNote subsequent files do NOT override pre-existing variables defined in previous files or env. This follows historic principle. For example, above `--overload` flag$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ dotenvx run -f .env.local -f .env --overload -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local,.env
Hello WorldNote that with `--verbose` flag$ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --verbose -- node index.js
[dotenvx][verbose] injecting env from /path/to/.env.production
[dotenvx][verbose] HELLO set
[dotenvx@1.X.X] injecting env (1) from .env.production
Hello production`--debug` flag$ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --debug -- node index.js
[dotenvx][debug] configuring options
[dotenvx][debug] {"envFile":[".env.production"]}
[dotenvx][verbose] injecting env from /path/to/.env.production
[dotenvx][debug] reading env from /path/to/.env.production
[dotenvx][debug] parsing env from /path/to/.env.production
[dotenvx][debug] {"HELLO":"production"}
[dotenvx][debug] writing env from /path/to/.env.production
[dotenvx][verbose] HELLO set
[dotenvx][debug] HELLO set to production
[dotenvx@1.X.X] injecting env (1) from .env.production
Hello production`--quiet` flagUse $ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --quiet -- node index.js
Hello production`--log-level` flagSet $ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --log-level=error -- node index.js
Hello productionAvailable log levels are `--convention` flagLoad envs using Next.js' convention or dotenv-flow convention. Set $ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env
$ dotenvx run --convention=nextjs -- node index.js
Hello development local
$ dotenvx run --convention=flow -- node index.js
Hello development local(more conventions available upon request)
Encryption
$ dotenvx encrypt
✔ encrypted (.env)
More examples `.env`$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env
Hello World`.env.production`$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env.production
Hello ProductionNote the `.env.ci`$ echo "HELLO=Ci" > .env.ci
$ dotenvx encrypt -f .env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY_CI="<.env.ci private key>" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env.ci
Hello CiNote the combine multiple encrypted .env files$ dotenvx set HELLO World -f .env
$ dotenvx set HELLO Production -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY="<.env private key>" DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (3) from .env, .env.production
Hello WorldNote the combine multiple encrypted .env files for monorepo$ mkdir app1
$ mkdir app2
$ dotenvx set HELLO app1 -f app1/.env.ci
$ dotenvx set HELLO app2 -f app2/.env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY_CI="<app1/privat ci key>,<app2/private ci key>" dotenvx run -f app1/.env.ci -f app2/.env.ci -- node index.js
[dotenvx@1.X.X] injecting env (2) from app1/.env.ci,app2/.env.ci
Hello app1
$ DOTENV_PRIVATE_KEY_CI="<app1/privat ci key>,<app2/private ci key>" dotenvx run -f app1/.env.ci -f app2/.env.ci --overload -- node index.js
[dotenvx@1.X.X] injecting env (2) from app1/.env.ci,app2/.env.ci
Hello app2Note the `--stdout`$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
$ dotenvx encrypt --stdout > .env.encryptedother curves
Advanced
CLI 📟Advanced CLI commands. `run` - Variable ExpansionReference and expand variables already on your machine for use in your .env file. # .env
USERNAME="username"
DATABASE_URL="postgres://${USERNAME}@localhost/my_database"// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)$ dotenvx run --debug -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_database`run` - Default ValuesUse default values when environment variables are unset or empty. # .env
# Default value syntax: use value if set, otherwise use default
DATABASE_HOST=${DB_HOST:-localhost}
DATABASE_PORT=${DB_PORT:-5432}
# Alternative syntax (no colon): use value if set, otherwise use default
API_URL=${API_BASE_URL-https://api.example.com}// index.js
console.log('DATABASE_HOST', process.env.DATABASE_HOST)
console.log('DATABASE_PORT', process.env.DATABASE_PORT)
console.log('API_URL', process.env.API_URL)$ dotenvx run --debug -- node index.js
[dotenvx@1.X.X] injecting env (3) from .env
DATABASE_HOST localhost
DATABASE_PORT 5432
API_URL https://api.example.com`run` - Alternate ValuesUse alternate values when environment variables are set and non-empty. # .env
NODE_ENV=production
# Alternate value syntax: use alternate if set and non-empty, otherwise empty
DEBUG_MODE=${NODE_ENV:+false}
LOG_LEVEL=${NODE_ENV:+error}
# Alternative syntax (no colon): use alternate if set, otherwise empty
CACHE_ENABLED=${NODE_ENV+true}// index.js
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('DEBUG_MODE', process.env.DEBUG_MODE)
console.log('LOG_LEVEL', process.env.LOG_LEVEL)
console.log('CACHE_ENABLED', process.env.CACHE_ENABLED)$ dotenvx run --debug -- node index.js
[dotenvx@1.X.X] injecting env (4) from .env
NODE_ENV production
DEBUG_MODE false
LOG_LEVEL error
CACHE_ENABLED true`run` - Interpolation Syntax Summary (Variable Expansion, Default/Alternate Values)Complete reference for variable interpolation patterns supported by dotenvx: # .env
DEFINED_VAR=hello
EMPTY_VAR=
# UNDEFINED_VAR is not set
# Default value syntax - use variable if set/non-empty, otherwise use default
TEST1=${DEFINED_VAR:-fallback} # Result: "hello"
TEST2=${EMPTY_VAR:-fallback} # Result: "fallback"
TEST3=${UNDEFINED_VAR:-fallback} # Result: "fallback"
# Default value syntax (no colon) - use variable if set, otherwise use default
TEST4=${DEFINED_VAR-fallback} # Result: "hello"
TEST5=${EMPTY_VAR-fallback} # Result: "" (empty, but set)
TEST6=${UNDEFINED_VAR-fallback} # Result: "fallback"
# Alternate value syntax - use alternate if variable is set/non-empty, otherwise empty
TEST7=${DEFINED_VAR:+alternate} # Result: "alternate"
TEST8=${EMPTY_VAR:+alternate} # Result: "" (empty)
TEST9=${UNDEFINED_VAR:+alternate} # Result: "" (empty)
# Alternate value syntax (no colon) - use alternate if variable is set, otherwise empty
TEST10=${DEFINED_VAR+alternate} # Result: "alternate"
TEST11=${EMPTY_VAR+alternate} # Result: "alternate" (empty but set)
TEST12=${UNDEFINED_VAR+alternate} # Result: "" (empty)Key differences:
`run` - Command SubstitutionAdd the output of a command to one of your variables in your .env file. # .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)$ dotenvx run --debug -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database`run` - Shell ExpansionPrevent your shell from expanding inline $ dotenvx run --env="HELLO=World" -- sh -c 'echo Hello $HELLO'
Hello World`run` - MultilineDotenvx supports multiline values. This is particularly useful in conjunction with Docker - which does not support multiline values. # .env
MULTILINE_PEM="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
u4QuUoobAgMBAAE=
-----END PUBLIC KEY-----"// index.js
console.log('MULTILINE_PEM', process.env.MULTILINE_PEM)$ dotenvx run -- node index.js
MULTILINE_PEM -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
u4QuUoobAgMBAAE=
-----END PUBLIC KEY-----`run` - Contextual HelpUnlike other dotenv libraries, dotenvx attempts to unblock you with contextual help. For example, when missing a custom .env file: $ dotenvx run -f .env.missing -- echo $HELLO
[MISSING_ENV_FILE] missing .env.missing file (/Users/scottmotte/Code/dotenvx/playground/apr-16/.env.missing)
[MISSING_ENV_FILE] https://github.com/dotenvx/dotenvx/issues/484 and re-run [dotenvx run -- echo]or when missing a KEY: $ echo "HELLO=World" > .env
$ dotenvx get GOODBYE
[MISSING_KEY] missing GOODBYE key`run` - multiple `-f` flagsCompose multiple $ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.local -f .env -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local, .env
Hello localNote subsequent files do NOT override pre-existing variables defined in previous files or env. This follows historic principle. For example, above `run --env HELLO=String`Set environment variables as a simple $ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run --env HELLO=String -f .env -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env, and --env flag
Hello String`run --overload`Override existing env variables. These can be variables already on your machine or variables loaded as files consecutively. The last variable seen will 'win'. $ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.local -f .env --overload -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local, .env
Hello WorldNote that with `run` - Environment Variable Precedence (Container/Cloud Deployments)When deploying applications in containers or cloud environments, you often need to override specific environment variables at runtime without modifying committed # .env.prod contains: MODEL_REGISTRY=registry.company.com/models/v1
$ echo "MODEL_REGISTRY=registry.company.com/models/v1" > .env.prod
$ echo "console.log('MODEL_REGISTRY:', process.env.MODEL_REGISTRY)" > app.js
# Without environment variable set - uses .env.prod value
$ dotenvx run -f .env.prod -- node app.js
MODEL_REGISTRY: registry.company.com/models/v1
# With environment variable set (e.g., via Azure Container Service) - environment variable takes precedence
$ MODEL_REGISTRY=registry.azure.com/models/v2 dotenvx run -f .env.prod -- node app.js
MODEL_REGISTRY: registry.azure.com/models/v2
# To force .env.prod to override environment variables, use --overload
$ MODEL_REGISTRY=registry.azure.com/models/v2 dotenvx run -f .env.prod --overload -- node app.js
MODEL_REGISTRY: registry.company.com/models/v1For container deployments: Set environment variables through your cloud provider's UI/configuration (Azure Container Service, AWS ECS, etc.) to override specific values from committed `DOTENV_PRIVATE_KEY=key run`Decrypt your encrypted $ touch .env
$ dotenvx set HELLO encrypted
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check your .env.keys files for your privateKey
$ DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env
Hello encrypted`DOTENV_PRIVATE_KEY_PRODUCTION=key run`Decrypt your encrypted $ touch .env.production
$ dotenvx set HELLO "production encrypted" -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check .env.keys for your privateKey
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env.production
Hello production encryptedNote the `DOTENV_PRIVATE_KEY_CI=key dotenvx run`Decrypt your encrypted $ touch .env.ci
$ dotenvx set HELLO "ci encrypted" -f .env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check .env.keys for your privateKey
$ DOTENV_PRIVATE_KEY_CI="122...0b8" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env.ci
Hello ci encryptedNote the `DOTENV_PRIVATE_KEY=key DOTENV_PRIVATE_KEY_PRODUCTION=key run` - Combine MultipleDecrypt your encrypted $ touch .env
$ touch .env.production
$ dotenvx set HELLO encrypted
$ dotenvx set HELLO "production encrypted" -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check .env.keys for your privateKeys
$ DOTENV_PRIVATE_KEY="122...0b8" DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (3) from .env, .env.production
Hello encrypted
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (3) from .env.production, .env
Hello production encryptedCompose any encrypted files you want this way. As long as a `run --verbose`Set log level to $ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --verbose -- node index.js
loading env from .env.production (/path/to/.env.production)
HELLO set
[dotenvx@1.X.X] injecting env (1) from .env.production
Hello production`run --debug`Set log level to $ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --debug -- node index.js
process command [node index.js]
options: {"env":[],"envFile":[".env.production"]}
loading env from .env.production (/path/to/.env.production)
{"HELLO":"production"}
HELLO set
HELLO set to production
[dotenvx@1.X.X] injecting env (1) from .env.production
executing process command [node index.js]
expanding process command to [/opt/homebrew/bin/node index.js]
Hello production`run --quiet`Use $ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --quiet -- node index.js
Hello production`run --log-level`Set $ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --log-level=error -- node index.js
Hello productionAvailable log levels are `run --strict`Exit with code $ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.missing --strict -- node index.js
[MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
[MISSING_ENV_FILE] ? add one with [echo "HELLO=World" > .env.missing]This can be useful in `run --ignore`Ignore errors like $ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.missing --ignore=MISSING_ENV_FILE -- node index.js
...`run --convention=nextjs`Load envs using Next.js' convention. Set $ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run --convention=nextjs -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.development.local, .env.local, .env.development, .env
Hello development local(more conventions available upon request) `run --convention=flow`Load envs using dotenv-flow's convention. Set $ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=local" > .env.local
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ NODE_ENV=development dotenvx run --convention=flow -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development localFurther, we recommend using $ DOTENV_ENV=development dotenvx run --convention=flow -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development local`run -fk`Specify path to $ mkdir -p apps/app1
$ touch apps/app1/.env
$ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
$ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand`get KEY`Return a single environment variable's value. $ echo "HELLO=World" > .env
$ dotenvx get HELLO
World`get KEY -f`Return a single environment variable's value from a specific $ echo "HELLO=World" > .env
$ echo "HELLO=production" > .env.production
$ dotenvx get HELLO -f .env.production
production`get KEY -fk`Specify path to $ mkdir -p apps/app1
$ touch apps/app1/.env
$ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
$ dotenvx get HELLO -fk .env.keys -f apps/app1/.env
world`get KEY --env`Return a single environment variable's value from a $ dotenvx get HELLO --env HELLO=String -f .env.production
String`get KEY --overload`Return a single environment variable's value where each found value is overloaded. $ echo "HELLO=World" > .env
$ echo "HELLO=production" > .env.production
$ dotenvx get HELLO -f .env.production --env HELLO=String -f .env --overload
World`get KEY --strict`Exit with code $ dotenvx get DOES_NOT_EXIST --strict
[MISSING_KEY] missing DOES_NOT_EXIST key`get KEY --convention=nextjs`Return a single environment variable's value using Next.js' convention. Set $ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx get HELLO --convention=nextjs
development local`get KEY --convention=flow`Return a single environment variable's value using dotenv-flow's convention. Set $ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=local" > .env.local
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ NODE_ENV=development dotenvx get HELLO --convention=flow
development localFurther, we recommend using $ DOTENV_ENV=development dotenvx get HELLO --convention=flow
development local`get` (json)Return a json response of all key/value pairs in a $ echo "HELLO=World" > .env
$ dotenvx get
{"HELLO":"World"}`get --format shell`Return a shell formatted response of all key/value pairs in a $ echo "HELLO=World" > .env
$ echo "KEY=value" >> .env
$ dotenvx get --format shell
HELLO=World KEY=valueThis can be useful when combined with or with `get --format eval`Return an $ echo "HELLO=World" > .env
$ echo "KEY=value" >> .env
$ dotenvx get --format eval
HELLO="World"
KEY="value"Note that this exports newlines and quoted strings. This can be useful for more complex .env values (spaces, escaped characters, quotes, etc) combined with $ echo "console.log('Hello ' + process.env.KEY + ' ' + process.env.HELLO)" > index.js
$ eval $(dotenvx get --format=eval) node index.js
Hello value WorldBe careful with `get --all`Return preset machine envs as well. $ echo "HELLO=World" > .env
$ dotenvx get --all
{"PWD":"/some/file/path","USER":"username","LIBRARY_PATH":"/usr/local/lib", ..., "HELLO":"World"}`get --all --pretty-print`Make the output more readable - pretty print it. $ echo "HELLO=World" > .env
$ dotenvx get --all --pretty-print
{
"PWD": "/some/filepath",
"USER": "username",
"LIBRARY_PATH": "/usr/local/lib",
...,
"HELLO": "World"
}`set KEY value`Set an encrypted key/value (on by default). $ touch .env
$ dotenvx set HELLO World
set HELLO with encryption (.env)`set KEY value -f`Set an (encrypted) key/value for another $ touch .env.production
$ dotenvx set HELLO production -f .env.production
set HELLO with encryption (.env.production)`set KEY value -fk`Specify path to $ mkdir -p apps/app1
$ touch apps/app1/.env
$ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
set HELLO with encryption (.env)Put it to use. $ dotenvx get -fk .env.keys -f apps/app1/.envUse it with a relative path. $ cd apps/app1
$ dotenvx get -fk ../../.env.keys -f .env`set KEY "value with spaces"`Set a value containing spaces. $ touch .env.ci
$ dotenvx set HELLO "my ci" -f .env.ci
set HELLO with encryption (.env.ci)`set KEY -- "- + * ÷"`If your value starts with a dash ( $ touch .env.ci
$ dotenvx set HELLO -f .env.ci -- "- + * ÷"
set HELLO with encryption (.env.ci)`set KEY value --plain`Set a plaintext key/value. $ touch .env
$ dotenvx set HELLO World --plain
set HELLO (.env)`encrypt`Encrypt the contents of a $ echo "HELLO=World" > .env
$ dotenvx encrypt
✔ encrypted (.env)
✔ key added to .env.keys (DOTENV_PRIVATE_KEY)
⮕ next run [dotenvx ext gitignore --pattern .env.keys] to gitignore .env.keys
⮕ next run [DOTENV_PRIVATE_KEY='122...0b8' dotenvx run -- yourcommand] to test decryption locally`encrypt -f`Encrypt the contents of a specified $ echo "HELLO=World" > .env
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
✔ encrypted (.env.production)
✔ key added to .env.keys (DOTENV_PRIVATE_KEY_PRODUCTION)
⮕ next run [dotenvx ext gitignore --pattern .env.keys] to gitignore .env.keys
⮕ next run [DOTENV_PRIVATE_KEY='bff...bc4' dotenvx run -- yourcommand] to test decryption locally`encrypt -fk`Specify path to $ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
✔ encrypted (apps/app1/.env)Put it to use. $ dotenvx run -fk .env.keys -f apps/app1/.envUse with a relative path. $ cd apps/app1
$ dotenvx run -fk ../../.env.keys -f .env`encrypt -k`Specify the key(s) to encrypt by passing $ echo "HELLO=World\nHELLO2=Universe" > .env
$ dotenvx encrypt -k HELLO2
✔ encrypted (.env)Even specify a glob pattern. $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt -k "HE*"
✔ encrypted (.env)`encrypt -ek`Specify the key(s) to NOT encrypt by passing $ echo "HELLO=World\nHELLO2=Universe" > .env
$ dotenvx encrypt -ek HELLO
✔ encrypted (.env)Even specify a glob pattern. $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt -ek "HO*"
✔ encrypted (.env)`encrypt --stdout`Encrypt the contents of a $ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
# .env
HELLO="encrypted:BDqDBibm4wsYqMpCjTQ6BsDHmMadg9K3dAt+Z9HPMfLEIRVz50hmLXPXRuDBXaJi/LwWYEVUNiq0HISrslzQPaoyS8Lotg3gFWJTsNCdOWnqpjF2xNUX2RQiP05kAbEXM6MWVjDr"or send to a file: $ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout > somefile.txt`decrypt`Decrypt the contents of an encrypted $ echo "HELLO=World" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx decrypt
✔ decrypted (.env)`decrypt -f`Decrypt the contents of a specified encrypted $ echo "HELLO=World" > .env
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
✔ encrypted (.env.production)
$ dotenvx decrypt -f .env.production
✔ decrypted (.env.production)`decrypt -fk`Specify path to $ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
✔ encrypted (apps/app1/.env)
$ dotenvx decrypt -fk .env.keys -f apps/app1/.env
✔ decrypted (apps/app1/.env)`decrypt -k`Decrypt the contents of a specified key inside an encrypted $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx decrypt -k HELLO
✔ decrypted (.env)Even specify a glob pattern. $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx decrypt -k "HE*"
✔ encrypted (.env)`decrypt -ek`Decrypt the contents inside an encrypted $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx decrypt -ek HOLA
✔ decrypted (.env)Even specify a glob pattern. $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx decrypt -ek "HO*"
✔ encrypted (.env)`decrypt --stdout`Decrypt the contents of an encrypted $ dotenvx decrypt --stdout
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
# .env
HELLO="World"or send to a file: $ dotenvx decrypt --stdout > somefile.txt`keypair`Print public/private keys for $ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair
{"DOTENV_PUBLIC_KEY":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}`keypair -f`Print public/private keys for $ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
$ dotenvx keypair -f .env.production
{"DOTENV_PUBLIC_KEY_PRODUCTION":"<publicKey>","DOTENV_PRIVATE_KEY_PRODUCTION":"<privateKey>"}`keypair -fk`Specify path to $ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
$ dotenvx keypair -fk .env.keys -f apps/app1/.env
{"DOTENV_PUBLIC_KEY":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}`keypair DOTENV_PRIVATE_KEY`Print specific keypair for $ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair DOTENV_PRIVATE_KEY
<privateKey>`keypair --format shell`Print a shell formatted response of public/private keys. $ echo "HELLO=World" > .env
$ dotenx encrypt
$ dotenvx keypair --format shell
DOTENV_PUBLIC_KEY=<publicKey> DOTENV_PRIVATE_KEY=<privateKey>`ls`Print all $ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ dotenvx ls
├─ .env.production
├─ .env
└─ apps
└─ backend
└─ .env`ls directory`Print all $ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ dotenvx ls apps/backend
└─ .env`ls -f`Glob $ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ touch apps/backend/.env.prod
$ dotenvx ls -f **/.env.prod*
├─ .env.production
└─ apps
└─ backend
└─ .env.prod`ls -ef`Glob $ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ touch apps/backend/.env.prod
$ dotenvx ls -ef '**/.env.prod*'
├─ .env
└─ apps
└─ backend
└─ .env`rotate`Rotate public/private keys for $ echo "HELLO=World" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx rotate
✔ rotated (.env)`rotate -f`Rotate public/private keys for a specified encrypted $ echo "HELLO=World" > .env
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
✔ encrypted (.env.production)
$ dotenvx rotate -f .env.production
✔ rotated (.env.production)`rotate -fk`Specify path to $ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
✔ encrypted (apps/app1/.env)
$ dotenvx rotate -fk .env.keys -f apps/app1/.env
✔ rotated (apps/app1/.env)`rotate -k`Rotate the contents of a specified key inside an encrypted $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx rotate -k HELLO
✔ rotated (.env)Even specify a glob pattern. $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx rotate -k "HE*"
✔ rotated (.env)`rotate -ek`Rotate the encrypted contents inside an encrypted $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx rotate -ek HOLA
✔ rotated (.env)Even specify a glob pattern. $ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
✔ encrypted (.env)
$ dotenvx rotate -ek "HO*"
✔ rotated (.env)`rotate --stdout`Rotate the contents of an encrypted $ dotenvx rotate --stdout
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
# .env
HELLO="encrypted:12345"or send to a file: $ dotenvx rotate --stdout > somefile.txt`help`Output help for $ dotenvx help
Usage: dotenvx run -- yourcommand
a secure dotenv–from the creator of `dotenv`
Options:
-l, --log-level <level> set log level (default: "info")
-q, --quiet sets log level to error
-v, --verbose sets log level to verbose
-d, --debug sets log level to debug
-V, --version output the version number
-h, --help display help for command
Commands:
run inject env at runtime [dotenvx run -- yourcommand]
get [KEY] return a single environment variable
set <KEY> <value> set a single environment variable
encrypt convert .env file(s) to encrypted .env file(s)
decrypt convert encrypted .env file(s) to plain .env file(s)
keypair [KEY] print public/private keys for .env file(s)
ls [directory] print all .env files in a tree structure
Advanced:
pro 🏆 pro
ext 🔌 extensionsYou can get more detailed help per command with $ dotenvx help run
Usage: @dotenvx/dotenvx run [options]
inject env at runtime [dotenvx run -- yourcommand]
Options:
-e, --env <strings...> environment variable(s) set as string (example: "HELLO=World") (default: [])
-f, --env-file <paths...> path(s) to your env file(s) (default: [])
-fv, --env-vault-file <paths...> path(s) to your .env.vault file(s) (default: [])
-o, --overload override existing env variables
--convention <name> load a .env convention (available conventions: ['nextjs'])
-h, --help display help for command
Examples:
$ dotenvx run -- npm run dev
$ dotenvx run -- flask --app index run
$ dotenvx run -- php artisan serve
$ dotenvx run -- bin/rails s
Try it:
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env
Hello World`--version`Check current version of $ dotenvx --version
X.X.XExtensions 🔌CLI extensions. `ext genexample`In one command, generate a $ echo "HELLO=World" > .env
$ dotenvx ext genexample
✔ updated .env.example (1)# .env.example
HELLO=""`ext genexample -f`Pass multiple $ echo "HELLO=World" > .env
$ echo "DB_HOST=example.com" > .env.production
$ dotenvx ext genexample -f .env -f .env.production
✔ updated .env.example (2)# .env.example
HELLO=""
DB_HOST=""`ext genexample directory`Generate a $ echo "HELLO=World" > .env
$ mkdir -p apps/backend
$ echo "HELLO=Backend" > apps/backend/.env
$ dotenvx ext genexample apps/backend
✔ updated .env.example (1)# apps/backend/.env.example
HELLO=""`ext gitignore`Gitignore your $ dotenvx ext gitignore
✔ ignored .env* (.gitignore)`ext gitignore --pattern`Gitignore specific pattern(s) of $ dotenvx ext gitignore --pattern .env.keys
✔ ignored .env.keys (.gitignore)`ext precommit`Prevent $ dotenvx ext precommit
[dotenvx][precommit] .env files (1) protected (encrypted or gitignored)`ext precommit --install`Install a shell script to $ dotenvx ext precommit --install
[dotenvx][precommit] dotenvx ext precommit installed [.git/hooks/pre-commit]`ext precommit directory`Prevent $ echo "HELLO=World" > .env
$ mkdir -p apps/backend
$ echo "HELLO=Backend" > apps/backend/.env
$ dotenvx ext precommit apps/backend
[dotenvx][precommit] apps/backend/.env not protected (encrypted or gitignored)`ext prebuild`Prevent Add it to your # Dockerfile
RUN curl -fsS https://dotenvx.sh | sh
...
RUN dotenvx ext prebuild
CMD ["dotenvx", "run", "--", "node", "index.js"]`ext prebuild directory`Prevent Add it to your # Dockerfile
RUN curl -fsS https://dotenvx.sh | sh
...
RUN dotenvx ext prebuild apps/backend
CMD ["dotenvx", "run", "--", "node", "apps/backend/index.js"]`ext scan`Scan for leaked secrets. $ dotenvx ext scan
100 commits scanned.
no leaks foundUses gitleaks under the hood. Library 📦Use dotenvx directly in code. `config()`Use directly in node.js code. # .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config()
console.log(`Hello ${process.env.HELLO}`)$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env
Hello WorldIt defaults to looking for a `config(path: ['.env.local', '.env'])` - multiple filesSpecify path(s) to multiple .env files. # .env.local
HELLO="Me"# .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config({path: ['.env.local', '.env']})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.local', '.env']});
console.log(`Hello ${process.env.HELLO}`)$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local, .env
Hello Me`config(overload: true)` - overloadUse # .env.local
HELLO="Me"# .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config({path: ['.env.local', '.env'], overload: true})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.local', '.env'], overload: true});
console.log(`Hello ${process.env.HELLO}`)$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local, .env
Hello World`config(quiet: true)` - quietSuppress all output (except errors). # .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], quiet: true})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.missing', '.env'], quiet: true});
console.log(`Hello ${process.env.HELLO}`)$ node index.js
Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
Hello World`config(strict: true)` - strictExit with code # .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], strict: true})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.missing', '.env'], strict: true});
console.log(`Hello ${process.env.HELLO}`)$ node index.js
Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)`config(ignore:)` - ignoreUse # .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], ignore: ['MISSING_ENV_FILE']})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.missing', '.env'], ignore: ['MISSING_ENV_FILE']});
console.log(`Hello ${process.env.HELLO}`)$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env
Hello World`config(envKeysFile:)` - envKeysFileUse # .env
HELLO="World"// index.js
require('@dotenvx/dotenvx').config({path: ['.env'], envKeysFile: '../../.env.keys'})`config(convention:)` - conventionSet a convention when using # Setup environment files
$ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env// index.js
require('@dotenvx/dotenvx').config({ convention: 'nextjs' })
console.log(`Hello ${process.env.HELLO}`)$ NODE_ENV=development node index.js
[dotenvx@1.28.0] injecting env (1) from .env.development.local, .env.local, .env.development, .env
Hello development localThis is equivalent to using $ dotenvx run --convention=nextjs -- node index.js`config(opsOff:)` - opsOffTurn off Dotenvx Ops features. // index.js
require('@dotenvx/dotenvx').config({opsOff: true})`parse(src)`Parse a // index.js
const dotenvx = require('@dotenvx/dotenvx')
const src = 'HELLO=World'
const parsed = dotenvx.parse(src)
console.log(`Hello ${parsed.HELLO}`)$ node index.js
Hello World`parse(src, {processEnv:})`Sometimes, you want to run // index.js
const dotenvx = require('@dotenvx/dotenvx')
const src = 'USER=Me'
const parsed = dotenvx.parse(src, { processEnv: {} })
console.log(`Hello ${parsed.USER}`)$ node index.js
Hello Me`parse(src, {privateKey:})`Decrypt an encrypted // index.js
const dotenvx = require('@dotenvx/dotenvx')
const src = 'HELLO="encrypted:BE9Y7LKANx77X1pv1HnEoil93fPa5c9rpL/1ps48uaRT9zM8VR6mHx9yM+HktKdsPGIZELuZ7rr2mn1gScsmWitppAgE/1lVprNYBCqiYeaTcKXjDUXU5LfsEsflnAsDhT/kWG1l"'
const parsed = dotenvx.parse(src, { privateKey: 'a4547dcd9d3429615a3649bb79e87edb62ee6a74b007075e9141ae44f5fb412c' })
console.log(`Hello ${parsed.HELLO}`)$ node index.js
Hello World`set(KEY, value)`Programmatically set an environment variable. // index.js
const dotenvx = require('@dotenvx/dotenvx')
dotenvx.set('HELLO', 'World', { path: '.env' })`set(KEY, value, {plain:})`Programmatically set a plaintext environment variable. // index.js
const dotenvx = require('@dotenvx/dotenvx')
dotenvx.set('HELLO', 'World', { plain: true })`get(KEY)` - Decryption at AccessProgrammatically get an environment variable at access/runtime. // index.js
const dotenvx = require('@dotenvx/dotenvx')
const decryptedValue = dotenvx.get('HELLO')
console.log(decryptedValue)This is known as Decryption at Access and is written about in the whitepaper.
AS2 🔐
agentic secret storage.
QuickstartInstall npm i -g vestauth
vestauth agent initYour agent can Your agent can
Ops 🏰production grade dotenvx–with operational primitives.
QuickstartInstall it and gain $ curl -sfS https://dotenvx.sh/ops | sh
$ dotenvx ops backup
✔ backed up [username/project]
⮕ next run [dotenvx-ops open] to viewCLI`ops backup`Back up .env.keys. $ dotenvx-ops backup
✔ backed up [username/project]`ops login`Log in. $ dotenvx-ops login
press Enter to open [https://ops.dotenvx.com/login/device] and enter code [D9C1-03BC]... (Y/n)
⠹ waiting on browser authorization
✔ logged in [username] to this device and activated token [dxo_6kjPifI…]`ops logout`Log out. $ dotenvx ops logout
✔ logged out [username] from this device and revoked token [dxo_5ZrwRXV…]`settings`Check and configure various settings for Ops - $ dotenvx-ops settings
Usage: dotenvx-ops settings [options] [command]
⚙️ settings
Options:
-h, --help display help for command
Commands:
username print your username
token [options] print your access token (--unmask)
device [options] print your device pubkey (--unmask)
hostname print hostname
path print path to settings file
help [command] display help for command
Whitepaper
Guides
FAQHow does encryption work?Dotenvx uses Elliptic Curve Integrated Encryption Scheme (ECIES) to encrypt each secret with a unique ephemeral key, while ensuring it can be decrypted using a long-term private key. When you initialize encryption, a DOTENV_PUBLIC_KEY (encryption key) and DOTENV_PRIVATE_KEY (decryption key) are generated. The DOTENV_PUBLIC_KEY is used to encrypt secrets, and the DOTENV_PRIVATE_KEY is securely stored in your cloud secrets manager or .env.keys file. Your encrypted .env file is then safely committed to code. Even if the file is exposed, secrets remain protected since decryption requires the separate DOTENV_PRIVATE_KEY, which is never stored alongside it. Read the whitepaper for more details. Is it safe to commit an encrypted .env file to code?Yes. Dotenvx encrypts secrets using AES-256 with ephemeral keys, ensuring that even if the encrypted .env file is exposed, its contents remain secure. The encryption keys themselves are protected using Secp256k1 elliptic curve cryptography, which is widely used for secure key exchange in technologies like Bitcoin. This means that every secret in the .env file is encrypted with a unique AES-256 key, and that key is further encrypted using a public key (Secp256k1). Even if an attacker obtains the encrypted .env file, they would still need the corresponding private key—stored separately in a secrets manager—to decrypt anything. Breaking this encryption would require brute-forcing both AES-256 and elliptic curve cryptography, which is computationally infeasible with current technology. Read the whitepaper for more details. Why am I getting the error
|
…tmatter Co-authored-by: motdotla <3848+motdotla@users.noreply.github.com>
... Done in e68383e.
Both skills cross-link each other at the bottom. |
|
I ran into an issue while reviewing PR #940. Visible progress:
Blockers found:
Quick clarification: Can you confirm that skills.sh (or any agent system) actually auto-discovers and processes nested You can see my work on the |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #940 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 3 3
Lines 476 476
=========================================
Hits 476 476 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|




AI coding agents (Claude Code, Copilot, Codex, Gemini CLI) lack a structured activation signal for dotenv — they fall back to parsing the full README or relying on training data. The skills.sh ecosystem solves this with a
SKILL.mdconvention: YAML frontmatter for agent routing + full reference docs.Changes
skills/dotenv/SKILL.md— dotenv skill following the skills.sh format:name+descriptionfor agent routingREADME.md(badges and logo image stripped; all sections preserved including Usage, Advanced, Docs, FAQ, Agents/AS2)skills/dotenvx/SKILL.md— dotenvx skill mirroring the dotenvx README:name+descriptionfor agent routingCHANGELOG.md— Unreleased entry addedInstall via skills.sh
Install a specific skill only:
Agents get the full, authoritative README content for each package — the same docs humans use — routed precisely via YAML frontmatter. The two skills cross-link each other for easy navigation.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.