Skip to content

Environment Variables Reference

Ahoy can load environment variables from files before executing any command. Files are declared using the env field at the config level (global) or within individual commands (per-command). Both levels accept the same three forms:

  • A single file path: env: .env
  • An array of file paths v2.5.0+
  • An array mixing file paths with { path, optional } mapping entries v3.0.1+, which mark the files that are allowed to be absent - see Optional env files

All env file paths are relative to the directory containing the .ahoy.yml file - not your current working directory. This ensures consistent behaviour regardless of which subdirectory you run commands from.

Variables are applied in this order, from lowest to highest priority:

  1. Shell environment - variables already in your shell when you run ahoy
  2. Global env files - loaded in the order listed under the top-level env key
  3. Command-specific env files - loaded in the order listed under a command’s env key

Later sources override earlier ones. A command-specific variable always wins over a global one with the same name.

Each entry under an env key is required by default. When a required file is absent, Ahoy writes a warning to stderr and carries on:

[warn] environment file '.env.local' not found, continuing without it. Add 'optional: true' to that entry if it is meant to be absent.

The command still runs, and the exit code is unchanged; only the reporting is new. Because the warning goes to stderr, a command’s stdout stays clean for piping.

To declare a file that is expected to be missing, write the entry as a mapping with path and optional:

env:
- .env.base # Required
- path: .env.local
optional: true # Absent without comment
- path: .env.deploy
optional: false # Same as the plain string form

optional means the same thing here as it does for imports: the entry may legitimately not resolve to a file. It is set on the env entry itself, though - the command-level optional flag covers imports only, and does not silence a missing env file.

The two forms mix freely within one list, and both work at the global and per-command level. Every pre-v3.0.1 form parses unchanged: the env: .env shorthand, plain lists, empty lists and nulls.

ahoy config validate reflects the distinction:

🌍 Environment Files:
✅ .env.base (global)
➖ .env.local (global) - missing, marked optional
❌ .env.missing (global) - missing

Ahoy automatically injects two variables into every command’s environment, regardless of any env file configuration:

Variable Value
AHOY_COMMAND_NAME The name of the command being run
AHOY_CMD Path to the ahoy binary

These cannot be overridden by env files.

Ahoy env files use standard shell variable format. The parser follows these rules:

  • One variable per line: NAME=VALUE
  • Lines starting with # are treated as comments and ignored
  • Empty lines are ignored
  • Leading and trailing whitespace on each line is trimmed
  • The export keyword prefix is accepted but has no effect
  • Variable references within values (e.g. ${OTHER_VAR}) are not resolved by Ahoy - they are passed through as literals and may be expanded by the shell when the command runs
Terminal window
# Simple values
DB_HOST=localhost
DB_PORT=3306
APP_NAME=myapp
# Values with spaces (quotes are part of the value - the shell strips them)
FULL_NAME="Jane Smith"
GREETING='Hello, World!'
# Comments are fully ignored
# This line does nothing
# Inline comments are also ignored
API_KEY=abc123def456 # production key
# Empty value (sets the variable to an empty string)
OPTIONAL_VALUE=
# Export prefix (accepted but no special meaning)
export DATABASE_URL=postgres://user:pass@localhost/myapp
# Multi-line values
SSH_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"
  • Variable interpolation within the file (API_URL=${BASE_URL}/api is passed through literally)
  • Backslash continuation across lines
  • Shell arithmetic or command substitution

If you need these, set variables in a shell script and call that script from your cmd.

By convention, environment variable names use UPPER_SNAKE_CASE. Names can contain letters, digits, and underscores. Names starting with a digit are technically invalid in most shells.

Never commit real credentials to version control. A common pattern:

project/
├── .env.example # Committed - contains placeholder values only
├── .env # Not committed - contains real local values
└── .gitignore # Contains ".env" pattern
.gitignore
.env
.env.local
.env.*.local
*.secrets

By environment:

.env # Shared defaults
.env.local # Developer overrides (not committed)
.env.staging # Staging-specific (may be committed)
.env.production # Production-specific (not committed)

By purpose:

.env # Global project defaults
.env.db # Database credentials
.env.deploy # Deployment configuration

Layered approach (useful for team projects):

env:
- .env.defaults # Committed defaults for the whole team
- .env.local # Personal overrides, gitignored

Variables are available through standard shell syntax in bash commands:

commands:
connect:
env: .env.db
cmd: mysql -u$DB_USER -p$DB_PASS -h$DB_HOST $DB_NAME

Or with defaults using bash parameter expansion:

commands:
serve:
cmd: python -m http.server ${PORT:-8000}

When using a non-bash entrypoint, access variables through the appropriate mechanism for that runtime (e.g. process.env.VARIABLE in Node.js, os.environ['VARIABLE'] in Python).