Django DevOps Skill
Purpose
Define and enforce DevOps standards for building, deploying, monitoring, and operating enterprise Django 6 systems with Strawberry GraphQL, SSE, Celery, Redis, and PostgreSQL.
Scope
- Containerization (Docker)
- CI/CD pipeline design
- Environment management
- Database migration deployment
- Health checks and readiness probes
- Logging and monitoring
- Secrets management in deployments
- Scaling and resource allocation
- Backup and disaster recovery
Responsibilities
- ENFORCE containerized deployments.
- ENFORCE CI/CD with automated testing and migration validation.
- ENFORCE environment parity across development, staging, and production.
- ENFORCE structured logging and monitoring.
- ENFORCE health check endpoints for orchestrators.
- PREVENT manual deployments without CI/CD.
- PREVENT unreviewed migrations in production.
- PREVENT secret leakage in CI/CD logs or Docker images.
Mandatory Rules
ALWAYS
- ALWAYS containerize the application with a multi-stage Dockerfile:
# Stage 1: Builder
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: Runtime
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "CMS.asgi:application", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
- ALWAYS use ASGI server (Uvicorn/Daphne/Hypercorn) for production. Never use Django's
runserver.
- ALWAYS implement health check endpoints:
# urls.py
path('health/', HealthCheckView.as_view(), name='health-check'),
path('health/ready/', ReadinessCheckView.as_view(), name='readiness-check'),
- Liveness (
/health/): Returns 200 if the process is alive.
- Readiness (
/health/ready/): Returns 200 if DB, Redis, and Celery are reachable.
- ALWAYS run migrations as a separate step in deployment, BEFORE starting new application instances:
# CI/CD Pipeline
steps:
- name: Run migrations
run: python manage.py migrate --noinput
- name: Deploy new version
run: kubectl rollout restart deployment/cms-api
- ALWAYS validate migrations in CI before deployment:
python manage.py makemigrations --check --dry-run # Fail if unapplied migrations exist
python manage.py migrate --plan # Show migration plan
- ALWAYS use environment variables for all configuration. Never bake configuration into Docker images.
- ALWAYS use
.dockerignore to exclude .git, __pycache__, .env, node_modules, media files.
- ALWAYS tag Docker images with the Git commit SHA, not
latest:docker build -t cms-backend:$(git rev-parse --short HEAD) .
- ALWAYS use structured JSON logging in production:
LOGGING = {
'version': 1,
'formatters': {
'json': {
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(name)s %(levelname)s %(message)s',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'json',
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
}
- ALWAYS monitor these metrics:
- Request latency (p50, p95, p99)
- Error rate (5xx responses)
- Database query count and latency
- Redis connection pool usage
- Celery task queue depth and processing time
- SSE active connection count
- Memory and CPU usage per container
- ALWAYS define resource limits for containers:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
- ALWAYS implement graceful shutdown — handle SIGTERM to finish in-flight requests:
# Uvicorn handles this natively with --timeout-graceful-shutdown
CMD ["uvicorn", "CMS.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--timeout-graceful-shutdown", "30"]
NEVER
- NEVER use
runserver in production.
- NEVER bake secrets into Docker images.
- NEVER deploy without running the test suite in CI.
- NEVER run migrations and application startup in the same process simultaneously.
- NEVER use
DEBUG=True in production.
- NEVER expose database ports to the public internet.
- NEVER use
latest tag for Docker images in production deployments.
- NEVER skip health checks in orchestrator configuration.
- NEVER log request/response bodies in production (PII risk).
- NEVER store persistent data in container filesystems (ephemeral).
CI/CD Pipeline
Pipeline Stages
1. Lint → ruff check, ruff format --check
2. Test → pytest with coverage
3. Migration → makemigrations --check, migrate --plan
4. Build → Docker image build
5. Security → Dependency vulnerability scan (pip-audit, safety)
6. Push → Push image to registry
7. Deploy → Apply migrations → Rolling update
8. Smoke Test → Hit health endpoints on new deployment
CI Configuration Example
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: cms_test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports: ["5432:5432"]
redis:
image: redis:7
ports: ["6379:6379"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: ruff check .
- run: ruff format --check .
- run: python manage.py makemigrations --check --dry-run
- run: pytest --cov --cov-fail-under=80
Database Deployment Rules
- Run migrations in a dedicated init container or CI step, not in the application process.
- Use
--plan to preview migrations before applying.
- For zero-downtime migrations, follow the expand-contract pattern:
- Expand: Add new column (nullable), deploy code that writes to both old and new.
- Migrate: Backfill data.
- Contract: Remove old column, deploy code that uses only new.
- Never run destructive migrations (RemoveField, DeleteModel) without a maintenance window or expand-contract strategy.
Environment Management
.env.example # Template with all required vars (committed)
.env # Local development values (NOT committed)
.env.staging # Staging values (NOT committed, in CI secrets)
.env.production # Production values (NOT committed, in secret manager)
Required environment variables:
SECRET_KEY=
DEBUG=
DATABASE_URL=
REDIS_URL=
ALLOWED_HOSTS=
CORS_ALLOWED_ORIGINS=
CELERY_BROKER_URL=
Security Considerations
- Scan dependencies for vulnerabilities in CI:
pip-audit, safety check.
- Use non-root user in Docker containers.
- Run containers with read-only filesystem where possible.
- Use network policies to restrict container-to-container communication.
- Rotate secrets regularly.
Performance Considerations
- Use Gunicorn with Uvicorn workers for ASGI:
gunicorn -k uvicorn.workers.UvicornWorker -w 4.
- Worker count formula:
(2 × CPU cores) + 1 for CPU-bound, higher for I/O-bound.
- Use PgBouncer for database connection pooling in production.
- Enable gzip compression at the reverse proxy level (nginx).
- Serve static files via CDN/nginx, not Django.
Scalability Guidelines
- Run ASGI servers, Celery workers, and Celery Beat as separate containers/processes.
- Scale ASGI servers horizontally behind a load balancer.
- Scale Celery workers per queue based on queue depth.
- Use horizontal pod autoscaling based on CPU/memory metrics.
- Use database read replicas for read-heavy workloads.
- Configure load balancer for SSE: HTTP/1.1, keep-alive, no buffering, long read timeout.
Production Readiness Checklist
Refusal Conditions
REFUSE to generate deployment configurations that:
- Use
runserver in production.
- Hardcode secrets in Dockerfiles or compose files.
- Use
latest image tags.
- Skip health checks.
- Run as root in containers.
- Expose database ports publicly.
Trade-off Handling
| Trade-off |
Decision Rule |
| Docker Compose vs Kubernetes |
Compose for dev/staging. Kubernetes for production. |
| Single process vs Multi-process |
Multi-process: separate web, worker, beat containers. |
| Rolling update vs Blue-green |
Rolling update for normal deploys. Blue-green for breaking migrations. |
| Managed DB vs Self-hosted |
Managed (RDS, CloudSQL) for production. Self-hosted only for development. |
1---2name: django-devops3description: Enterprise Django 6 DevOps — containerization, CI/CD, environment management, database deployment, monitoring, and production readiness checklist.4---56# Django DevOps Skill78## Purpose910Define and enforce DevOps standards for building, deploying, monitoring, and operating enterprise Django 6 systems with Strawberry GraphQL, SSE, Celery, Redis, and PostgreSQL.1112## Scope1314- Containerization (Docker)15- CI/CD pipeline design16- Environment management17- Database migration deployment18- Health checks and readiness probes19- Logging and monitoring20- Secrets management in deployments21- Scaling and resource allocation22- Backup and disaster recovery2324## Responsibilities25261. ENFORCE containerized deployments.272. ENFORCE CI/CD with automated testing and migration validation.283. ENFORCE environment parity across development, staging, and production.294. ENFORCE structured logging and monitoring.305. ENFORCE health check endpoints for orchestrators.316. PREVENT manual deployments without CI/CD.327. PREVENT unreviewed migrations in production.338. PREVENT secret leakage in CI/CD logs or Docker images.3435---3637## Mandatory Rules3839### ALWAYS40411. ALWAYS containerize the application with a multi-stage Dockerfile:42 ```dockerfile43 # Stage 1: Builder44 FROM python:3.12-slim AS builder45 WORKDIR /app46 COPY requirements.txt .47 RUN pip install --no-cache-dir --prefix=/install -r requirements.txt4849 # Stage 2: Runtime50 FROM python:3.12-slim51 WORKDIR /app52 COPY --from=builder /install /usr/local53 COPY . .54 RUN python manage.py collectstatic --noinput55 EXPOSE 800056 CMD ["gunicorn", "CMS.asgi:application", "-k", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]57 ```582. ALWAYS use ASGI server (Uvicorn/Daphne/Hypercorn) for production. Never use Django's `runserver`.593. ALWAYS implement health check endpoints:60 ```python61 # urls.py62 path('health/', HealthCheckView.as_view(), name='health-check'),63 path('health/ready/', ReadinessCheckView.as_view(), name='readiness-check'),64 ```65 - **Liveness** (`/health/`): Returns 200 if the process is alive.66 - **Readiness** (`/health/ready/`): Returns 200 if DB, Redis, and Celery are reachable.674. ALWAYS run migrations as a separate step in deployment, BEFORE starting new application instances:68 ```yaml69 # CI/CD Pipeline70 steps:71 - name: Run migrations72 run: python manage.py migrate --noinput73 - name: Deploy new version74 run: kubectl rollout restart deployment/cms-api75 ```765. ALWAYS validate migrations in CI before deployment:77 ```bash78 python manage.py makemigrations --check --dry-run # Fail if unapplied migrations exist79 python manage.py migrate --plan # Show migration plan80 ```816. ALWAYS use environment variables for all configuration. Never bake configuration into Docker images.827. ALWAYS use `.dockerignore` to exclude `.git`, `__pycache__`, `.env`, `node_modules`, media files.838. ALWAYS tag Docker images with the Git commit SHA, not `latest`:84 ```bash85 docker build -t cms-backend:$(git rev-parse --short HEAD) .86 ```879. ALWAYS use structured JSON logging in production:88 ```python89 LOGGING = {90 'version': 1,91 'formatters': {92 'json': {93 '()': 'pythonjsonlogger.jsonlogger.JsonFormatter',94 'format': '%(asctime)s %(name)s %(levelname)s %(message)s',95 },96 },97 'handlers': {98 'console': {99 'class': 'logging.StreamHandler',100 'formatter': 'json',101 },102 },103 'root': {104 'handlers': ['console'],105 'level': 'INFO',106 },107 }108 ```10910. ALWAYS monitor these metrics:110 - Request latency (p50, p95, p99)111 - Error rate (5xx responses)112 - Database query count and latency113 - Redis connection pool usage114 - Celery task queue depth and processing time115 - SSE active connection count116 - Memory and CPU usage per container11711. ALWAYS define resource limits for containers:118 ```yaml119 resources:120 requests:121 memory: "256Mi"122 cpu: "250m"123 limits:124 memory: "512Mi"125 cpu: "500m"126 ```12712. ALWAYS implement graceful shutdown — handle SIGTERM to finish in-flight requests:128 ```python129 # Uvicorn handles this natively with --timeout-graceful-shutdown130 CMD ["uvicorn", "CMS.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--timeout-graceful-shutdown", "30"]131 ```132133### NEVER1341351. NEVER use `runserver` in production.1362. NEVER bake secrets into Docker images.1373. NEVER deploy without running the test suite in CI.1384. NEVER run migrations and application startup in the same process simultaneously.1395. NEVER use `DEBUG=True` in production.1406. NEVER expose database ports to the public internet.1417. NEVER use `latest` tag for Docker images in production deployments.1428. NEVER skip health checks in orchestrator configuration.1439. NEVER log request/response bodies in production (PII risk).14410. NEVER store persistent data in container filesystems (ephemeral).145146---147148## CI/CD Pipeline149150### Pipeline Stages151152```1531. Lint → ruff check, ruff format --check1542. Test → pytest with coverage1553. Migration → makemigrations --check, migrate --plan1564. Build → Docker image build1575. Security → Dependency vulnerability scan (pip-audit, safety)1586. Push → Push image to registry1597. Deploy → Apply migrations → Rolling update1608. Smoke Test → Hit health endpoints on new deployment161```162163### CI Configuration Example164165```yaml166# .github/workflows/ci.yml167name: CI168on: [push, pull_request]169jobs:170 test:171 runs-on: ubuntu-latest172 services:173 postgres:174 image: postgres:16175 env:176 POSTGRES_DB: cms_test177 POSTGRES_USER: test178 POSTGRES_PASSWORD: test179 ports: ["5432:5432"]180 redis:181 image: redis:7182 ports: ["6379:6379"]183 steps:184 - uses: actions/checkout@v4185 - uses: actions/setup-python@v5186 with:187 python-version: '3.12'188 - run: pip install -r requirements.txt189 - run: ruff check .190 - run: ruff format --check .191 - run: python manage.py makemigrations --check --dry-run192 - run: pytest --cov --cov-fail-under=80193```194195---196197## Database Deployment Rules1981991. Run migrations in a dedicated init container or CI step, not in the application process.2002. Use `--plan` to preview migrations before applying.2013. For zero-downtime migrations, follow the expand-contract pattern:202 - Expand: Add new column (nullable), deploy code that writes to both old and new.203 - Migrate: Backfill data.204 - Contract: Remove old column, deploy code that uses only new.2054. Never run destructive migrations (RemoveField, DeleteModel) without a maintenance window or expand-contract strategy.206207---208209## Environment Management210211```212.env.example # Template with all required vars (committed)213.env # Local development values (NOT committed)214.env.staging # Staging values (NOT committed, in CI secrets)215.env.production # Production values (NOT committed, in secret manager)216```217218Required environment variables:219```220SECRET_KEY=221DEBUG=222DATABASE_URL=223REDIS_URL=224ALLOWED_HOSTS=225CORS_ALLOWED_ORIGINS=226CELERY_BROKER_URL=227```228229---230231## Security Considerations2322331. Scan dependencies for vulnerabilities in CI: `pip-audit`, `safety check`.2342. Use non-root user in Docker containers.2353. Run containers with read-only filesystem where possible.2364. Use network policies to restrict container-to-container communication.2375. Rotate secrets regularly.238239---240241## Performance Considerations2422431. Use Gunicorn with Uvicorn workers for ASGI: `gunicorn -k uvicorn.workers.UvicornWorker -w 4`.2442. Worker count formula: `(2 × CPU cores) + 1` for CPU-bound, higher for I/O-bound.2453. Use PgBouncer for database connection pooling in production.2464. Enable gzip compression at the reverse proxy level (nginx).2475. Serve static files via CDN/nginx, not Django.248249---250251## Scalability Guidelines2522531. Run ASGI servers, Celery workers, and Celery Beat as separate containers/processes.2542. Scale ASGI servers horizontally behind a load balancer.2553. Scale Celery workers per queue based on queue depth.2564. Use horizontal pod autoscaling based on CPU/memory metrics.2575. Use database read replicas for read-heavy workloads.2586. Configure load balancer for SSE: HTTP/1.1, keep-alive, no buffering, long read timeout.259260---261262## Production Readiness Checklist263264- [ ] `DEBUG=False`265- [ ] `SECRET_KEY` from environment, not hardcoded266- [ ] `ALLOWED_HOSTS` explicitly set267- [ ] HTTPS enforced (`SECURE_SSL_REDIRECT=True`)268- [ ] Security headers set (HSTS, CSP, X-Frame-Options)269- [ ] Static files served via CDN/nginx270- [ ] Database connection pooling configured271- [ ] Redis connection pooling configured272- [ ] Health check endpoints implemented273- [ ] Structured JSON logging enabled274- [ ] Monitoring and alerting configured275- [ ] CI/CD pipeline with tests, lint, and migration checks276- [ ] Graceful shutdown configured277- [ ] Resource limits set on containers278- [ ] Backup strategy for database279- [ ] Disaster recovery plan documented280281---282283## Refusal Conditions284285REFUSE to generate deployment configurations that:2861. Use `runserver` in production.2872. Hardcode secrets in Dockerfiles or compose files.2883. Use `latest` image tags.2894. Skip health checks.2905. Run as root in containers.2916. Expose database ports publicly.292293---294295## Trade-off Handling296297| Trade-off | Decision Rule |298|---|---|299| Docker Compose vs Kubernetes | Compose for dev/staging. Kubernetes for production. |300| Single process vs Multi-process | Multi-process: separate web, worker, beat containers. |301| Rolling update vs Blue-green | Rolling update for normal deploys. Blue-green for breaking migrations. |302| Managed DB vs Self-hosted | Managed (RDS, CloudSQL) for production. Self-hosted only for development. |