Concept #32Mediumpython-for-gen-ai

How would you structure environment variables for API keys in production?

#gen-ai#python#mlops

Answer

Environment Variables and API Key Management in Production

Managing secrets securely is non-negotiable. Hardcoding API keys causes security incidents and billing nightmares.

Development:
text
.env
Files

bash
# .env (NEVER commit to git)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
PINECONE_API_KEY=...
LANGCHAIN_API_KEY=ls_...
DATABASE_URL=postgresql://user:pass@localhost/mydb

# Environment name
ENVIRONMENT=development
LOG_LEVEL=DEBUG
bash
# .gitignore — always include
.env
.env.local
.env.*.local
*.pem
*.key

Loading with Pydantic Settings (Recommended)

python
from pydantic_settings import BaseSettings
from pydantic import SecretStr, validator
from functools import lru_cache
from enum import Enum

class Environment(str, Enum):
    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"

class Settings(BaseSettings):
    # Secrets — masked in logs and repr
    openai_api_key: SecretStr
    anthropic_api_key: SecretStr | None = None
    pinecone_api_key: SecretStr | None = None

    # Config
    environment: Environment = Environment.DEVELOPMENT
    log_level: str = "INFO"
    llm_model: str = "gpt-4o"
    max_tokens: int = 2048

    @validator("openai_api_key")
    def validate_api_key(cls, v):
        if not str(v.get_secret_value()).startswith("sk-"):
            raise ValueError("Invalid OpenAI API key format")
        return v

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"
        case_sensitive = False

@lru_cache()
def get_settings() -> Settings:
    '''Singleton — settings loaded once and cached.'''
    return Settings()

# Usage
settings = get_settings()
# Access secret: settings.openai_api_key.get_secret_value()
# In logs: settings.openai_api_key → "**********" (masked)

Production: Use a Secrets Manager

Never store secrets as environment variables in production containers. Use:

python
import boto3
import json
from functools import lru_cache

@lru_cache()
def get_secret(secret_name: str, region: str = "us-east-1") -> dict:
    '''Fetch secrets from AWS Secrets Manager.'''
    client = boto3.client("secretsmanager", region_name=region)
    response = client.get_secret_value(SecretId=secret_name)
    return json.loads(response["SecretString"])

# Usage
secrets = get_secret("myapp/production/api-keys")
openai_key = secrets["OPENAI_API_KEY"]

Alternative services:

  • GCP Secret Manager:
    text
    google-cloud-secret-manager
  • HashiCorp Vault:
    text
    hvac
    Python client
  • Azure Key Vault:
    text
    azure-keyvault-secrets

Different Keys per Environment

python
import os

def get_llm_client():
    env = os.getenv("ENVIRONMENT", "development")

    if env == "production":
        # High-capacity key from secrets manager
        api_key = get_secret("prod/openai")["key"]
        model = "gpt-4o"
    elif env == "staging":
        # Separate staging key with spending limits
        api_key = get_secret("staging/openai")["key"]
        model = "gpt-4o-mini"
    else:
        # Local dev key from .env
        api_key = os.getenv("OPENAI_API_KEY")
        model = "gpt-4o-mini"  # Cheaper for dev

    from openai import OpenAI
    return OpenAI(api_key=api_key), model

Security Checklist

RuleImplementation
Never hardcode secretsUse
text
.env
+ secrets manager
Never commit
text
.env
Add to
text
.gitignore
immediately
Rotate keys regularlyAutomate via secrets manager
Separate keys per envDev, staging, prod keys
Set spending limitsOpenAI dashboard → Usage limits
Monitor usageAlerts for anomalous API spend
Least privilegeEach service gets only keys it needs

Incident prevention: Run

text
git-secrets
or
text
truffleHog
in your CI pipeline to automatically detect accidentally committed secrets before they reach the remote repository.