Monorepo Setup¶
This guide explains how to use envdrift in a monorepo with multiple services.
Directory Structure¶
A typical monorepo might look like:
my-monorepo/
├── envdrift.toml # Shared config
├── services/
│ ├── api/
│ │ ├── .env.production
│ │ ├── .env.keys
│ │ └── config.py # API-specific schema
│ ├── web/
│ │ ├── .env.production
│ │ ├── .env.keys
│ │ └── config.py # Web-specific schema
│ └── worker/
│ ├── .env.production
│ ├── .env.keys
│ └── config.py # Worker-specific schema
└── shared/
└── settings.py # Shared base schema
Shared Base Schema¶
Create a base schema that all services extend:
# shared/settings.py
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class BaseAppSettings(BaseSettings):
"""Base settings shared across all services."""
model_config = SettingsConfigDict(extra="forbid")
# Common required variables
ENVIRONMENT: str
LOG_LEVEL: str = "INFO"
# Common secrets
DATABASE_URL: str = Field(json_schema_extra={"sensitive": True})
Service-Specific Schemas¶
Each service extends the base with its own requirements:
# services/api/config.py
from shared.settings import BaseAppSettings
from pydantic import Field
class Settings(BaseAppSettings):
"""API service settings."""
API_KEY: str = Field(json_schema_extra={"sensitive": True})
API_RATE_LIMIT: int = 100
CORS_ORIGINS: str = "*"
# services/web/config.py
from shared.settings import BaseAppSettings
class Settings(BaseAppSettings):
"""Web frontend settings."""
NEXT_PUBLIC_API_URL: str
SESSION_SECRET: str
# services/worker/config.py
from shared.settings import BaseAppSettings
from pydantic import Field
class Settings(BaseAppSettings):
"""Background worker settings."""
REDIS_URL: str = Field(json_schema_extra={"sensitive": True})
WORKER_CONCURRENCY: int = 4
Validation¶
Use --service-dir to set the correct Python path:
# Validate each service
envdrift validate services/api/.env.production \
--schema config:Settings \
--service-dir services/api
envdrift validate services/web/.env.production \
--schema config:Settings \
--service-dir services/web
envdrift validate services/worker/.env.production \
--schema config:Settings \
--service-dir services/worker
Validation Script¶
Create a script to validate all services:
#!/bin/bash
# scripts/validate-all.sh
set -e
SERVICES=("api" "web" "worker")
for service in "${SERVICES[@]}"; do
echo "Validating $service..."
envdrift validate "services/$service/.env.production" \
--schema config:Settings \
--service-dir "services/$service" \
--ci
done
echo "All services validated!"
Configuration¶
Configure vault sync for all services:
# envdrift.toml
[vault]
provider = "azure"
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.sync]
default_vault_name = "my-keyvault"
# API service
[[vault.sync.mappings]]
secret_name = "api-dotenvx-key"
folder_path = "services/api"
environment = "production"
# Web service
[[vault.sync.mappings]]
secret_name = "web-dotenvx-key"
folder_path = "services/web"
environment = "production"
# Worker service
[[vault.sync.mappings]]
secret_name = "worker-dotenvx-key"
folder_path = "services/worker"
environment = "production"
Sync and Pull¶
Sync keys for all services at once:
Output:
Processing: services/api
+ services/api/.env.keys - created
Processing: services/web
+ services/web/.env.keys - created
Processing: services/worker
+ services/worker/.env.keys - created
╭──────────── Sync Summary ────────────╮
│ Services processed: 3 │
│ Created: 3 │
│ Updated: 0 │
│ Skipped: 0 │
│ Errors: 0 │
╰──────────────────────────────────────╯
CI/CD Integration¶
GitHub Actions¶
# .github/workflows/validate.yml
name: Validate Environment
on:
pull_request:
paths:
- 'services/**/.env.*'
- 'services/**/config.py'
jobs:
validate:
runs-on: ubuntu-latest
strategy:
matrix:
service: [api, web, worker]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install envdrift
pip install -e shared/ # If shared is a package
- name: Validate ${{ matrix.service }}
run: |
envdrift validate services/${{ matrix.service }}/.env.production \
--schema config:Settings \
--service-dir services/${{ matrix.service }} \
--ci
Pre-commit Hook¶
Validate changed services only:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: envdrift-validate-api
name: Validate API env
entry: envdrift validate services/api/.env.production --schema config:Settings --service-dir services/api --ci
language: system
files: ^services/api/\.env\.production$
pass_filenames: false
- id: envdrift-validate-web
name: Validate Web env
entry: envdrift validate services/web/.env.production --schema config:Settings --service-dir services/web --ci
language: system
files: ^services/web/\.env\.production$
pass_filenames: false
- id: envdrift-validate-worker
name: Validate Worker env
entry: envdrift validate services/worker/.env.production --schema config:Settings --service-dir services/worker --ci
language: system
files: ^services/worker/\.env\.production$
pass_filenames: false
Comparing Across Services¶
Compare the same environment across services:
# Compare API and Worker database configs
envdrift diff services/api/.env.production services/worker/.env.production
Compare a service across environments:
# Compare API staging vs production
envdrift diff services/api/.env.staging services/api/.env.production
Tips¶
Use Profiles for Local Development¶
# Local dev mappings
[[vault.sync.mappings]]
secret_name = "api-local-key"
folder_path = "services/api"
profile = "local"
activate_to = "services/api/.env"
[[vault.sync.mappings]]
secret_name = "web-local-key"
folder_path = "services/web"
profile = "local"
activate_to = "services/web/.env"
Shared .env.keys Location¶
If services share the same encryption key, use a single keys file:
Then reference it from each service:
Makefile for Convenience¶
# Makefile
.PHONY: validate-all sync-all pull-all lock-all
SERVICES := api web worker
validate-all:
@for service in $(SERVICES); do \
echo "Validating $$service..."; \
envdrift validate services/$$service/.env.production \
--schema config:Settings \
--service-dir services/$$service \
--ci; \
done
sync-all:
envdrift sync
pull-all:
envdrift pull
lock-all:
envdrift lock --verify-vault
See Also¶
- CI/CD Integration — Pipeline setup
- Schema Best Practices — Schema design patterns
- Vault Sync — Team key sharing