Skip to content

Fix: .env File Not Loading – dotenv Variables Are Undefined in Node.js, Python, or Docker

FixDevs ·

Quick Answer

How to fix .env files not being loaded by dotenv, Next.js, Vite, Django, or Docker Compose, including wrong file path, missing dotenv.config(), and variable naming issues.

The Error

You have a .env file with your variables defined, but your application acts like it does not exist. The variables come back as undefined (Node.js), None (Python), or empty strings (Docker):

// Node.js
console.log(process.env.DATABASE_URL);
// undefined
# Python
import os
print(os.getenv("DATABASE_URL"))
# None
# docker-compose.yml -- variables from .env are empty
services:
  app:
    environment:
      - DATABASE_URL=${DATABASE_URL}

The .env file is right there in your project. You can see it. But something between the file and your runtime is broken, and the variables never get loaded.

This is different from a variable being undefined because of a missing prefix or typo. Here, the entire .env file is being ignored. None of the variables load, or the file itself is not found by the loader.

Why This Happens

A .env file is just a text file. It does nothing on its own. Something in your toolchain must explicitly read it, parse each line, and inject the key-value pairs into the process environment. When that step fails or never runs, every variable from the file is missing.

The most common reasons the file is not loaded:

  • dotenv.config() or its equivalent was never called. Node.js, Python, and most languages do not read .env files by default. You need a library or a framework feature to do it.
  • The file is named wrong. The file must be called exactly .env (or a recognized variant like .env.local). A file named env, .env.txt, or .ENV will not be picked up.
  • The file is in the wrong directory. Most tools look for .env relative to the working directory or the project root. If your script runs from a subdirectory, the file will not be found.
  • The framework requires a specific prefix on variable names, and without it, variables are silently excluded from the client-side bundle.
  • Docker Compose is not configured to use the file. The env_file directive is missing, or the file path is wrong.
  • Variable expansion is breaking values. Characters like $ in values trigger variable interpolation, corrupting passwords and connection strings.
  • The file exists locally but is missing from the deployed environment. Because .env is in .gitignore, it never reaches the server, container, or CI pipeline.

Fix 1: Call dotenv.config() in Node.js

Plain Node.js and Express do not read .env files. You need the dotenv package, and you need to call it before anything else tries to read process.env.

Install it:

npm install dotenv

Then load it at the very top of your entry file:

// CommonJS
require('dotenv').config();

const express = require('express');
const app = express();

console.log(process.env.DATABASE_URL); // now loaded

If you use ES modules:

import 'dotenv/config';

import express from 'express';

The critical detail is import order. If another module reads process.env at import time and that import comes before dotenv/config, the variable will be undefined in that module. Always make dotenv the very first import.

A common pattern that fails:

// ❌ Wrong -- db.js reads process.env.DATABASE_URL at import time
import { connectDB } from './db.js';
import 'dotenv/config';

connectDB(); // DATABASE_URL is undefined inside db.js
// ✅ Correct -- dotenv loads first
import 'dotenv/config';
import { connectDB } from './db.js';

connectDB(); // DATABASE_URL is available

Node.js 20.6+ built-in .env support

If you are on Node.js 20.6 or later, you can skip the dotenv package entirely:

node --env-file=.env app.js

This loads the .env file before any of your code runs. You can also chain multiple files:

node --env-file=.env --env-file=.env.local app.js

Fix 2: Use the Correct File Name and Path

The file must be named exactly .env (lowercase, starting with a dot, no extension). These common mistakes will cause loading to fail silently:

Wrong nameWhy it fails
envMissing the leading dot
.env.txtExtra extension — your OS may have added this
.ENVCase matters on Linux/macOS (works on Windows by accident)
.envTrailing space in the filename

Check what file actually exists:

# Linux/macOS
ls -la .env*

# Windows (PowerShell)
Get-ChildItem -Force .env*

The file also must be in the right directory. By default, dotenv in Node.js looks for .env relative to process.cwd() — the directory from which you ran the node command, not the directory where your script file lives.

If your project structure looks like this:

project/
├── .env
├── package.json
└── src/
    └── server.js

And you run node src/server.js from project/, then dotenv.config() will find .env because process.cwd() is project/. But if you cd src && node server.js, it will look for src/.env, which does not exist.

To be safe, specify the path explicitly:

require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });

Pro Tip: If you are running your app through a process manager like PM2, a systemd unit, or a Docker entrypoint script, the working directory might not be what you expect. Always use an absolute or explicitly resolved path for the .env file in production-like environments to avoid silent loading failures.

Fix 3: Add the Required Prefix for Next.js or Vite

Both Next.js and Vite automatically load .env files — you do not need to install dotenv. But they deliberately filter which variables are exposed to browser-side code.

Next.js: NEXT_PUBLIC_ prefix

Only variables starting with NEXT_PUBLIC_ are available in client components. Without the prefix, the variable is available in server-side code (API routes, Server Components) but will be undefined on the client:

# .env
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_KEY=super-secret
// Client component
console.log(process.env.NEXT_PUBLIC_API_URL); // "https://api.example.com"
console.log(process.env.SECRET_KEY);          // undefined (by design)

If all your variables appear undefined even in server-side code, the .env file itself is not being found. Make sure it is in the project root (next to package.json), not inside src/ or app/. Also, check for hydration mismatches if values differ between server and client rendering.

Vite: VITE_ prefix

Vite uses import.meta.env instead of process.env. Only variables prefixed with VITE_ are included in the client bundle:

# .env
VITE_API_URL=https://api.example.com
DB_PASSWORD=secret
console.log(import.meta.env.VITE_API_URL); // "https://api.example.com"
console.log(import.meta.env.DB_PASSWORD);  // undefined

If import.meta.env.VITE_API_URL is still undefined, check the file location. Vite looks for .env in the project root (where vite.config.js lives). You can override this with the envDir option in your Vite config:

// vite.config.js
export default defineConfig({
  envDir: './config',
});

If you are getting import resolution errors in Vite, make sure your config file itself is not broken, as that can prevent .env loading too.

After changing .env in either framework, restart the dev server. Environment variables are read at startup.

Fix 4: Load .env in Python with python-dotenv

Python does not read .env files natively. The python-dotenv package handles this:

pip install python-dotenv

Then load it in your code:

from dotenv import load_dotenv
import os

load_dotenv()  # loads .env from the current working directory

database_url = os.getenv("DATABASE_URL")
print(database_url)  # now has the value from .env

If the file is in a different location, pass the path:

from pathlib import Path
from dotenv import load_dotenv

env_path = Path(__file__).resolve().parent.parent / '.env'
load_dotenv(dotenv_path=env_path)

Django

Django does not load .env files by default. You have two main approaches:

Option 1: python-dotenv in manage.py and wsgi.py:

# manage.py
import dotenv

def main():
    dotenv.load_dotenv()
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
    # ...

Option 2: django-environ package:

pip install django-environ
# settings.py
import environ

env = environ.Env()
environ.Env.read_env()  # reads .env from BASE_DIR by default

SECRET_KEY = env('SECRET_KEY')
DEBUG = env.bool('DEBUG', default=False)

If your Python variables are still None, make sure the .env file is where the loader expects it. Django projects commonly have this structure:

myproject/
├── .env
├── manage.py
└── myproject/
    ├── settings.py
    └── wsgi.py

The .env file goes next to manage.py, not inside the inner myproject/ directory. If you are dealing with missing Python modules, resolve that first, because a broken import of dotenv itself will prevent loading.

Common Mistake: Calling load_dotenv() inside a function that runs late in the application lifecycle. If settings.py or another module reads os.getenv() at import time (module-level code), the variables need to be loaded before that module is imported. In Django, this means loading .env in manage.py or at the very top of settings.py, not in a view or a signal handler.

Fix 5: Configure env_file in Docker Compose

Docker Compose does not automatically use your .env file for container environment variables. There are two separate mechanisms, and confusing them is a frequent source of errors.

Mechanism 1: Variable substitution in docker-compose.yml

Docker Compose reads a .env file in the same directory as docker-compose.yml to substitute variables inside the compose file itself:

# docker-compose.yml
services:
  app:
    image: myapp:${APP_VERSION}
# .env (next to docker-compose.yml)
APP_VERSION=1.2.3

This substitutes ${APP_VERSION} so the image becomes myapp:1.2.3. But the variable APP_VERSION is not passed into the container’s environment. It only affects the compose file parsing.

Mechanism 2: env_file for container environment

To inject variables from a file into the container, use env_file:

# docker-compose.yml
services:
  app:
    build: .
    env_file:
      - .env

Now every variable in .env is available inside the container as an environment variable.

You can also combine multiple files:

env_file:
  - .env
  - .env.production

If your compose setup is producing other errors, check the Docker Compose troubleshooting guide to rule out syntax or configuration issues.

Common Docker .env problems

The .env file is not copied into the image. If your Dockerfile has COPY . . but .dockerignore excludes .env, the file will not be in the image. For most setups, you should not copy .env into the image at all. Instead, pass variables at runtime with env_file or docker run --env-file .env.

Path is wrong in env_file. The path in env_file is relative to the compose file’s directory, not the project root or the build context:

# If docker-compose.yml is in deploy/ and .env is in the project root:
env_file:
  - ../.env

Fix 6: Handle Variable Expansion and Special Characters

Most .env parsers support variable expansion using $ syntax. This can break values that contain $ literally, such as passwords:

# .env
PASSWORD=p@$$w0rd

The parser may try to expand $$w0rd as a reference to a variable named $w0rd, producing a corrupted value.

Fix: Wrap values containing $ in single quotes (if the parser supports it) or escape the dollar sign:

# Option 1: Single quotes (supported by most parsers)
PASSWORD='p@$$w0rd'

# Option 2: Escape the dollar sign
PASSWORD=p@\$\$w0rd

In dotenv for Node.js, variable expansion is off by default in the main dotenv package but enabled in dotenv-expand. If you are using dotenv-expand, be aware of this:

# .env
BASE_URL=https://api.example.com
API_URL=${BASE_URL}/v2
require('dotenv').config();
require('dotenv-expand').expand(/* ... */);

console.log(process.env.API_URL); // "https://api.example.com/v2"

If you do not want expansion, do not use dotenv-expand, or quote the value with single quotes.

Docker Compose also expands $ in .env files. To use a literal $, double it:

# .env for Docker Compose
PASSWORD=p@$$$$w0rd
# Results in: p@$$w0rd

Yes, this is confusing. The safest approach is to avoid $ in values entirely when possible, or use a secrets manager instead.

Fix 7: Ensure .env Exists in Deployed Environments

The .env file should be in .gitignore to avoid committing secrets. But this means it will not exist when you clone the repo on a server, in a CI pipeline, or inside a Docker build:

# .gitignore
.env
.env.local
.env.*.local

This is correct for security. But you need another mechanism to provide the variables in each environment:

  • CI/CD pipelines (GitHub Actions, GitLab CI, etc.): Set variables in the pipeline’s secret management. In GitHub Actions, use repository or environment secrets and reference them with ${{ secrets.DATABASE_URL }}.
  • Docker/Kubernetes: Use env_file, Kubernetes Secrets, or a secrets manager.
  • Hosting platforms (Vercel, Railway, Render, Fly.io): Set variables through the platform’s dashboard or CLI.

Create a .env.example file listing every required variable without values, and commit it:

# .env.example
DATABASE_URL=
API_KEY=
REDIS_URL=
NEXT_PUBLIC_API_URL=

This serves as documentation for anyone deploying the project.

Still Not Working?

If you have tried everything above and your .env file still is not loading, work through this checklist:

Print the resolved path. Confirm dotenv is looking where you think:

// Node.js
const path = require('path');
const result = require('dotenv').config({
  path: path.resolve(process.cwd(), '.env'),
  debug: true,
});
console.log(result);
// If .error exists, it will tell you what went wrong
# Python
from dotenv import load_dotenv, find_dotenv
print(find_dotenv())  # prints the path it found, or empty string if not found

Check for BOM (byte order mark). Some Windows editors save files with a UTF-8 BOM. This invisible character at the start of the file can make the first variable name unreadable. Re-save the file as UTF-8 without BOM.

Check file permissions. On Linux, make sure the application user can read the file:

ls -la .env
# Should show at least -rw-r--r-- or -rw-------

Check for override conflicts. By default, dotenv in Node.js does not overwrite variables that already exist in the shell environment. If DATABASE_URL is already set in your shell (perhaps from a profile script), the .env value is silently ignored:

require('dotenv').config({ override: true }); // forces .env to win

In Python:

load_dotenv(override=True)

Check for multiline values. If a value spans multiple lines (like an RSA key), the syntax varies by parser. In dotenv for Node.js, wrap it in double quotes with literal newlines:

PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----"

Verify the process is actually using your code. If you are running behind a process manager (PM2, supervisor) or in a container, make sure the running process is the version of code that includes the dotenv.config() call. An old cached build or a stale container image is a surprisingly common cause of “I added dotenv but nothing changed.”


Related: For broader environment variable issues including type coercion, prefix requirements, and framework-specific behavior, see Fix: process.env.VARIABLE_NAME Is Undefined.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles