Env config
Security-audited skills for Claude, Codex & Claude Code. One-click install, quality verified.
npx -y skills add aiskillstore/marketplace --skill env-configAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
What its author says it does
Copied from the file, not written here
Use when setting up environment configuration, loading .env files, or managing application settings across environments. Triggers for: .env setup, loading environment variables, Pydantic BaseSettings, configuration validation, or secret management. NOT for: code-specific logic, feature flags, or runtime feature toggles.
SKILL.md
9.0 KB, as published. Nobody here has run it
Environment Configuration Skill
Expert environment configuration management for Python/FastAPI projects with secure secrets handling and multi-environment support.
Quick Reference
| Pattern | Usage |
|---|---|
| Load .env | load_dotenv() at application start |
| Access var | settings.DB_URL, settings.JWT_SECRET |
| Required var | Field(..., description="Database URL") |
| Optional var | DB_HOST: str = "localhost" |
| Secret type | SecretStr for sensitive values |
Project Structure
project/
├── .env # Local development (NOT committed)
├── .env.example # Template with all required vars (committed)
├── .env.staging # Staging environment
├── .env.production # Production environment (managed by infra)
└── config/
├── __init__.py
└── settings.py # Pydantic BaseSettings
settings.py - Base Configuration
# config/settings.py
from functools import lru_cache
from pydantic import Field, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
# Application
APP_NAME: str = "ERP System"
DEBUG: bool = False
API_V1_PREFIX: str = "/v1"
# Database
DB_URL: str = Field(
...,
description="PostgreSQL connection URL",
examples=["postgresql://user:pass@localhost:5432/dbname"],
)
DB_POOL_SIZE: int = Field(default=5, ge=1, le=100)
# JWT Authentication
JWT_SECRET_KEY: SecretStr = Field(
...,
description="Secret key for JWT signing",
)
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRATION_MINUTES: int = Field(default=15, ge=1)
# Redis (Optional)
REDIS_URL: str | None = None
# Logging
LOG_LEVEL: str = Field(default="INFO", pattern="^(DEBUG|INFO|WARNING|ERROR)$")
# CORS
CORS_ORIGINS: list[str] = ["http://localhost:3000"]
@property
def is_production(self) -> bool:
return not self.DEBUG
@lru_cache
def get_settings() -> Settings:
"""Cached settings instance for application lifecycle."""
return Settings()
.env.example - Template File
# .env.example - Copy to .env and fill in values
# DO NOT commit actual secrets!
# Application
APP_NAME="ERP System"
DEBUG=false
API_V1_PREFIX="/v1"
# Database (required)
DB_URL="postgresql://user:password@localhost:5432/erp_db"
# JWT Authentication (required - generate with: openssl rand -hex 32)
JWT_SECRET_KEY="your-secret-key-here-generate-with-openssl-rand-hex-32"
JWT_ALGORITHM="HS256"
JWT_EXPIRATION_MINUTES=15
# Redis (optional)
# REDIS_URL="redis://localhost:6379/0"
# Logging
LOG_LEVEL="INFO"
# CORS
CORS_ORIGINS="http://localhost:3000"
.env - Local Development
# .env - Local development only
# NEVER commit this file to version control
APP_NAME="ERP System"
DEBUG=true
API_V1_PREFIX="/v1"
# Local PostgreSQL
DB_URL="postgresql://postgres:postgres@localhost:5432/erp_dev"
# Generate with: openssl rand -hex 32
JWT_SECRET_KEY="local-dev-secret-key-change-in-production"
JWT_ALGORITHM="HS256"
JWT_EXPIRATION_MINUTES=15
# Local Redis (if using)
REDIS_URL="redis://localhost:6379/0"
LOG_LEVEL="DEBUG"
CORS_ORIGINS="http://localhost:3000,http://localhost:5173"
Usage in Application
FastAPI Application
# main.py
from contextlib import asynccontextmanager
from dotenv import load_dotenv
load_dotenv() # Load .env file
from config.settings import get_settings
@asynccontextmanager
async def lifespan(app):
settings = get_settings()
print(f"Starting {settings.APP_NAME} in {'DEBUG' if settings.DEBUG else 'PROD'} mode")
yield
print("Shutting down...")
app = FastAPI(
title=get_settings().APP_NAME,
lifespan=lifespan,
)
# Include routers
from app.routers import fees, students
app.include_router(fees.router, prefix=get_settings().API_V1_PREFIX)
app.include_router(students.router, prefix=get_settings().API_V1_PREFIX)
Database Connection
# database.py
from sqlmodel import create_engine, Session
from config.settings import get_settings
settings = get_settings()
engine = create_engine(
settings.DB_URL.get_secret_value() if hasattr(settings.DB_URL, 'get_secret_value') else settings.DB_URL,
pool_size=settings.DB_POOL_SIZE,
max_overflow=10,
)
def get_session():
with Session(engine) as session:
yield session
JWT Configuration
# auth/jwt.py
from datetime import timedelta
from config.settings import get_settings
settings = get_settings()
JWT_SECRET = settings.JWT_SECRET_KEY.get_secret_value()
JWT_ALGORITHM = settings.JWT_ALGORITHM
ACCESS_TOKEN_EXPIRE_MINUTES = settings.JWT_EXPIRATION_MINUTES
def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str:
# ... token creation logic
pass
Multi-Environment Support
Environment-Specific Configs
# config/settings.py
class Settings(BaseSettings):
# ... shared settings
@classmethod
def from_env(cls, env: str = "development") -> "Settings":
"""Load settings for specific environment."""
env_file = {
"development": ".env",
"staging": ".env.staging",
"production": ".env.production",
}.get(env, ".env")
return cls(_env_file=env_file)
Production Override
# Production should use environment variables, not .env files
# Set these in your deployment platform (Docker, K8s, Cloud Run, etc.)
export DB_URL="postgresql://prod_user:[email protected]:5432/erp_prod"
export JWT_SECRET_KEY="production-secret-key-from-secrets-manager"
export DEBUG=false
export LOG_LEVEL="WARNING"
Secret Management
Generate Secrets
# Generate secure random secret
openssl rand -hex 32 # For JWT_SECRET_KEY
# Generate database password
openssl rand -base64 32
Secret Rotation Script
# scripts/rotate_secret.py
"""Rotate a secret in all environments."""
import os
import re
def rotate_secret(env_file: str, key: str, new_value: str):
"""Replace secret value in .env file."""
with open(env_file, "r") as f:
content = f.read()
# Pattern to match KEY=value
pattern = f"^{key}=.*$"
replacement = f"{key}={new_value}"
new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
with open(env_file, "w") as f:
f.write(new_content)
print(f"Rotated {key} in {env_file}")
if __name__ == "__main__":
import sys
if len(sys.argv) != 4:
print("Usage: rotate_secret.py <env_file> <key> <new_value>")
sys.exit(1)
rotate_secret(sys.argv[1], sys.argv[2], sys.argv[3])
Quality Checklist
- Validation on load: All required fields have
Field(..., ...)validation - No leaks in logs: Use
SecretStrfor sensitive values, never print settings - .env.example committed: Template shows all required variables
- .env.gitignored: Local development file excluded from version control
- Secrets generated: JWT secrets use
openssl rand -hex 32 - Environment isolation: Different configs for dev/staging/production
- Type safety: All settings have proper type annotations
Integration with Other Skills
| Skill | Integration Point |
|---|---|
@jwt-auth | JWT_SECRET_KEY from settings |
@sqlmodel-crud | DB_URL from settings |
@fastapi-app | All app settings from settings |
@db-migration | Database URL for migrations |
@api-route-design | API prefix, CORS origins |
Security Best Practices
DO
- Use
SecretStrfor passwords, API keys, tokens - Rotate secrets regularly
- Use different secrets per environment
- Store production secrets in secrets manager
- Validate all required settings at startup
DON'T
- Never commit
.envfiles to version control - Never hardcode secrets in source code
- Never log settings or environment variables
- Never use default/placeholder secrets in production
- Never expose configuration details in error messages
Startup Validation
# config/validate.py
"""Validate required configuration at startup."""
from pydantic import ValidationError
from config.settings import Settings
def validate_settings() -> bool:
"""Ensure all required settings are configured."""
try:
settings = Settings()
return True
except ValidationError as e:
print("Configuration validation failed:")
for error in e.errors():
print(f" - {error['loc'][0]}: {error['msg']}")
return False
if __name__ == "__main__":
if not validate_settings():
exit(1)