Django is the web framework for perfectionists with deadlines. It powers Instagram, Pinterest, and thousands of internal DevOps tools. When you need to build a dashboard for infrastructure metrics, a self-service portal for teams, or a REST API for your automation—Django is the answer. In this lesson, you'll build a complete infrastructure monitoring dashboard from scratch.

1. Learning Objectives

By the end of this lesson, you will be able to:

  • Create a Django project and understand its structure
  • Define models for infrastructure resources
  • Create views and URL routing
  • Build templates for web dashboards
  • Use Django's admin interface for data management
  • Create a REST API for infrastructure monitoring

2. Why Django for DevOps?

Real-world scenario: Your team needs a web interface to see which EC2 instances are running, their status, and who last deployed to them. You could build this from scratch in weeks, or use Django to build it in days. Django gives you:

  • ✅ Admin interface (automatic CRUD for your data)
  • ✅ ORM (database queries without SQL)
  • ✅ Authentication and permissions
  • ✅ Form handling and validation
  • ✅ Security (SQL injection, XSS, CSRF protection)
  • ✅ REST API framework (Django REST Framework)

3. Core Concepts

Project Setup

# Create virtual environment
python3 -m venv django-devops
source django-devops/bin/activate

# Install Django
pip install django djangorestframework

# Create project
mkdir devops-dashboard
cd devops-dashboard
django-admin startproject devops_dashboard .

# Create app (each app is a module)
python manage.py startapp infrastructure

# Run initial setup
python manage.py migrate
python manage.py createsuperuser

# Run development server
python manage.py runserver

# Visit http://127.0.0.1:8000/
Setting up a Django project
kubectl get pods -w
$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 22, 2025 - 10:00:00
Django version 5.0, using settings 'devops_dashboard.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Django development server running

Project Structure Explained

devops-dashboard/
├── manage.py                 # Command-line utility
├── devops_dashboard/         # Project config directory
│   ├── __init__.py
│   ├── settings.py           # Project settings
│   ├── urls.py               # Main URL routing
│   └── wsgi.py               # Deployment entry point
└── infrastructure/           # Your app
    ├── __init__.py
    ├── admin.py              # Admin interface config
    ├── apps.py               # App config
    ├── models.py             # Database models
    ├── urls.py               # App URL routing
    ├── views.py              # Request handlers
    └── templates/            # HTML templates
        └── infrastructure/
Django project structure

Models: Defining Your Data

# infrastructure/models.py
from django.db import models
from django.utils import timezone

class Server(models.Model):
    """Infrastructure server model"""
    
    # Choices for server status
    STATUS_CHOICES = [
        ('running', 'Running'),
        ('stopped', 'Stopped'),
        ('degraded', 'Degraded'),
        ('maintenance', 'Maintenance'),
    ]
    
    ENVIRONMENT_CHOICES = [
        ('dev', 'Development'),
        ('staging', 'Staging'),
        ('production', 'Production'),
    ]
    
    # Basic info
    name = models.CharField(max_length=100, unique=True)
    ip_address = models.GenericIPAddressField()
    environment = models.CharField(max_length=20, choices=ENVIRONMENT_CHOICES, default='dev')
    
    # Status and metrics
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='running')
    cpu_usage = models.FloatField(default=0.0, help_text="CPU usage percentage")
    memory_usage = models.FloatField(default=0.0, help_text="Memory usage percentage")
    disk_usage = models.FloatField(default=0.0, help_text="Disk usage percentage")
    
    # Metadata
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    last_checked = models.DateTimeField(default=timezone.now)
    
    # Tags
    tags = models.CharField(max_length=500, blank=True, help_text="Comma-separated tags")
    
    class Meta:
        ordering = ['-created_at']
        
    def __str__(self):
        return f"{self.name} ({self.ip_address}) - {self.status}"
    
    def is_healthy(self):
        """Determine if server is healthy"""
        return self.status == 'running' and self.cpu_usage < 90


class Deployment(models.Model):
    """Deployment history"""
    
    STATUS_CHOICES = [
        ('success', 'Success'),
        ('failed', 'Failed'),
        ('in_progress', 'In Progress'),
        ('rolled_back', 'Rolled Back'),
    ]
    
    server = models.ForeignKey(Server, on_delete=models.CASCADE, related_name='deployments')
    version = models.CharField(max_length=50)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='in_progress')
    started_at = models.DateTimeField(auto_now_add=True)
    completed_at = models.DateTimeField(null=True, blank=True)
    deployed_by = models.CharField(max_length=100)
    commit_hash = models.CharField(max_length=40, blank=True)
    notes = models.TextField(blank=True)
    
    class Meta:
        ordering = ['-started_at']
        
    def __str__(self):
        return f"{self.server.name} - {self.version} ({self.status})"


class Metric(models.Model):
    """Time-series metrics for servers"""
    
    server = models.ForeignKey(Server, on_delete=models.CASCADE, related_name='metrics')
    metric_type = models.CharField(max_length=50)  # cpu, memory, disk, network
    value = models.FloatField()
    recorded_at = models.DateTimeField(default=timezone.now)
    
    class Meta:
        ordering = ['-recorded_at']
        indexes = [
            models.Index(fields=['server', 'metric_type', '-recorded_at']),
        ]
    
    def __str__(self):
        return f"{self.server.name} - {self.metric_type}: {self.value}"
Complete infrastructure models for DevOps

Register Models with Admin

# infrastructure/admin.py
from django.contrib import admin
from .models import Server, Deployment, Metric

@admin.register(Server)
class ServerAdmin(admin.ModelAdmin):
    list_display = ['name', 'ip_address', 'environment', 'status', 'cpu_usage', 'is_healthy']
    list_filter = ['environment', 'status']
    search_fields = ['name', 'ip_address', 'tags']
    readonly_fields = ['created_at', 'updated_at']
    fieldsets = (
        ('Basic Info', {
            'fields': ('name', 'ip_address', 'environment')
        }),
        ('Status', {
            'fields': ('status', 'cpu_usage', 'memory_usage', 'disk_usage')
        }),
        ('Metadata', {
            'fields': ('tags', 'created_at', 'updated_at', 'last_checked')
        }),
    )

@admin.register(Deployment)
class DeploymentAdmin(admin.ModelAdmin):
    list_display = ['server', 'version', 'status', 'started_at', 'deployed_by']
    list_filter = ['status', 'server']
    search_fields = ['server__name', 'version', 'commit_hash']
    readonly_fields = ['started_at']

@admin.register(Metric)
class MetricAdmin(admin.ModelAdmin):
    list_display = ['server', 'metric_type', 'value', 'recorded_at']
    list_filter = ['metric_type', 'server']
    date_hierarchy = 'recorded_at'
Django admin configuration

Apply Database Changes

# Create migration files
python manage.py makemigrations

# See SQL that will be executed
python manage.py sqlmigrate infrastructure 0001

# Apply migrations
python manage.py migrate

# Create admin user (if not done)
python manage.py createsuperuser

# Run server
python manage.py runserver
Migrations and setup

Views: Handling Requests

# infrastructure/views.py
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.core.paginator import Paginator
from django.utils import timezone
from .models import Server, Deployment, Metric

def server_list(request):
    """Display all servers with pagination"""
    servers = Server.objects.all()
    
    # Filter by environment
    environment = request.GET.get('environment')
    if environment:
        servers = servers.filter(environment=environment)
    
    # Filter by status
    status = request.GET.get('status')
    if status:
        servers = servers.filter(status=status)
    
    # Pagination
    paginator = Paginator(servers, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    # Statistics for dashboard
    stats = {
        'total': Server.objects.count(),
        'running': Server.objects.filter(status='running').count(),
        'production': Server.objects.filter(environment='production').count(),
        'healthy': sum(1 for s in Server.objects.all() if s.is_healthy()),
    }
    
    context = {
        'servers': page_obj,
        'stats': stats,
        'environments': Server.ENVIRONMENT_CHOICES,
        'statuses': Server.STATUS_CHOICES,
    }
    return render(request, 'infrastructure/server_list.html', context)


def server_detail(request, server_id):
    """Display detailed information for a specific server"""
    server = get_object_or_404(Server, id=server_id)
    deployments = server.deployments.all()[:10]
    recent_metrics = server.metrics.all()[:24]  # Last 24 metrics
    
    context = {
        'server': server,
        'deployments': deployments,
        'metrics': recent_metrics,
    }
    return render(request, 'infrastructure/server_detail.html', context)


def dashboard(request):
    """Main dashboard with overview statistics"""
    # Get all servers
    servers = Server.objects.all()
    
    # Health summary
    healthy = sum(1 for s in servers if s.is_healthy())
    degraded = servers.filter(status='degraded').count()
    maintenance = servers.filter(status='maintenance').count()
    
    # Recent deployments
    recent_deployments = Deployment.objects.all()[:10]
    
    # Environment breakdown
    dev_count = servers.filter(environment='dev').count()
    staging_count = servers.filter(environment='staging').count()
    prod_count = servers.filter(environment='production').count()
    
    context = {
        'total_servers': servers.count(),
        'healthy_servers': healthy,
        'degraded_servers': degraded,
        'maintenance_servers': maintenance,
        'dev_count': dev_count,
        'staging_count': staging_count,
        'prod_count': prod_count,
        'recent_deployments': recent_deployments,
        'recent_servers': servers.order_by('-created_at')[:5],
    }
    return render(request, 'infrastructure/dashboard.html', context)


@require_http_methods(["POST"])
def update_server_metrics(request, server_id):
    """API endpoint to update server metrics"""
    import json
    from django.views.decorators.csrf import csrf_exempt
    
    server = get_object_or_404(Server, id=server_id)
    
    try:
        data = json.loads(request.body)
        
        # Update server metrics
        server.cpu_usage = data.get('cpu_usage', server.cpu_usage)
        server.memory_usage = data.get('memory_usage', server.memory_usage)
        server.disk_usage = data.get('disk_usage', server.disk_usage)
        server.status = data.get('status', server.status)
        server.last_checked = timezone.now()
        server.save()
        
        # Save metric history
        Metric.objects.create(
            server=server,
            metric_type='cpu',
            value=server.cpu_usage
        )
        Metric.objects.create(
            server=server,
            metric_type='memory',
            value=server.memory_usage
        )
        
        return JsonResponse({'status': 'success', 'message': 'Metrics updated'})
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)}, status=400)


def api_servers(request):
    """REST API endpoint for servers"""
    servers = Server.objects.all()
    data = [{
        'id': s.id,
        'name': s.name,
        'ip_address': s.ip_address,
        'environment': s.environment,
        'status': s.status,
        'cpu_usage': s.cpu_usage,
        'memory_usage': s.memory_usage,
        'healthy': s.is_healthy(),
    } for s in servers]
    return JsonResponse({'servers': data})
Complete views for infrastructure management

URL Configuration

# infrastructure/urls.py
from django.urls import path
from . import views

app_name = 'infrastructure'

urlpatterns = [
    # Dashboard and listing
    path('', views.dashboard, name='dashboard'),
    path('servers/', views.server_list, name='server_list'),
    path('servers/<int:server_id>/', views.server_detail, name='server_detail'),
    
    # API endpoints
    path('api/servers/', views.api_servers, name='api_servers'),
    path('api/servers/<int:server_id>/metrics/', views.update_server_metrics, name='update_metrics'),
]

# devops_dashboard/urls.py (main project urls)
# Add to existing urlpatterns:
# path('', include('infrastructure.urls')),
# path('admin/', admin.site.urls),
URL routing configuration

Templates: Creating the Dashboard UI

<!-- infrastructure/templates/infrastructure/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>DevOps Dashboard</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
        .navbar-brand { font-weight: bold; }
        .status-badge {
            padding: 0.25rem 0.5rem;
            border-radius: 20px;
            font-size: 0.75rem;
            font-weight: 600;
        }
        .status-running { background: #d1fae5; color: #065f46; }
        .status-stopped { background: #fee2e2; color: #991b1b; }
        .status-degraded { background: #fed7aa; color: #9a3412; }
        .card-stats { border-left: 4px solid var(--bs-primary); }
        .metric-value { font-size: 2rem; font-weight: bold; }
    </style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container">
            <a class="navbar-brand" href="{% url 'infrastructure:dashboard' %}">
                🔧 DevOps Dashboard
            </a>
            <div class="navbar-nav">
                <a class="nav-link" href="{% url 'infrastructure:dashboard' %}">Dashboard</a>
                <a class="nav-link" href="{% url 'infrastructure:server_list' %}">Servers</a>
                <a class="nav-link" href="/admin/">Admin</a>
            </div>
        </div>
    </nav>
    
    <main class="container mt-4">
        {% block content %}{% endblock %}
    </main>
</body>
</html>

<!-- infrastructure/templates/infrastructure/dashboard.html -->
{% extends 'infrastructure/base.html' %}

{% block content %}
<h1 class="mb-4">Infrastructure Dashboard</h1>

<!-- Stats Cards -->
<div class="row mb-4">
    <div class="col-md-3">
        <div class="card card-stats">
            <div class="card-body">
                <h6 class="text-muted">Total Servers</h6>
                <h2 class="mb-0">{{ total_servers }}</h2>
            </div>
        </div>
    </div>
    <div class="col-md-3">
        <div class="card card-stats border-success">
            <div class="card-body">
                <h6 class="text-muted">Healthy Servers</h6>
                <h2 class="mb-0 text-success">{{ healthy_servers }}</h2>
            </div>
        </div>
    </div>
    <div class="col-md-3">
        <div class="card card-stats border-warning">
            <div class="card-body">
                <h6 class="text-muted">Degraded</h6>
                <h2 class="mb-0 text-warning">{{ degraded_servers }}</h2>
            </div>
        </div>
    </div>
    <div class="col-md-3">
        <div class="card card-stats border-danger">
            <div class="card-body">
                <h6 class="text-muted">Maintenance</h6>
                <h2 class="mb-0 text-danger">{{ maintenance_servers }}</h2>
            </div>
        </div>
    </div>
</div>

<!-- Environment Breakdown -->
<div class="row mb-4">
    <div class="col-md-4">
        <div class="card">
            <div class="card-header">
                <h5>Environment Breakdown</h5>
            </div>
            <div class="card-body">
                <ul class="list-unstyled">
                    <li>Development: <strong>{{ dev_count }}</strong></li>
                    <li>Staging: <strong>{{ staging_count }}</strong></li>
                    <li>Production: <strong>{{ prod_count }}</strong></li>
                </ul>
            </div>
        </div>
    </div>
    
    <div class="col-md-8">
        <div class="card">
            <div class="card-header">
                <h5>Recent Servers</h5>
            </div>
            <div class="card-body">
                <table class="table">
                    <thead>
                        <tr><th>Name</th><th>Environment</th><th>Status</th><th>CPU</th></tr>
                    </thead>
                    <tbody>
                        {% for server in recent_servers %}
                        <tr>
                            <td><a href="{% url 'infrastructure:server_detail' server.id %}">{{ server.name }}</a></td>
                            <td>{{ server.environment }}</td>
                            <td><span class="status-badge status-{{ server.status }}">{{ server.status }}</span></td>
                            <td>{{ server.cpu_usage }}%</td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</div>

<!-- Recent Deployments -->
<div class="card">
    <div class="card-header">
        <h5>Recent Deployments</h5>
    </div>
    <div class="card-body">
        <table class="table">
            <thead>
                <tr><th>Server</th><th>Version</th><th>Status</th><th>Deployed By</th><th>Time</th></tr>
            </thead>
            <tbody>
                {% for deployment in recent_deployments %}
                <tr>
                    <td>{{ deployment.server.name }}</td>
                    <td>{{ deployment.version }}</td>
                    <td>{{ deployment.status }}</td>
                    <td>{{ deployment.deployed_by }}</td>
                    <td>{{ deployment.started_at|timesince }} ago</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}
Django templates for the infrastructure dashboard

4. Complete Project: Infrastructure Dashboard

# management/commands/seed_data.py
# Populate database with sample data
from django.core.management.base import BaseCommand
from infrastructure.models import Server, Deployment
import random

class Command(BaseCommand):
    help = 'Seed database with sample infrastructure data'
    
    def handle(self, *args, **options):
        # Create servers
        servers_data = [
            ('web-01', '10.0.1.10', 'production', 'running', 45),
            ('web-02', '10.0.1.11', 'production', 'running', 52),
            ('db-01', '10.0.2.10', 'production', 'running', 38),
            ('cache-01', '10.0.3.10', 'staging', 'degraded', 92),
            ('dev-01', '10.0.10.10', 'dev', 'stopped', 0),
        ]
        
        for name, ip, env, status, cpu in servers_data:
            Server.objects.create(
                name=name,
                ip_address=ip,
                environment=env,
                status=status,
                cpu_usage=cpu,
                memory_usage=random.randint(20, 80),
                disk_usage=random.randint(30, 70),
            )
        
        self.stdout.write(self.style.SUCCESS('✓ Database seeded with sample data'))
Seed script for sample data
# Run the seed script
python manage.py seed_data

# Start the server
python manage.py runserver

# Visit:
# http://127.0.0.1:8000/ - Dashboard
# http://127.0.0.1:8000/servers/ - Server list
# http://127.0.0.1:8000/admin/ - Admin interface
# http://127.0.0.1:8000/api/servers/ - REST API
Running the complete application

5. Common Errors & Solutions

6. Summary Checklist

7. Next Steps

Next topic: Docker - Containerization

You'll learn to:

  • Containerize this Django application
  • Use Docker Compose for multi-container setups
  • Deploy to production with Gunicorn and Nginx

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen January 22, 2025

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment