Environment Variables and DATABASE_URL in Production: The Complete Guide
What environment variables are, how your app reads them, the anatomy of DATABASE_URL piece by piece, and where to store your secrets without leaking them. Practical guide with examples in Node.js and Python.

Environment variables are configuration values that liveoutside your codeand that the operating system passes to your app when it starts. They allow the same codebase to work locally and in production without changing a single line: only the values change.
DATABASE_URLis the standard convention for storing the connection string to your database in a single variable. The golden rule:neverwrite credentials in your code or upload your file.envto the repository; declare the variables in each platform's environment and read them withprocess.env(Node) oros.environ(Python).
The underlying principle: configuration doesn't live in the code
Before touchingDATABASE_URL, it's a good idea to understandwhyenvironment variables exist. The idea is to separate two things that are often mixed:
- The code:what your app does. It's identical on your laptop, in staging, and in production.
- The configuration:the values that change between environments —the database URL, API keys, the execution mode.
If you put configuration inside the code, you have to edit and redeploy every time a value changes, and you end up with production credentials written in files that anyone with access to the repo can read. Environment variables solve both problems: the code asks "What is the database URL?" and the environment responds with the correct value depending on where it's running.
How your app reads an environment variable
The operating system exposes the variables to your app's process. Each language has its own way of reading them.
InNode.js, throughprocess.env:
javascript
const dbUrl = process.env.DATABASE_URL;const port = process.env.PORT || 3000;
InPython, viaos.environ:
python
importosdb_url = os.environ.get("DATABASE_URL")port = int(os.environ.get("PORT", 3000))
Notice the patternprocess.env.PORT || 3000: it reads the environment value and, if it doesn't exist, uses a default one. This matters because many platforms assign the port dynamically via the variablePORT, and your app must listen on that port, not a fixed one.
Anatomy ofDATABASE_URL
DATABASE_URLcondenses all connection data into a single string with a standard format. Breaking it down is the best way to understand it:

Piece by piece:
- Schema:the database engine (
postgresql://,mysql://,mongodb://). - Username and password:the credentials, separated by
:and ending in@. - Host:the domain or IP of the database server.
- Port:where the engine listens (
5432for PostgreSQL,3306for MySQL). - Database name:after the
/. - Parameters:extra options after
?, likesslmode=requireto force an encrypted connection.
The advantage of having everything in a variable is that moving the app between environments or providers only requires changingonevalue.
The same code locally and in production
The practical challenge is that conditions change locally and in production —especially SSL. In development, you usually connect to a local database without encryption; in production, the provider almost always requires SSL. The clean way to handle this is to branch based on the environment:
const { Pool } = require("pg");const isProduction = process.env.NODE_ENV === "production";const pool = new Pool({connectionString: process.env.DATABASE_URL,ssl: isProduction ? { rejectUnauthorized: false } : false,});
Here, SSL is enabled in production and disabled locally, using the same variableDATABASE_URLin both cases. The value of that variable is the only thing that differs between environments; the code doesn't change.
A common mistake is leaving the SSL block active locally when your development database doesn't support it: the connection fails with an SSL error. That's why it's conditioned with
isProduction.
Where to store secrets (and where never to)
Not all places to store a variable are the same. Here is the security hierarchy:
Location | Use? | Purpose |
|---|---|---|
Variables panel of your deployment platform | ✅ Yes | Production secrets (encrypted, outside the repo) |
Dedicated secrets manager (vault) | ✅ Yes | Large teams, rotation, auditing |
File | ✅ Yes | Local development only |
File | ✅ Yes | Document which variables need to be defined |
Values hardcoded in the code | ❌ Never | — |
| ❌ Never | — |
Variables printed in the logs ( | ❌ Never | — |
Three rules that prevent most leaks:
- Add
.envto your.gitignorefrom the first commit. A credential leaked into Git history is hard to permanently delete. - Do not print sensitive variables in logs.Logs are stored, shared, and indexed; a secret there is an exposed secret.
- Version a
.env.examplewith the keys but without the values, so your team knows what to define without exposing anything.
How to verify that your app reads the correct variables
When something doesn't connect, before touching the code, confirm what your app is actually receiving:
- Temporarily print theexistenceof the variable, not its value:
console.log("DATABASE_URL defined:", Boolean(process.env.DATABASE_URL)). - Verify that the name matchesexactly —
DATABASE_URLis not the same asDATABASE_Url. - Check that the variable is declared in the correct environment (build-time vs runtime, and in the correct service if you have multiple).
- Confirm that there are no accidental spaces or quotes around the value when pasting it into the panel.
Frequently asked questions
Why shouldn't I write the database password in the code?Because it's exposed to anyone with access to the repository and Git history, and it forces you to redeploy to change it. Environment variables keep credentials out of the code and allow you to rotate them without touching the codebase.
What does?sslmode=requireat the end ofDATABASE_URL?It's a parameter that forces the connection to use SSL encryption. Most database providers require it in production to protect data in transit.
Why does my database connection fail in production but work locally?The most common cause is SSL: your local database doesn't require it, but the production provider does. Make the optionssldepending on the environment so it is only active in production.
Should I upload my file.envto the repository?No. Add it to.gitignoreand instead version a.env.examplewith the variable names but without values.
What is the difference between build-time and runtime variables?Build-time ones are available while the app is being compiled; runtime ones, while the app is running. Some platforms only inject one or the other, so it's a good idea to confirm which phase your app needs each variable in.