
How to Set Environment Variables: Complete Guide for Linux, macOS, and Windows
Learn how to set environment variables on Linux, macOS, and Windows using CLI and GUI. Covers export, set, setx, .env files, Docker, CI/CD, and team workflows with practical examples.
How to Set Environment Variables
Your deploy just failed because someone forgot to set DATABASE_URL. Or your app is connecting to the wrong database because a developer's local config drifted from staging. Environment variables are the fix — and knowing how to set them correctly across every platform saves you from these exact moments.
Whether you're setting a database URL, an API key, or adding a tool to PATH, the process varies depending on your operating system and shell. This guide covers everything from quick one-liners to persistent system-wide configuration, with practical examples for every major platform.
What Are Environment Variables?
If you're new to environment variables, here's the quick version: they're key-value pairs that your operating system stores and passes to running processes. When you launch an application, it inherits these variables from its parent process (usually your shell).
Think of them as configuration settings that live outside your code. Instead of hardcoding DATABASE_URL="postgres://localhost:5432/mydb" in your application, you set it as an environment variable and read it at runtime. This means you can use the same codebase across development, staging, and production environments with different configurations.
The most common use cases: API keys, database credentials, feature flags, build configurations, and modifying application behavior without recompiling. Every process can read its environment variables, but changes only affect child processes launched afterward.
How to Set a Single Environment Variable
The syntax for setting a single environment variable differs across operating systems and shells. Here's the quick reference:
Linux and macOS (Bash/Zsh):
export API_KEY="your-secret-key-here"
This sets the variable in your current shell session and makes it available to all programs you launch from that shell. For platform-specific persistence options and system-wide configuration, see the Linux deep dive.
Windows Command Prompt:
set API_KEY=your-secret-key-here
The CMD syntax doesn't use quotes unless you want them in the actual value. For GUI configuration, registry editing, and persistent Windows settings, check the Windows deep dive.
Windows PowerShell:
$env:API_KEY = "your-secret-key-here"
PowerShell uses a completely different syntax with the $env: prefix. For PowerShell profiles, dot-sourcing, and advanced scripting patterns, see the PowerShell guide.
All three methods create temporary variables that disappear when you close the terminal. We'll cover persistence in the next section.
How to Set Multiple Environment Variables at Once
When you need to configure several variables, typing them one by one gets tedious fast. Here are better approaches.
Multiple export commands (Bash/Zsh):
export DATABASE_URL="postgres://localhost:5432/mydb"
export REDIS_URL="redis://localhost:6379"
export API_KEY="sk_live_abc123"
export LOG_LEVEL="debug"
You can also chain them on a single line with semicolons, but separate lines are more readable.
Using .env files:
The most portable approach is creating a .env file with your variables:
# .env
DATABASE_URL=postgres://localhost:5432/mydb
REDIS_URL=redis://localhost:6379
API_KEY=sk_live_abc123
LOG_LEVEL=debug
Then load them using platform-specific tools. In Bash:
export $(cat .env | xargs)
Or with proper handling of comments and quoted values:
set -a
source .env
set +a
Docker environment files:
Docker supports .env files natively. With docker run:
docker run --env-file .env myimage
Or in docker-compose.yml:
services:
web:
image: myapp
env_file:
- .env
You can also specify multiple environment variables directly:
docker run \
-e DATABASE_URL="postgres://localhost:5432/mydb" \
-e REDIS_URL="redis://localhost:6379" \
-e API_KEY="sk_live_abc123" \
myimage
Batch files (Windows):
Create a setenv.bat file:
@echo off
set DATABASE_URL=postgres://localhost:5432/mydb
set REDIS_URL=redis://localhost:6379
set API_KEY=sk_live_abc123
set LOG_LEVEL=debug
Run it with call setenv.bat to apply the variables to your current CMD session.
Temporary vs Persistent Environment Variables
The difference between temporary and persistent variables trips up developers constantly. Here's what you need to know:
| Method | Scope | Lifetime | When to Use |
|---|---|---|---|
export VAR=value (Bash/Zsh) | Current shell + child processes | Until shell closes | Testing, one-off commands |
set VAR=value (CMD) | Current CMD session only | Until CMD closes | Temporary overrides |
$env:VAR = "value" (PowerShell) | Current PowerShell session | Until PowerShell closes | Scripts, temporary config |
| Shell config files (.bashrc, .zshrc) | All new shells for user | Until manually removed | Personal development tools |
/etc/environment (Linux) | System-wide, all users | Permanent (survives reboots) | Server configuration |
setx VAR value (Windows) | User or system-wide | Permanent | Windows persistent config |
| Windows registry | System-wide or per-user | Permanent | System administration |
Making a temporary variable permanent:
On Linux/macOS, add the export line to your shell configuration file:
echo 'export API_KEY="sk_live_abc123"' >> ~/.bashrc
source ~/.bashrc # Apply immediately
For Zsh users, use ~/.zshrc instead of ~/.bashrc.
On Windows, use setx instead of set:
setx API_KEY "sk_live_abc123"
Critical gotcha: setx affects new processes only. You must close and reopen your terminal to see the change. Also, setx truncates values longer than 1024 characters, so for large values, use the GUI or registry.
For system-wide persistence on Windows (requires admin):
setx API_KEY "sk_live_abc123" /M
On Linux, system-wide variables go in /etc/environment:
sudo sh -c 'echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk" >> /etc/environment'
Using .env Files
The .env file has become the de facto standard for managing environment variables across platforms and languages. The format is simple:
# Database configuration
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
DB_POOL_SIZE=20
# API credentials
STRIPE_API_KEY=sk_live_abc123
STRIPE_WEBHOOK_SECRET=whsec_xyz789
# Feature flags
ENABLE_BETA_FEATURES=true
LOG_LEVEL=info
# Quoted values with spaces
APP_NAME="My Application"
Most modern frameworks and languages have libraries to load .env files automatically:
Python with python-dotenv:
from dotenv import load_dotenv
import os
load_dotenv()
api_key = os.getenv('API_KEY')
For detailed patterns including structured configuration with Pydantic and handling different environments, see the Python environment variables guide.
Java with dotenv-java:
import io.github.cdimascio.dotenv.Dotenv;
Dotenv dotenv = Dotenv.load();
String apiKey = dotenv.get("API_KEY");
Spring Boot users can leverage @ConfigurationProperties and externalized config. Check the Java environment variables guide for Spring-specific patterns.
Node.js with dotenv:
require('dotenv').config();
const apiKey = process.env.API_KEY;
Docker Compose:
services:
web:
image: myapp
env_file:
- .env
- .env.local # Override with local settings
Docker Compose merges multiple env files, with later files taking precedence. This pattern works well for base configuration + environment-specific overrides.
Important: Never commit .env files containing secrets to version control. Add .env to your .gitignore immediately. Instead, commit a .env.example file with dummy values showing the required variables:
# .env.example
DATABASE_URL=postgres://user:password@localhost:5432/dbname
API_KEY=your_api_key_here
Common Mistakes and Gotchas
Forgetting to export (Bash/Zsh):
# Wrong - sets variable only in current shell
API_KEY="abc123"
# Correct - makes it available to child processes
export API_KEY="abc123"
Without export, the variable exists in your shell but programs you launch won't see it. This catches everyone at least once.
Spaces around the equals sign:
# Wrong - tries to run a command named "API_KEY"
API_KEY = "abc123"
# Correct
API_KEY="abc123"
Shell syntax is strict about whitespace. No spaces around = when setting variables.
Not reopening terminal after setx (Windows):
When you use setx on Windows, the change only affects new processes. Your current CMD or PowerShell session still has the old value (or no value). Close and reopen your terminal to see the new variable.
Committing .env files to Git:
This is the #1 security mistake with environment variables. Once a secret is in Git history, it's there forever unless you rewrite history. Add .env to .gitignore immediately:
echo ".env" >> .gitignore
git add .gitignore
git commit -m "Add .env to gitignore"
If you already committed secrets, rotate them immediately and use git-filter-branch or BFG Repo-Cleaner to remove them from history.
Treating everything as a string:
Environment variables are always strings. If you need a number, boolean, or list, you must parse it in your code:
# Wrong assumption
DEBUG = os.getenv('DEBUG') # Returns string "false", which is truthy
if DEBUG:
enable_debug() # This runs even when DEBUG=false
# Correct parsing
DEBUG = os.getenv('DEBUG', 'false').lower() == 'true'
PATH separator differences:
Linux and macOS use : to separate PATH entries:
export PATH="/usr/local/bin:/usr/bin:/bin"
Windows uses ;:
set PATH=C:\tools;C:\Python311;%PATH%
If you're writing cross-platform scripts, handle this explicitly or use a tool that abstracts it.
Environment Variables in CI/CD
Continuous integration and deployment pipelines rely heavily on environment variables for secrets, configuration, and build parameters. Most CI systems provide UI-based secret management:
GitHub Actions:
jobs:
deploy:
runs-on: ubuntu-latest
env:
NODE_ENV: production
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
run: npm run deploy
Secrets are encrypted and never logged. Regular environment variables can be set at workflow, job, or step level.
GitLab CI:
deploy:
stage: deploy
variables:
NODE_ENV: production
script:
- npm run deploy
only:
- main
GitLab supports protected and masked variables, plus file-type variables for certificates. For Git-specific environment variables like GIT_COMMITTER_NAME, GIT_SSH_COMMAND, and debugging with GIT_TRACE, see the Git environment variables guide.
Docker builds:
ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV
ARG is build-time only, ENV persists in the final image. For secrets during build, use BuildKit secret mounts instead of ARG:
docker build --secret id=npmtoken,src=.npmrc .
Most CI systems integrate with secret managers (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) for production credentials. Hard-coding secrets in CI configuration is as bad as committing them to Git.
Managing Environment Variables Across Teams
As projects grow, keeping environment variables synchronized across developers and environments becomes painful. Common issues:
- Developers missing required variables and getting cryptic errors
- Different local configurations causing "works on my machine" bugs
- Onboarding new team members requires hunting down .env files in Slack or email
- No visibility into which variables exist across dev/staging/prod
- Secrets shared through insecure channels
The traditional solution is maintaining a .env.example file in the repository, but this becomes stale quickly and doesn't help with actual values. Teams often end up with .env files passed through Slack, Google Docs, or 1Password shared vaults.
For teams juggling environment variables across multiple projects and environments, EnvManager (free tier available) centralizes this workflow. Define your variables once, organize them by environment, and everyone pulls from a single source of truth instead of passing .env files through Slack. Changes propagate immediately, new developers can onboard with a single command, and you get audit logs showing who changed what and when.
The key is treating environment configuration as a first-class part of your infrastructure, with versioning, access controls, and proper secret management. Whether you use a dedicated tool, a secret manager, or a well-structured documentation system, the goal is the same: eliminate the "where do I get the .env file?" question.
Further Reading
This guide covered the fundamentals of setting environment variables across operating systems and use cases. For deeper platform-specific knowledge and language-specific patterns, check out these detailed guides:
- How to Set Environment Variables in Linux — Shell config files, systemd service configuration, and system-wide settings
- How to Set Environment Variables in Windows — GUI configuration, CMD, setx command, and registry editing
- Environment Variables in PowerShell — $env: syntax, persistent variables, and profile management
- Environment Variables in Python — os.environ, python-dotenv, structured config with Pydantic
- Environment Variables in Java — System.getenv(), Spring Boot externalized configuration, and .env loading
- Git Environment Variables — Identity configuration, SSH settings, debugging, and CI/CD usage
- Why .env Files Are a Security Nightmare — Security risks of scattered .env files and how to fix them