Every application needs configuration that changes between environments — database URLs, API keys, feature flags. The .env file is the standard way to manage these values locally without hardcoding them into your source code. This guide explains what .env files are, how they work across languages, and how to handle them safely from development through deployment.
What Is a .env File?
A .env file is a plain text file that sits in the root of your project and stores configuration as key-value pairs:
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=sk-abc123def456
DEBUG=true
PORT=3000
Your application reads this file at startup and loads the values into the process environment. This keeps sensitive credentials and environment-specific settings out of your codebase.
The name .env follows the Unix convention where dot-prefixed files are hidden by default — present but unobtrusive.
The KEY=VALUE Format
The syntax is simple, but there are rules worth knowing:
# This is a comment
DATABASE_URL=postgres://localhost:5432/myapp
# Quoted values preserve whitespace
APP_NAME="My Application"
# Single quotes prevent variable expansion
GREETING='Hello $USER'
# No spaces around the equals sign
CORRECT=value
# WRONG = value <-- this breaks most parsers
Key rules:
- No spaces around
=.KEY=valueis correct;KEY = valuebreaks most parsers - Comments start with
#at the beginning of a line - Empty lines are ignored
- Quotes are optional for simple values. Use double quotes for values with spaces, single quotes to prevent variable interpolation
- No
exportkeyword. Unlike shell scripts,.envfiles omitexport. Some parsers accept it, but it's not standard
How Different Frameworks Load .env Files
The .env file doesn't load itself. Your language or framework needs a library to read it and inject values into the process environment.
Node.js
The dotenv package is the standard:
npm install dotenv
require('dotenv').config();
const dbUrl = process.env.DATABASE_URL;
const port = process.env.PORT || 3000;
Node.js 20.6+ has a built-in --env-file flag that eliminates the package entirely:
node --env-file=.env app.js
Python
pip install python-dotenv
from dotenv import load_dotenv
import os
load_dotenv()
db_url = os.getenv("DATABASE_URL")
debug = os.getenv("DEBUG", "false").lower() == "true"
Django projects often use django-environ for typed access:
import environ
env = environ.Env()
environ.Env.read_env()
DEBUG = env.bool("DEBUG", default=False)
DATABASE_URL = env.db()
Ruby
# Gemfile
gem 'dotenv', groups: [:development, :test]
# Load early in boot
require 'dotenv/load'
db_url = ENV['DATABASE_URL']
Rails projects use dotenv-rails, which loads automatically before the app initializes.
PHP
composer require vlucas/phpdotenv
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$dbUrl = $_ENV['DATABASE_URL'];
// Require specific variables (throws exception if missing)
$dotenv->required(['DATABASE_URL', 'API_KEY']);
Laravel loads .env automatically. Access values with the env() helper:
$debug = env('APP_DEBUG', false);
If you're shipping a Laravel app to production, our zero-downtime Laravel deployment guide walks through build pipelines and environment configuration end to end.
Security Best Practices
The .env file often contains the most sensitive data in your project. Mishandling it is one of the most common security mistakes in web development.
Never Commit .env to Version Control
Add .env to your .gitignore immediately:
.env
.env.local
.env.*.local
If you've already committed a .env file, removing it from .gitignore isn't enough — the values are still in your Git history. Rotate every exposed secret, then use BFG Repo-Cleaner to scrub the history. Our guide to protecting your API keys covers rotation and leak response in more depth.
Use .env.example as a Template
Create a .env.example that documents every required variable with placeholder values:
# .env.example - Copy to .env and fill in real values
DATABASE_URL=postgres://user:password@localhost:5432/dbname
API_KEY=your-api-key-here
DEBUG=false
PORT=3000
Commit this file. New developers copy it, fill in their values, and they're ready to go.
Restrict File Permissions
On Unix systems:
chmod 600 .env
This ensures only the file owner can read or write it.
Environment-Specific Files
Most frameworks support a hierarchy of .env files that override each other:
.env # Shared defaults (committed)
.env.local # Local overrides (not committed)
.env.development # Development-specific
.env.test # Test-specific
.env.production # Production-specific
The typical loading order (varies by framework):
.env— base defaults.env.local— local overrides (skipped in test).env.{environment}— environment-specific values.env.{environment}.local— local environment overrides
Vite only exposes variables prefixed with VITE_ to client-side code. Next.js uses NEXT_PUBLIC_. Create React App uses REACT_APP_. These prefixes prevent accidental exposure of server-side secrets to the browser.
Common Patterns and Gotchas
Variable Expansion
Some parsers support referencing other variables:
BASE_URL=https://api.example.com
USERS_API=${BASE_URL}/users
POSTS_API=${BASE_URL}/posts
The dotenv-expand package enables this in Node.js. Python's python-dotenv supports it natively. Not all parsers handle expansion, so test before relying on it.
Multiline Values
Multiline values require double quotes:
PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF...
-----END RSA PRIVATE KEY-----"
If your parser struggles with this, base64-encode the value instead:
PRIVATE_KEY_BASE64=TUlJRXBBSUJBQUtDQVFF...
const key = Buffer.from(process.env.PRIVATE_KEY_BASE64, 'base64').toString();
Boolean Values
There's no standard for booleans. These are all common:
DEBUG=true
DEBUG=1
DEBUG=yes
Pick one convention and be consistent. Always parse explicitly:
const debug = process.env.DEBUG === 'true';
Configuring Secrets in DeployHQ: UI, CLI, and API
During local development, .env files are convenient. In production, you should not deploy .env files to your servers. Once your secrets are out of your repository, you still need a way to store them and inject them at deploy time. DeployHQ gives you three ways to do that — the web UI, the dhq command-line tool, and the API — so you can pick whichever fits a one-off change or an automated workflow.
Config Files vs Environment Variables
Before configuring anything, it helps to know which of the two mechanisms you actually need:
- Config files are whole files uploaded at deploy time and not stored in your repository — think a production
.env, acredentials.json, or asettings.yml. - Environment variables are individual values injected into the build and referenced from config files or SSH commands.
The rule of thumb is simple: if you're managing a single value, use an environment variable; if you're managing an entire file, use a config file.
The UI Method
For a one-off change, the DeployHQ web UI is the fastest path.
Config files live under Settings → Config Files. They're uploaded during deployment rather than stored in your repository, which makes them ideal for sensitive data like database configuration. When you create one, you specify:
- The full path as the filename (this is where the file lands on the server)
- The file content
- An optional description
- A language for syntax highlighting
Two toggles shape how the file is used. Build Pipeline Integration makes the config file available during your build commands. Server Distribution lets you upload it to all current and future servers and server groups, or target specific ones. You can also set up multiple config files per environment type — development, staging, and production — and only deploy them where they belong. Config files can go out with a standard deployment, or via a dedicated Config file deployment that ships only config files with no other changes.
Environment variables live in the project sidebar under Environment Variables. These are dynamic values usable in config files, SSH commands, and notifications. To create one:
- The name must start with a letter and contain only uppercase letters, numbers, and underscores
- Enter a value
- Toggle Use this variable with the Build Pipeline if the build needs it
- Choose which server or server group can access it
In the build pipeline, these are exported as real environment variables, so tools like Vite, Webpack, Next.js, and Create React App read them via process.env.VARIABLE_NAME with no .env file setup at all. In deployments, config files, and SSH commands, you reference them with the %VARIABLE_NAME% syntax.
On security: all variable values are encrypted at rest and never visible in logs or the UI. Values can also be locked, which prevents viewing or changing them while they keep working in deployments. When the same variable is defined at multiple levels, targeting precedence runs highest to lowest: server-specific overrides a server group, which overrides all-servers. DeployHQ also ships roughly 20 built-in variables — such as %environment%, %branch%, %deployer%, and %status% — that you can reference without defining anything.
If you're new to how the build stage fits into a deployment, the DeployHQ build pipeline explains where these variables and files are injected.
The CLI Method
The dhq command-line tool is a single binary that brings the DeployHQ workflow to your terminal — deploy, monitor, manage servers, and automate, all without leaving the shell.
Install it with Homebrew, the install script, or a prebuilt binary:
brew install deployhq/tap/dhq
# or
curl -fsSL https://raw.githubusercontent.com/deployhq/deployhq-cli/main/install.sh | sh
You can also grab a binary directly from GitHub Releases.
Authenticate once with dhq auth login, which stores your credentials in the OS keyring. dhq hello gives you a guided login/signup flow plus project selection, and for CI/CD you authenticate via environment variables instead of the interactive login.
The commands relevant to secrets are:
dhq env-vars— manage a project's environment variablesdhq global-env-vars— manage account-wide environment variablesdhq config-files— manage configuration filesdhq api— reach the full set of 144+ API endpoints
The CLI also offers JSON output for scripting and shell completions for faster typing.
The exact create, set, and update sub-command syntax isn't fixed here — run dhq <command> --help (for example, dhq env-vars --help) to see the current usage for your version rather than guessing at flags.
The API Method
For fully programmatic management, DeployHQ exposes 144+ API endpoints, reachable either through dhq api or directly. This is the right layer when you want to script secret provisioning — for instance, auto-creating a standard set of environment variables and config files every time you onboard a new project or client.
Which One Should You Use?
The CLI and API make your secrets reproducible and automatable, which is exactly what you want when they need to live in CI or in an onboarding script that spins up a new project the same way every time. The UI, by contrast, is the quickest route for a one-off change. Most teams end up using all three: the UI to eyeball and tweak, the CLI and API to codify.
flowchart LR A[Code in Git] --> B[DeployHQ] B --> C[Build with env vars] C --> D[Deploy to server] E[Secure variable store] --> B
The end-to-end workflow looks like this:
- Development: Use
.envfiles locally - CI/Build: Environment variables and config files set in DeployHQ's project configuration
- Deployment: Variables available during build commands and injected into config files
- Runtime: Application reads from the server's environment
If you deploy from GitHub or GitLab, DeployHQ connects to your repository and handles the rest. For agencies managing multiple client projects, each project gets an isolated variable store so credentials never leak between clients. This same config-in-the-environment discipline is the backbone of the 12-Factor App methodology, and it applies just as much to a static site on Cloudflare's edge as it does to a traditional server.
Ready to get your secrets out of Git for good? Start a DeployHQ project and add your first encrypted environment variable in a couple of minutes.
FAQ
Should I commit .env.example to Git? Yes. It documents which variables your app requires with placeholder values. Never put real credentials in it.
What happens if a variable is defined in both .env and the system environment? In most libraries, system environment variables take precedence. This lets your deployment platform override defaults without modifying files.
Can I use .env files in Docker containers?
Yes. Docker supports --env-file natively, and Docker Compose reads .env in the project root automatically for variable substitution.
How do I handle .env files across a team?
Use .env.example as the shared template. Each developer maintains their own .env with local values. For shared secrets, use a password vault rather than distributing files via chat.
Are .env files secure enough for production? For local development, yes. For production, use your platform's built-in secrets management — like DeployHQ's secure environment variables, which are encrypted at rest and injected at deploy time.
Ready to stop worrying about environment configuration? Automate deployments with DeployHQ's build pipeline and manage your environment variables securely from day one. See pricing for team plans.
Questions? Reach out at support@deployhq.com or @deployhq.