Codifly
Back to blog
DeploymentJun 2, 202623 min read

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.

magnific minimal premium scene primary application module c 55246

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

importos
db_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:

ChatGPT Image 2 jun 2026, 15 08 40

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 withisProduction.

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.envlocalunversioned

✅ Yes

Local development only

File.env.exampleversionedno values

✅ Yes

Document which variables need to be defined

Values hardcoded in the code

❌ Never

.envreal uploaded to the repository

❌ Never

Variables printed in the logs (console.log)

❌ Never

Three rules that prevent most leaks:

  1. Add.envto your.gitignorefrom the first commit. A credential leaked into Git history is hard to permanently delete.
  2. Do not print sensitive variables in logs.Logs are stored, shared, and indexed; a secret there is an exposed secret.
  3. 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 matchesexactlyDATABASE_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.

Related articles