Environment Files

Environment Files #

Quad-Ops automatically discovers and processes environment files to provide configuration flexibility and secure handling of sensitive data.

Environment File Discovery #

Standard Environment File #

The .env file in the same directory as the Docker Compose file is automatically loaded:

project/
├── docker-compose.yml
└── .env                 # ✅ Automatically loaded

Service-Specific Environment Files #

For each service, Quad-Ops searches for service-specific environment files in this order:

  1. .env.<service-name> - Hidden file with service suffix
  2. <service-name>.env - Service name with .env extension
  3. env/<service-name>.env - In env subdirectory
  4. envs/<service-name>.env - In envs subdirectory

Example for service named webapp:

project/
├── docker-compose.yml
├── .env                 # Global environment
├── .env.webapp          # ✅ Service-specific (highest priority)
├── webapp.env           # ✅ Alternative naming
├── env/
│   └── webapp.env       # ✅ In env directory
└── envs/
    └── webapp.env       # ✅ In envs directory

Environment File Processing #

Variable Interpolation in Compose Files #

Environment variables are substituted in Docker Compose files during processing:

.env file:

APP_VERSION=1.0.0
DB_NAME=myapp
PORT=8080

docker-compose.yml:

version: '3.8'
services:
  web:
    image: myapp:${APP_VERSION}
    ports:
      - "${PORT}:80"
    environment:
      - DATABASE_NAME=${DB_NAME}

Processed result:

services:
  web:
    image: myapp:1.0.0
    ports:
      - "8080:80"
    environment:
      - DATABASE_NAME=myapp

Service Environment Configuration #

Environment files are added to Quadlet units using the EnvironmentFile directive:

Service-specific file: .env.webapp

NODE_ENV=production
API_URL=https://api.example.com
SESSION_SECRET=super-secret-key

Generated Quadlet unit:

[Container]
Image=myapp:latest
EnvironmentFile=.env.webapp
NetworkAlias=webapp

Environment File Examples #

Development Configuration #

.env:

# Global development settings
NODE_ENV=development
DEBUG=true
LOG_LEVEL=debug

.env.api:

# API service specific
API_PORT=3000
DATABASE_URL=postgresql://localhost:5432/dev_db
REDIS_URL=redis://localhost:6379

.env.frontend:

# Frontend service specific
REACT_APP_API_URL=http://localhost:3000
REACT_APP_DEBUG=true

Production Configuration #

.env:

# Global production settings
NODE_ENV=production
DEBUG=false
LOG_LEVEL=info

.env.api:

# API service specific
API_PORT=8080
DATABASE_URL=postgresql://db:5432/prod_db
REDIS_URL=redis://cache:6379
JWT_SECRET=prod-jwt-secret-key

.env.frontend:

# Frontend service specific
REACT_APP_API_URL=https://api.example.com
REACT_APP_DEBUG=false

Multi-Environment Structure #

project/
├── docker-compose.yml
├── environments/
│   ├── dev/
│   │   ├── .env
│   │   ├── .env.api
│   │   └── .env.frontend
│   ├── staging/
│   │   ├── .env
│   │   ├── .env.api
│   │   └── .env.frontend
│   └── prod/
│       ├── .env
│       ├── .env.api
│       └── .env.frontend

Environment Variable Syntax #

Basic Variables #

# Simple key-value pairs
DATABASE_HOST=localhost
DATABASE_PORT=5432
DEBUG=true

Quoted Values #

# Handle spaces and special characters
APP_NAME="My Application"
DATABASE_URL="postgresql://user:pass@host:5432/db"
SECRET_KEY='complex!@#$%^&*()_+secret'

Multi-line Values #

# Multi-line values (use quotes)
SSL_CERT="-----BEGIN CERTIFICATE-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END CERTIFICATE-----"

Comments #

# Database configuration
DATABASE_HOST=localhost  # Local development database
DATABASE_PORT=5432      # Default PostgreSQL port

# API configuration
API_VERSION=v1          # Current API version

Security Best Practices #

Sensitive Data Handling #

❌ Avoid committing secrets to Git:

# .env (DO NOT COMMIT)
DATABASE_PASSWORD=secret123
API_KEY=sk_live_abcdef123456
JWT_SECRET=super-secret-key

✅ Use Podman secrets (recommended):

For maximum security, use Quad-Ops’ x-podman-env-secrets extension to map Podman secrets to environment variables:

services:
  app:
    x-podman-env-secrets:
      DATABASE_PASSWORD: db_password_secret  # Maps secret to env var
      API_KEY: api_key_secret
      JWT_SECRET: jwt_secret

✅ Use environment-specific files:

# .gitignore
.env.local
.env.*.local
*.secret
secrets/

✅ Template files for documentation:

# .env.example (safe to commit)
DATABASE_PASSWORD=your_secure_password_here
API_KEY=your_api_key_here
JWT_SECRET=generate_a_secure_jwt_secret

File Permissions #

Protect sensitive environment files:

# Restrict access to environment files
chmod 600 .env*
chmod 600 env/*.env
chmod 600 envs/*.env

# Verify permissions
ls -la .env*

Service-Specific Isolation #

Use service-specific files to limit secret exposure:

project/
├── .env                 # Non-sensitive global vars
├── .env.api            # API secrets (DB, external APIs)
├── .env.frontend       # Frontend config (public API URLs)
└── .env.worker         # Worker-specific config

Environment Variable Precedence #

Variables are resolved in this order (highest to lowest priority):

  1. Container environment (from environment: in compose)
  2. Service-specific env files (.env.service)
  3. Global env file (.env)
  4. System environment (from shell)

Example Precedence #

.env:

APP_ENV=development
DATABASE_HOST=localhost
API_PORT=3000

.env.api:

APP_ENV=staging        # Overrides global setting
DATABASE_HOST=db       # Overrides global setting
# API_PORT inherited from .env (3000)

docker-compose.yml:

services:
  api:
    environment:
      - APP_ENV=production  # Highest priority - overrides both files
      # DATABASE_HOST=db from .env.api
      # API_PORT=3000 from .env

Final environment for api service:

APP_ENV=production      # From compose environment
DATABASE_HOST=db        # From .env.api
API_PORT=3000          # From .env

Special Characters and Escaping #

Handling Special Characters #

Environment files with special characters benefit from service-specific files:

Problematic in compose interpolation:

# .env - Can cause issues with compose parsing
PASSWORD=p@ssw*rd!2023
COMMAND=echo "hello world"

Safe in service-specific files:

# .env.api - Passed directly to container
PASSWORD=p@ssw*rd!2023
COMMAND=echo "hello world"
DATABASE_URL=postgresql://user:p@ss@host/db?param=value&other=123

Escaping Guidelines #

For compose file interpolation:

# Use quotes for complex values
DATABASE_URL="postgresql://user:p@ss@host/db"
SHELL_COMMAND="echo 'Hello World'"

# Escape special characters
REGEX_PATTERN="^[a-zA-Z0-9_]+\$$"

Debugging Environment Variables #

Check variable resolution:

# Test compose file processing
docker-compose -f docker-compose.yml config

# Check what variables are available
cat .env .env.* 2>/dev/null

Verify generated units:

# Check EnvironmentFile directives
grep -r "EnvironmentFile" /etc/containers/systemd/

# View specific unit
cat /etc/containers/systemd/myapp-service.container

Test container environment:

# Check running container environment
podman exec myapp-service env

# View specific variable
podman exec myapp-service echo "$DATABASE_URL"

Environment File Validation #

Check file syntax:

# Verify no syntax errors
source .env && echo "Syntax OK"

# Check for common issues
grep -n "=" .env | grep -v "^[A-Z_][A-Z0-9_]*="

Validate variable names:

# Check for invalid variable names
grep -E "^[^A-Z_]" .env
grep -E "^[0-9]" .env

Integration Examples #

GitOps Workflow #

Development repository:

my-app/
├── docker-compose.yml
├── .env.example        # Template (committed)
├── .env               # Local dev (ignored)
└── .env.api           # API config (ignored)

Production repository:

my-app-deploy/
├── docker-compose.yml  # Same as dev
├── environments/
│   ├── staging/
│   │   ├── .env
│   │   └── .env.api
│   └── production/
│       ├── .env
│       └── .env.api

CI/CD Integration #

#!/bin/bash
# deployment script

ENV_NAME=${1:-staging}

# Copy environment files
cp environments/$ENV_NAME/.env .
cp environments/$ENV_NAME/.env.* .

# Deploy with quad-ops
quad-ops sync --repository myapp

Next Steps #