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:
.env.<service-name>
- Hidden file with service suffix<service-name>.env
- Service name with .env extensionenv/<service-name>.env
- In env subdirectoryenvs/<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):
- Container environment (from
environment:
in compose) - Service-specific env files (
.env.service
) - Global env file (
.env
) - 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 #
- Build Support - Docker build configurations
- Repository Structure - Understanding file discovery
- Docker Compose Support - Complete compose reference