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](https://www.deployhq.com/features).

## 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=value` is correct; `KEY = value` breaks 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 `export` keyword**. Unlike shell scripts, `.env` files omit `export`. 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](https://github.com/motdotla/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](https://django-environ.readthedocs.io/) 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](https://www.deployhq.com/blog/how-to-deploy-laravel-zero-downtime-build-pipelines-and-best-practices) 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](https://www.deployhq.com/blog/understanding-keep-and-gitkeep-files-a-guide) 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](https://rtyley.github.io/bfg-repo-cleaner/) to scrub the history. Our guide to [protecting your API keys](https://www.deployhq.com/blog/protecting-your-api-keys-a-quick-guide) 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):

1. `.env` — base defaults
2. `.env.local` — local overrides (skipped in test)
3. `.env.{environment}` — environment-specific values
4. `.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](https://www.deployhq.com/features/build-pipelines) 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`, a `credentials.json`, or a `settings.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](https://www.deployhq.com) web UI is the fastest path.

[**Config files**](https://www.deployhq.com/support/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**](https://www.deployhq.com/support/projects/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](https://www.deployhq.com) 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](https://www.deployhq.com/blog/the-deployhq-build-pipeline)[DeployHQ](https://www.deployhq.com) build pipeline explains where these variables and files are injected.

### The CLI Method

The [`dhq` command-line tool](https://www.deployhq.com/support/cli) is a single binary that brings the [DeployHQ](https://www.deployhq.com) 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 variables
- `dhq global-env-vars` — manage account-wide environment variables
- `dhq config-files` — manage configuration files
- `dhq 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](https://www.deployhq.com) 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:

1. **Development** : Use `.env` files locally
2. **CI/Build** : Environment variables and config files set in DeployHQ's project configuration
3. **Deployment** : Variables available during build commands and injected into config files
4. **Runtime** : Application reads from the server's environment

If you deploy from [GitHub](https://www.deployhq.com/deploy-from-github) or [GitLab](https://www.deployhq.com/deploy-from-gitlab), [DeployHQ](https://www.deployhq.com) connects to your repository and handles the rest. For [agencies](https://www.deployhq.com/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](https://www.deployhq.com/blog/implementing-12-factor-app-methodology-with-deployhq), and it applies just as much to a [static site on Cloudflare's edge](https://www.deployhq.com/blog/deployhq-static-hosting-vs-cloudflare-pages) as it does to a traditional server.

Ready to get your secrets out of Git for good? **[Start a](https://www.deployhq.com/signup)[DeployHQ](https://www.deployhq.com) 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](https://www.deployhq.com/features/automatic-deployments)** and manage your environment variables securely from day one. See [pricing](https://www.deployhq.com/pricing) for team plans.

* * *

Questions? Reach out at [support@deployhq.com](mailto:support@deployhq.com) or [@deployhq](https://x.com/deployhq).

