---
title: "Environment Variables & Secrets"
description: "How to manage .env files, Docker secrets, and sensitive configuration for self-hosted tools. Stop hardcoding passwords."
---

# Environment Variables & Secrets

Every self-hosted tool needs configuration: database passwords, API keys, admin emails. The **wrong** way is hardcoding them in `docker-compose.yml`. The **right** way is environment variables.

## The Basics: `.env` Files

Docker Compose automatically reads a `.env` file in the same directory as your `docker-compose.yml`:

```bash
# .env
POSTGRES_PASSWORD=super_secret_password_123
ADMIN_EMAIL=you@yourdomain.com
SECRET_KEY=a1b2c3d4e5f6g7h8i9j0
```

```yaml
# docker-compose.yml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
```

Docker Compose substitutes `${POSTGRES_PASSWORD}` with the value from `.env`. Your secrets stay out of your Compose file.

> ⚠️ **Critical:** Add `.env` to your `.gitignore` immediately. Never commit secrets to Git.

```bash
echo ".env" >> .gitignore
```

## Generating Strong Passwords

Don't use `password123`. Generate proper secrets:

```bash
# Generate a 32-character random string
openssl rand -base64 32

# Generate a hex string (great for SECRET_KEY)
openssl rand -hex 32

# Generate a URL-safe string
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
```

### Template for Common Tools

Most self-hosted tools need similar variables. Here's a reusable `.env` template:

```bash
# .env template — generate all values before first run

# Database
POSTGRES_USER=app
POSTGRES_PASSWORD=          # openssl rand -base64 32
POSTGRES_DB=app_db

# App
SECRET_KEY=                 # openssl rand -hex 32
ADMIN_EMAIL=you@yourdomain.com
ADMIN_PASSWORD=             # openssl rand -base64 24
BASE_URL=https://app.yourdomain.com

# SMTP (for email notifications)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASSWORD=              # Use app-specific password
```

## Default Values (Fallbacks)

Use the `:-` syntax for non-sensitive defaults:

```yaml
environment:
  NODE_ENV: ${NODE_ENV:-production}        # Defaults to "production"
  LOG_LEVEL: ${LOG_LEVEL:-info}            # Defaults to "info"
  POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}  # No default — MUST be set
```

## Docker Secrets (Advanced)

For production setups, Docker Secrets are more secure than environment variables — they're stored encrypted and mounted as files:

```yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
```

```bash
# Create the secret file
mkdir -p secrets
openssl rand -base64 32 > secrets/db_password.txt
chmod 600 secrets/db_password.txt
```

> 💡 Not all images support `_FILE` suffix variables. Check the image's documentation on Docker Hub.

## Multiple Environments

Keep separate `.env` files for different environments:

```bash
.env              # Production (default)
.env.local        # Local development
.env.staging      # Staging server
```

Use them explicitly:

```bash
# Use a specific env file
docker compose --env-file .env.staging up -d
```

## Security Checklist

- [ ] `.env` is in `.gitignore`
- [ ] No secrets are hardcoded in `docker-compose.yml`
- [ ] All passwords are randomly generated (32+ characters)
- [ ] Database ports are NOT exposed to the internet
- [ ] Secret files have `chmod 600` permissions
- [ ] Default passwords from docs have been changed

## Common Mistakes

**"Variable is empty in the container"** → Check for typos. Variable names are case-sensitive. `POSTGRES_password` ≠ `POSTGRES_PASSWORD`.

**"Changes to .env aren't applying"** → You need to recreate the container: `docker compose up -d --force-recreate`.

**"I committed my .env to Git"** → Even after removing it, it's in Git history. Rotate ALL secrets immediately and use `git filter-branch` or BFG Repo Cleaner.

## Next Steps

→ [Monitoring & Observability](/concepts/monitoring) — Know when things break
→ [Docker in 10 Minutes](/concepts/docker-basics) — Review the fundamentals
