The Development Environment

Managing Django Environments: Local, Staging, and Production

Proper environment management is crucial for Django applications. This comprehensive guide covers setting up and managing different environments, ensuring smooth transitions from development to production while maintaining security and performance.

Managing Django Environments: Local, Staging, and Production

Proper environment management is crucial for Django applications. This comprehensive guide covers setting up and managing different environments, ensuring smooth transitions from development to production while maintaining security and performance.

Environment Overview

Environment Types

Local Development

  • Individual developer machines
  • Fast iteration and debugging
  • Simplified configuration
  • Test data and fixtures

Staging

  • Production-like environment
  • Integration testing
  • Performance validation
  • Deployment rehearsal

Production

  • Live application serving users
  • Optimized for performance and security
  • Monitoring and logging
  • Backup and disaster recovery

Environment Configuration Strategy

Directory Structure

myproject/
├── config/
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── local.py
│   │   ├── staging.py
│   │   └── production.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── requirements/
│   ├── base.txt
│   ├── local.txt
│   ├── staging.txt
│   └── production.txt
├── deploy/
│   ├── docker/
│   ├── nginx/
│   └── scripts/
├── .env.example
├── .env.local
├── .env.staging
└── .env.production

Base Configuration

config/settings/base.py:

import os
from pathlib import Path
from decouple import config, Csv

# Build paths
BASE_DIR = Path(__file__).resolve().parent.parent.parent

# Core Django settings
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())

# Application definition
DJANGO_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

THIRD_PARTY_APPS = [
    'rest_framework',
    'corsheaders',
]

LOCAL_APPS = [
    'accounts',
    'blog',
    'api',
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'config.urls'

# Templates
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# WSGI/ASGI
WSGI_APPLICATION = 'config.wsgi.application'
ASGI_APPLICATION = 'config.asgi.application'

# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']

# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# Email configuration
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='noreply@example.com')

# Logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
        },
    },
    'root': {
        'handlers': ['console'],
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': 'INFO',
            'propagate': False,
        },
    },
}

Local Development Environment

Local Settings

config/settings/local.py:

from .base import *

# Debug mode
DEBUG = True

# Allowed hosts
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0', '.ngrok.io']

# Database
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# Alternative: PostgreSQL for local development
# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.postgresql',
#         'NAME': config('DB_NAME', default='myproject_local'),
#         'USER': config('DB_USER', default='postgres'),
#         'PASSWORD': config('DB_PASSWORD', default=''),
#         'HOST': config('DB_HOST', default='localhost'),
#         'PORT': config('DB_PORT', default='5432'),
#     }
# }

# Development-specific apps
INSTALLED_APPS += [
    'debug_toolbar',
    'django_extensions',
    'django_browser_reload',
]

# Development middleware
MIDDLEWARE += [
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    'django_browser_reload.middleware.BrowserReloadMiddleware',
]

# Debug toolbar configuration
DEBUG_TOOLBAR_CONFIG = {
    'SHOW_TOOLBAR_CALLBACK': lambda request: True,
    'SHOW_COLLAPSED': True,
    'INTERCEPT_REDIRECTS': False,
}

# Internal IPs for debug toolbar
INTERNAL_IPS = [
    '127.0.0.1',
    'localhost',
]

# Email backend for development
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# Cache (dummy cache for development)
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

# CORS settings for development
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True

# Static files (development)
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'

# Media files (development)
MEDIA_ROOT = BASE_DIR / 'media'

# Logging (development)
LOGGING['loggers']['django']['level'] = 'DEBUG'
LOGGING['loggers']['myproject'] = {
    'handlers': ['console'],
    'level': 'DEBUG',
    'propagate': False,
}

# Development-specific settings
SHELL_PLUS_PRINT_SQL = True
SHELL_PLUS_PRINT_SQL_TRUNCATE = 1000

# File upload settings (relaxed for development)
FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760  # 10MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 10485760  # 10MB

Local Environment Variables

.env.local:

# Django settings
SECRET_KEY=local-development-secret-key-not-for-production
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0

# Database
DATABASE_URL=sqlite:///db.sqlite3
# DATABASE_URL=postgresql://postgres:password@localhost:5432/myproject_local

# Email (development)
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
EMAIL_HOST=localhost
EMAIL_PORT=1025

# Cache
CACHE_URL=dummy://

# External services (development)
REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/0

# API keys (development/testing values)
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test

# Feature flags
FEATURE_NEW_DASHBOARD=True
FEATURE_BETA_FEATURES=True

Local Development Setup

Setup Script (scripts/setup_local.sh):

#!/bin/bash
set -e

echo "🚀 Setting up local development environment..."

# Create virtual environment
if [ ! -d "venv" ]; then
    echo "📦 Creating virtual environment..."
    python -m venv venv
fi

# Activate virtual environment
source venv/bin/activate

# Upgrade pip
echo "⬆️ Upgrading pip..."
pip install --upgrade pip

# Install requirements
echo "📋 Installing requirements..."
pip install -r requirements/local.txt

# Copy environment file
if [ ! -f ".env" ]; then
    echo "🔧 Creating .env file..."
    cp .env.local .env
fi

# Create logs directory
mkdir -p logs

# Run migrations
echo "🗄️ Running migrations..."
python manage.py migrate

# Create superuser if it doesn't exist
echo "👤 Creating superuser..."
python manage.py shell -c "
from django.contrib.auth import get_user_model
User = get_user_model()
if not User.objects.filter(username='admin').exists():
    User.objects.create_superuser('admin', 'admin@example.com', 'admin123')
    print('Superuser created: admin/admin123')
else:
    print('Superuser already exists')
"

# Load sample data
echo "📊 Loading sample data..."
python manage.py loaddata fixtures/sample_data.json

echo "✅ Local development environment setup complete!"
echo "💡 Run 'python manage.py runserver' to start the development server"

Staging Environment

Staging Settings

config/settings/staging.py:

from .base import *
import dj_database_url

# Debug mode (can be True for staging to help with debugging)
DEBUG = config('DEBUG', default=False, cast=bool)

# Allowed hosts
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

# Database
DATABASES = {
    'default': dj_database_url.config(
        default=config('DATABASE_URL')
    )
}

# Security settings (less strict than production)
SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=True, cast=bool)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = config('SESSION_COOKIE_SECURE', default=True, cast=bool)
CSRF_COOKIE_SECURE = config('CSRF_COOKIE_SECURE', default=True, cast=bool)

# Static files
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

# Cache
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': config('REDIS_URL'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

# Email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

# Logging
LOGGING['handlers']['file'] = {
    'level': 'INFO',
    'class': 'logging.handlers.RotatingFileHandler',
    'filename': '/var/log/django/staging.log',
    'maxBytes': 1024*1024*10,  # 10MB
    'backupCount': 5,
    'formatter': 'verbose',
}

LOGGING['loggers']['django']['handlers'] = ['console', 'file']
LOGGING['loggers']['myproject'] = {
    'handlers': ['console', 'file'],
    'level': 'INFO',
    'propagate': False,
}

# Error reporting
ADMINS = [
    ('Staging Admin', config('ADMIN_EMAIL', default='staging@example.com')),
]

# Staging-specific apps (for testing)
INSTALLED_APPS += [
    'django_extensions',  # Useful for staging debugging
]

# Performance monitoring (staging)
if config('ENABLE_SILK', default=False, cast=bool):
    INSTALLED_APPS += ['silk']
    MIDDLEWARE += ['silk.middleware.SilkyMiddleware']

Staging Environment Variables

.env.staging:

# Django settings
SECRET_KEY=staging-secret-key-different-from-production
DEBUG=False
ALLOWED_HOSTS=staging.example.com

# Database
DATABASE_URL=postgresql://user:password@staging-db:5432/myproject_staging

# Email
EMAIL_HOST=smtp.mailgun.org
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=staging@mg.example.com
EMAIL_HOST_PASSWORD=staging-email-password

# Cache
REDIS_URL=redis://staging-redis:6379/0

# External services (staging/testing)
STRIPE_PUBLISHABLE_KEY=pk_test_staging_...
STRIPE_SECRET_KEY=sk_test_staging_...
AWS_ACCESS_KEY_ID=staging_access_key
AWS_SECRET_ACCESS_KEY=staging_secret_key
AWS_STORAGE_BUCKET_NAME=myproject-staging-media

# Monitoring
SENTRY_DSN=https://staging-sentry-dsn@sentry.io/project

# Feature flags (staging can test new features)
FEATURE_NEW_DASHBOARD=True
FEATURE_BETA_FEATURES=True

# Performance monitoring
ENABLE_SILK=True

Staging Deployment

Docker Compose for Staging:

# docker-compose.staging.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.staging
    env_file:
      - .env.staging
    depends_on:
      - db
      - redis
    volumes:
      - ./logs:/var/log/django
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 2

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myproject_staging
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: staging_db_password
    volumes:
      - postgres_staging_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./deploy/nginx/staging.conf:/etc/nginx/conf.d/default.conf
      - ./staticfiles:/var/www/static
    depends_on:
      - web

volumes:
  postgres_staging_data:

Production Environment

Production Settings

config/settings/production.py:

from .base import *
import dj_database_url
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.redis import RedisIntegration

# Security
DEBUG = False
SECRET_KEY = config('SECRET_KEY')
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())

# Database
DATABASES = {
    'default': dj_database_url.config(
        default=config('DATABASE_URL'),
        conn_max_age=60,
        conn_health_checks=True,
    )
}

# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_SECONDS = 31536000
SECURE_REDIRECT_EXEMPT = []
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 3600  # 1 hour
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
X_FRAME_OPTIONS = 'DENY'

# Static files
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
WHITENOISE_USE_FINDERS = True
WHITENOISE_AUTOREFRESH = False
WHITENOISE_MAX_AGE = 31536000

# Media files (use cloud storage in production)
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
AWS_ACCESS_KEY_ID = config('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = config('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = config('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = config('AWS_S3_REGION_NAME', default='us-east-1')
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_DEFAULT_ACL = 'public-read'

# Cache
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': config('REDIS_URL'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 50,
                'retry_on_timeout': True,
            },
            'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor',
            'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
        },
        'KEY_PREFIX': 'myproject_prod',
        'TIMEOUT': 300,
    }
}

# Session engine
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'

# Email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'

# Logging
LOGGING['handlers']['file'] = {
    'level': 'WARNING',
    'class': 'logging.handlers.RotatingFileHandler',
    'filename': '/var/log/django/production.log',
    'maxBytes': 1024*1024*50,  # 50MB
    'backupCount': 10,
    'formatter': 'verbose',
}

LOGGING['handlers']['error_file'] = {
    'level': 'ERROR',
    'class': 'logging.handlers.RotatingFileHandler',
    'filename': '/var/log/django/error.log',
    'maxBytes': 1024*1024*50,  # 50MB
    'backupCount': 10,
    'formatter': 'verbose',
}

LOGGING['loggers']['django'] = {
    'handlers': ['file', 'error_file'],
    'level': 'WARNING',
    'propagate': False,
}

LOGGING['loggers']['myproject'] = {
    'handlers': ['file', 'error_file'],
    'level': 'INFO',
    'propagate': False,
}

# Error reporting
ADMINS = [
    ('Production Admin', config('ADMIN_EMAIL')),
]
MANAGERS = ADMINS

# Sentry error tracking
sentry_sdk.init(
    dsn=config('SENTRY_DSN'),
    integrations=[
        DjangoIntegration(
            transaction_style='url',
            middleware_spans=True,
            signals_spans=True,
        ),
        RedisIntegration(),
    ],
    traces_sample_rate=0.1,
    send_default_pii=False,
    environment='production',
)

# Performance optimizations
CONN_MAX_AGE = 60
USE_TZ = True

# File upload restrictions
FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880  # 5MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880  # 5MB
DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000

# Admin URL (security through obscurity)
ADMIN_URL = config('ADMIN_URL', default='admin/')

Production Environment Variables

.env.production:

# Django settings
SECRET_KEY=super-secure-production-secret-key-change-this
DEBUG=False
ALLOWED_HOSTS=example.com,www.example.com

# Database
DATABASE_URL=postgresql://prod_user:secure_password@prod-db:5432/myproject_prod

# Email
EMAIL_HOST=smtp.sendgrid.net
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=apikey
EMAIL_HOST_PASSWORD=production-sendgrid-api-key
DEFAULT_FROM_EMAIL=noreply@example.com

# Cache
REDIS_URL=redis://prod-redis:6379/0

# AWS S3 (media files)
AWS_ACCESS_KEY_ID=production_access_key
AWS_SECRET_ACCESS_KEY=production_secret_key
AWS_STORAGE_BUCKET_NAME=myproject-production-media
AWS_S3_REGION_NAME=us-east-1

# External services (production)
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_SECRET_KEY=sk_live_...

# Monitoring
SENTRY_DSN=https://production-sentry-dsn@sentry.io/project

# Admin
ADMIN_EMAIL=admin@example.com
ADMIN_URL=secure-admin-url/

# Feature flags (production - conservative)
FEATURE_NEW_DASHBOARD=False
FEATURE_BETA_FEATURES=False

Production Deployment

Docker Compose for Production:

# docker-compose.production.yml
version: '3.8'

services:
  web:
    build: .
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.production
    env_file:
      - .env.production
    depends_on:
      - db
      - redis
    volumes:
      - ./logs:/var/log/django
      - ./staticfiles:/app/staticfiles
    command: gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --worker-class gevent
    restart: unless-stopped

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myproject_prod
      POSTGRES_USER: prod_user
      POSTGRES_PASSWORD: secure_db_password
    volumes:
      - postgres_prod_data:/var/lib/postgresql/data
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_prod_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./deploy/nginx/production.conf:/etc/nginx/conf.d/default.conf
      - ./deploy/ssl:/etc/nginx/ssl
      - ./staticfiles:/var/www/static
    depends_on:
      - web
    restart: unless-stopped

  celery:
    build: .
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.production
    env_file:
      - .env.production
    depends_on:
      - db
      - redis
    command: celery -A config worker -l info
    restart: unless-stopped

  celery-beat:
    build: .
    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.production
    env_file:
      - .env.production
    depends_on:
      - db
      - redis
    command: celery -A config beat -l info
    restart: unless-stopped

volumes:
  postgres_prod_data:
  redis_prod_data:

Environment Management Scripts

Environment Switcher

scripts/switch_env.py:

#!/usr/bin/env python
"""
Environment switcher script
Usage: python scripts/switch_env.py local|staging|production
"""
import os
import sys
import shutil
from pathlib import Path

def switch_environment(env_name):
    """Switch to specified environment."""
    
    valid_envs = ['local', 'staging', 'production']
    if env_name not in valid_envs:
        print(f"Invalid environment. Choose from: {', '.join(valid_envs)}")
        return False
    
    # Copy environment file
    env_file = f'.env.{env_name}'
    if not os.path.exists(env_file):
        print(f"Environment file {env_file} not found")
        return False
    
    shutil.copy(env_file, '.env')
    print(f"✓ Switched to {env_name} environment")
    
    # Update manage.py settings module
    settings_module = f'config.settings.{env_name}'
    
    # Create or update .django_settings file
    with open('.django_settings', 'w') as f:
        f.write(settings_module)
    
    print(f"✓ Django settings module: {settings_module}")
    
    # Show current configuration
    print("\nCurrent configuration:")
    print(f"Environment: {env_name}")
    print(f"Settings module: {settings_module}")
    print(f"Environment file: {env_file}")
    
    return True

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: python scripts/switch_env.py <environment>")
        print("Environments: local, staging, production")
        sys.exit(1)
    
    env_name = sys.argv[1]
    success = switch_environment(env_name)
    sys.exit(0 if success else 1)

Environment Validation

scripts/validate_env.py:

#!/usr/bin/env python
"""
Environment validation script
"""
import os
import sys
import django
from pathlib import Path

def validate_environment():
    """Validate current environment configuration."""
    
    # Load Django settings
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')
    django.setup()
    
    from django.conf import settings
    from django.core.management import call_command
    
    print("🔍 Validating environment configuration...")
    
    # Check required settings
    required_settings = [
        'SECRET_KEY',
        'DATABASES',
        'ALLOWED_HOSTS',
    ]
    
    missing_settings = []
    for setting in required_settings:
        if not hasattr(settings, setting):
            missing_settings.append(setting)
    
    if missing_settings:
        print(f"❌ Missing required settings: {', '.join(missing_settings)}")
        return False
    
    # Check database connectivity
    try:
        from django.db import connection
        connection.ensure_connection()
        print("✓ Database connection successful")
    except Exception as e:
        print(f"❌ Database connection failed: {e}")
        return False
    
    # Check cache connectivity
    try:
        from django.core.cache import cache
        cache.set('test_key', 'test_value', 30)
        if cache.get('test_key') == 'test_value':
            print("✓ Cache connection successful")
        else:
            print("⚠️ Cache connection issue")
    except Exception as e:
        print(f"⚠️ Cache connection failed: {e}")
    
    # Run Django system checks
    try:
        call_command('check', verbosity=0)
        print("✓ Django system checks passed")
    except Exception as e:
        print(f"❌ Django system checks failed: {e}")
        return False
    
    # Environment-specific checks
    if settings.DEBUG:
        print("⚠️ DEBUG mode is enabled")
        if 'production' in os.environ.get('DJANGO_SETTINGS_MODULE', ''):
            print("❌ DEBUG should be False in production")
            return False
    
    if settings.SECRET_KEY == 'django-insecure-default-key':
        print("❌ Using default SECRET_KEY")
        return False
    
    print("✅ Environment validation passed")
    return True

if __name__ == '__main__':
    success = validate_environment()
    sys.exit(0 if success else 1)

Deployment Strategies

Blue-Green Deployment

scripts/blue_green_deploy.sh:

#!/bin/bash
set -e

ENVIRONMENT=${1:-staging}
NEW_VERSION=${2:-latest}

echo "🚀 Starting blue-green deployment for $ENVIRONMENT"

# Build new version
echo "📦 Building new version..."
docker build -t myproject:$NEW_VERSION .

# Deploy to green environment
echo "🟢 Deploying to green environment..."
docker-compose -f docker-compose.$ENVIRONMENT.yml -p myproject-green up -d

# Health check
echo "🏥 Running health checks..."
sleep 30
if curl -f http://localhost:8001/health/; then
    echo "✅ Health check passed"
else
    echo "❌ Health check failed"
    docker-compose -f docker-compose.$ENVIRONMENT.yml -p myproject-green down
    exit 1
fi

# Switch traffic
echo "🔄 Switching traffic..."
# Update load balancer configuration
# This would be specific to your load balancer (nginx, HAProxy, etc.)

# Stop old version
echo "🔴 Stopping blue environment..."
docker-compose -f docker-compose.$ENVIRONMENT.yml -p myproject-blue down

echo "✅ Blue-green deployment completed successfully"

Rolling Deployment

scripts/rolling_deploy.sh:

#!/bin/bash
set -e

ENVIRONMENT=${1:-production}
REPLICAS=${2:-3}

echo "🔄 Starting rolling deployment for $ENVIRONMENT"

# Update one replica at a time
for i in $(seq 1 $REPLICAS); do
    echo "📦 Updating replica $i/$REPLICAS..."
    
    # Stop replica
    docker-compose -f docker-compose.$ENVIRONMENT.yml stop web_$i
    
    # Update and start replica
    docker-compose -f docker-compose.$ENVIRONMENT.yml up -d web_$i
    
    # Health check
    sleep 15
    if curl -f http://localhost:800$i/health/; then
        echo "✅ Replica $i updated successfully"
    else
        echo "❌ Replica $i health check failed"
        exit 1
    fi
done

echo "✅ Rolling deployment completed successfully"

Monitoring and Maintenance

Environment Health Check

management/commands/health_check.py:

from django.core.management.base import BaseCommand
from django.db import connection
from django.core.cache import cache
import requests

class Command(BaseCommand):
    help = 'Perform environment health check'
    
    def handle(self, *args, **options):
        checks = [
            self.check_database,
            self.check_cache,
            self.check_external_services,
        ]
        
        all_passed = True
        
        for check in checks:
            try:
                check()
                self.stdout.write(
                    self.style.SUCCESS(f'✓ {check.__name__} passed')
                )
            except Exception as e:
                self.stdout.write(
                    self.style.ERROR(f'✗ {check.__name__} failed: {e}')
                )
                all_passed = False
        
        if all_passed:
            self.stdout.write(self.style.SUCCESS('All health checks passed'))
        else:
            self.stdout.write(self.style.ERROR('Some health checks failed'))
            exit(1)
    
    def check_database(self):
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
            cursor.fetchone()
    
    def check_cache(self):
        cache.set('health_check', 'ok', 30)
        if cache.get('health_check') != 'ok':
            raise Exception('Cache not working')
    
    def check_external_services(self):
        # Check external API endpoints
        response = requests.get('https://api.example.com/health', timeout=5)
        response.raise_for_status()

Proper environment management ensures smooth development workflows, reliable deployments, and maintainable applications across different stages of the development lifecycle.