DevDocs v1.0

Environment Variables

Learn how to manage application configuration using environment variables in Docker and Docker Compose.

Environment Variables

Environment variables provide a flexible way to configure applications without modifying source code.

Instead of hardcoding values directly into your application, you can store configuration separately and inject it at runtime.

Common examples include:

  • Database connection strings
  • API keys
  • Application ports
  • Environment modes
  • Feature flags

Why Use Environment Variables?

Consider this example:

const databaseUrl =
  "postgres://admin:password123@localhost:5432/app";

This approach has several problems:

  • Credentials are exposed in source code
  • Configuration changes require code changes
  • Different environments need different values

A better approach:

const databaseUrl = process.env.DATABASE_URL;

Now the application can run in different environments without modifying code.

Setting Environment Variables with Docker

You can pass variables directly when starting a container.

Example:

docker run \
  -e NODE_ENV=production \
  -e PORT=3000 \
  my-app

Inside the container:

NODE_ENV=production
PORT=3000

The application can access these values at runtime.

Viewing Environment Variables

To inspect a container's environment variables:

docker exec -it my-container env

This displays all available environment variables.

Using Environment Variables in Docker Compose

Docker Compose makes environment variable management easier.

Example:

services:
  app:
    image: my-app
    environment:
      NODE_ENV: production
      PORT: 3000

Start the application:

docker compose up

The variables become available inside the container.

Using a .env File

For larger projects, storing variables in a separate file is preferred.

Create a file named:

.env

Example:

NODE_ENV=development
PORT=3000
DATABASE_URL=postgres://admin:password@database:5432/app

Reference it from Compose:

services:
  app:
    env_file:
      - .env

Docker Compose automatically loads the values.

Example Project Structure

project/
├── compose.yaml
├── Dockerfile
├── .env
└── src/

This keeps configuration separate from application code.

Variable Substitution in Compose

Docker Compose can substitute values directly.

Example:

services:
  app:
    ports:
      - "${PORT}:3000"

Environment file:

PORT=8080

Docker Compose expands the value automatically.

Result:

8080:3000

This is useful when different developers require different settings.

Default Values

You can provide fallback values.

Example:

services:
  app:
    ports:
      - "${PORT:-3000}:3000"

If PORT is not defined:

3000:3000

is used automatically.

Environment Variables and Databases

A common pattern is database configuration.

Example:

POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret

Compose file:

services:
  database:
    image: postgres:16
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

This keeps credentials outside the Compose configuration.

Application Environments

Many applications support environment-specific configurations.

Typical values include:

Development

NODE_ENV=development

Testing

NODE_ENV=test

Production

NODE_ENV=production

Applications can adjust behavior based on these values.

Security Considerations

Environment variables are convenient but should not be treated as highly secure storage.

Avoid committing sensitive files:

.env
.env.local
.env.production

Add them to .gitignore:

.env
.env.local
.env.production

This prevents accidental exposure in version control.

Example: Node.js

Reading an environment variable:

const port = process.env.PORT || 3000;

app.listen(port);

Running the container:

docker run -e PORT=8080 my-app

The application now listens on port 8080.

Example: Python

Reading an environment variable:

import os

port = os.getenv("PORT", 3000)

This pattern works well across Dockerized applications.

Best Practices

Never Hardcode Secrets

Avoid:

API_KEY=123456789

inside source code.

Use environment variables instead.

Use .env Files for Development

This simplifies local configuration.

Keep Environment-Specific Values Separate

Different environments often require different configurations.

Examples:

.env.development
.env.staging
.env.production

Validate Required Variables

Applications should fail fast if critical configuration is missing.

Examples:

  • Database URLs
  • Authentication secrets
  • API credentials

Avoid Committing Sensitive Files

Always review your .gitignore.

Common Commands

Run with environment variables:

docker run -e PORT=3000 my-app

View container variables:

docker exec -it my-container env

Start Compose services:

docker compose up

Recreate services after configuration changes:

docker compose up -d --force-recreate

Summary

Environment variables are the standard way to configure Dockerized applications.

Key benefits include:

  • Separation of configuration from code
  • Easier deployment across environments
  • Improved maintainability
  • Better security practices
  • Seamless integration with Docker Compose

Most production-ready Docker applications rely heavily on environment variables for configuration management.