Module 10: Kubernetes Manifests — Django + Next.js + Ingress
🎯 What You'll Build: A complete Kubernetes deployment for the DeployMart application using Django, Next.js, Services, ConfigMaps, Secrets, and Ingress.
⏱️ Time: 8 hours
📋 Prerequisites: Module 9 — Kubernetes 101
🎯 Difficulty: Intermediate
Kubernetes becomes much easier to understand when you stop thinking of it as a replacement for Docker and start thinking of it as a system that continuously manages your containers.
In Docker Compose, you describe the application you want to run:
- Start the Django backend.
- Start the Next.js frontend.
- Connect the application to a database.
- Expose ports.
- Configure environment variables.
Kubernetes uses several resource types to describe the same architecture.
For example:
- Deployment → manages application Pods.
- Pod → runs your container.
- Service → provides stable networking to Pods.
- ConfigMap → stores non-sensitive configuration.
- Secret → stores sensitive configuration.
- Ingress → routes HTTP/HTTPS traffic to Services.
- HorizontalPodAutoscaler → can automatically change the number of replicas.
A useful mental model is:
Docker packages the application. Kubernetes manages the packaged application.
The DeployMart Architecture
Our example application contains two primary application components:
1 Internet 2 │ 3 ▼ 4 ┌───────────────┐ 5 │ Ingress │ 6 │ nginx │ 7 └───────┬───────┘ 8 │ 9 ┌──────────┴──────────┐ 10 │ │ 11 /api/*│ │/* 12 ▼ ▼ 13 ┌────────────────┐ ┌────────────────┐ 14 │ Django Service │ │ Next.js Service│ 15 │ :8000 │ │ :3000 │ 16 └───────┬────────┘ └───────┬────────┘ 17 │ │ 18 ▼ ▼ 19 ┌────────────────┐ ┌────────────────┐ 20 │ Django Pods │ │ Next.js Pods │ 21 │ × 2 │ │ × 2 │ 22 └───────┬────────┘ └────────────────┘ 23 │ 24 ▼ 25 ┌────────────────┐ 26 │ PostgreSQL │ 27 │ / External DB │ 28 └────────────────┘
The important part is that users do not directly access individual Pods.
Instead:
1Browser 2 ↓ 3Ingress 4 ↓ 5Service 6 ↓ 7Pod 8 ↓ 9Container
This abstraction is one of the most important concepts in Kubernetes.
Docker Compose vs Kubernetes
If you have previously worked with Docker Compose, Kubernetes may initially look unnecessarily complicated.
Consider this simplified Compose architecture:
1services: 2 backend: 3 image: deploymart-backend:1.0 4 5 frontend: 6 image: deploymart-frontend:1.0
Kubernetes separates these responsibilities into multiple resources.
For example:
1docker-compose.yml 2 │ 3 ├── backend container 4 └── frontend container 5 6Kubernetes 7 │ 8 ├── backend Deployment 9 ├── backend Service 10 ├── frontend Deployment 11 ├── frontend Service 12 ├── ConfigMap 13 ├── Secret 14 └── Ingress
This looks more verbose, but the separation gives Kubernetes much more control over application lifecycle, networking, scaling, updates, and availability.
Kubernetes Manifest Structure
A Kubernetes manifest is normally a YAML document containing fields such as:
1apiVersion: apps/v1 2kind: Deployment 3 4metadata: 5 name: deploymart-backend 6 7spec: 8 replicas: 2
The four fields you should recognize immediately are:
| Field | Purpose |
|---|---|
apiVersion | Kubernetes API version |
kind | Resource type |
metadata | Resource name and labels |
spec | Desired configuration |
The spec is particularly important because it describes the state you want Kubernetes to maintain.
For example:
1spec: 2 replicas: 3
means:
Keep three replicas of this workload running.
If one Pod crashes, Kubernetes can create another Pod to return the application to the desired state.
Create a Namespace
Namespaces help organize resources inside a Kubernetes cluster.
For DeployMart, create a dedicated namespace:
1apiVersion: v1 2kind: Namespace 3metadata: 4 name: deploymart
Apply it:
1kubectl apply -f namespace.yaml
Verify it:
1kubectl get namespaces
You should see:
1NAME STATUS 2default Active 3deploymart Active
From this point forward, our application resources will use:
1namespace: deploymart
Create a ConfigMap
Configuration that is not sensitive can be stored in a ConfigMap.
For example:
1apiVersion: v1 2kind: ConfigMap 3metadata: 4 name: deploymart-config 5 namespace: deploymart 6 7data: 8 DJANGO_DEBUG: "False" 9 DJANGO_ALLOWED_HOSTS: "api.deploymart.local" 10 NEXT_PUBLIC_API_URL: "https://deploymart.local/api"
A ConfigMap is appropriate for values such as:
- Application mode.
- Non-sensitive URLs.
- Feature flags.
- Service names.
- Port configuration.
Do not put passwords or private API keys into a ConfigMap.
Create a Secret
Sensitive values should be placed in a Kubernetes Secret.
1apiVersion: v1 2kind: Secret 3metadata: 4 name: deploymart-secret 5 namespace: deploymart 6 7type: Opaque 8 9stringData: 10 DJANGO_SECRET_KEY: "replace-with-a-long-random-secret" 11 DATABASE_URL: "postgresql://deploymart:change-me@postgres:5432/deploymart"
For a real production system, do not commit actual credentials into Git.
Instead, use a secure secret-management system or inject secrets through your deployment pipeline.
The important distinction is:
1ConfigMap 2 ↓ 3Non-sensitive configuration 4 5Secret 6 ↓ 7Sensitive configuration
Kubernetes Secrets are not automatically equivalent to a full production secret-management solution. Treat them carefully and control access with RBAC and appropriate cluster security.
Deploy the Django Backend
Now create the backend Deployment.
1apiVersion: apps/v1 2kind: Deployment 3 4metadata: 5 name: deploymart-backend 6 namespace: deploymart 7 8spec: 9 replicas: 2 10 11 selector: 12 matchLabels: 13 app: deploymart-backend 14 15 template: 16 metadata: 17 labels: 18 app: deploymart-backend 19 20 spec: 21 containers: 22 - name: django 23 image: your-registry/deploymart-backend:1.0.0 24 25 ports: 26 - containerPort: 8000 27 28 envFrom: 29 - configMapRef: 30 name: deploymart-config 31 32 - secretRef: 33 name: deploymart-secret 34 35 readinessProbe: 36 httpGet: 37 path: /health/ 38 port: 8000 39 initialDelaySeconds: 10 40 periodSeconds: 10 41 42 livenessProbe: 43 httpGet: 44 path: /health/ 45 port: 8000 46 initialDelaySeconds: 30 47 periodSeconds: 20 48 49 resources: 50 requests: 51 cpu: "250m" 52 memory: "256Mi" 53 54 limits: 55 cpu: "500m" 56 memory: "512Mi"
There are several important concepts here.
Replicas
1replicas: 2
means Kubernetes should maintain two Django Pods.
Instead of:
1Django 2 └── 1 container
we now have:
1Django Deployment 2 │ 3 ├── Pod 1 4 │ └── Django container 5 │ 6 └── Pod 2 7 └── Django container
If one Pod becomes unavailable, Kubernetes can maintain the desired replica count.
Labels
The Pod contains:
1labels: 2 app: deploymart-backend
The Deployment selector uses:
1selector: 2 matchLabels: 3 app: deploymart-backend
These values must match.
Labels are fundamental to Kubernetes because Services, Deployments, NetworkPolicies, and other resources commonly use them to identify workloads.
Environment Variables
The backend receives configuration using:
1envFrom: 2 - configMapRef: 3 name: deploymart-config 4 5 - secretRef: 6 name: deploymart-secret
This avoids hardcoding configuration directly inside the container image.
Django Health Checks
A production-style Kubernetes application should expose a health endpoint.
For example:
1GET /health/
The endpoint should return a successful HTTP response when Django is ready to serve traffic.
The readiness probe:
1readinessProbe: 2 httpGet: 3 path: /health/ 4 port: 8000
answers:
Can this Pod currently receive application traffic?
The liveness probe:
1livenessProbe: 2 httpGet: 3 path: /health/ 4 port: 8000
answers:
Is this container still functioning, or should Kubernetes restart it?
These probes solve different problems.
1Readiness 2 ↓ 3Should traffic be sent here? 4 5Liveness 6 ↓ 7Should this container be restarted?
Create the Django Service
Pods are temporary.
Their IP addresses can change when Pods are recreated, so clients should not connect directly to Pod IP addresses.
A Service provides a stable network endpoint.
1apiVersion: v1 2kind: Service 3 4metadata: 5 name: deploymart-backend 6 namespace: deploymart 7 8spec: 9 selector: 10 app: deploymart-backend 11 12 ports: 13 - name: http 14 port: 8000 15 targetPort: 8000 16 17 type: ClusterIP
The important relationship is:
1Service selector 2 │ 3 ▼ 4app: deploymart-backend 5 │ 6 ▼ 7Matching Pods
The Service automatically maintains a set of endpoints for matching Pods.
Deploy the Next.js Frontend
Now create the frontend Deployment.
1apiVersion: apps/v1 2kind: Deployment 3 4metadata: 5 name: deploymart-frontend 6 namespace: deploymart 7 8spec: 9 replicas: 2 10 11 selector: 12 matchLabels: 13 app: deploymart-frontend 14 15 template: 16 metadata: 17 labels: 18 app: deploymart-frontend 19 20 spec: 21 containers: 22 - name: nextjs 23 image: your-registry/deploymart-frontend:1.0.0 24 25 ports: 26 - containerPort: 3000 27 28 envFrom: 29 - configMapRef: 30 name: deploymart-config 31 32 readinessProbe: 33 httpGet: 34 path: / 35 port: 3000 36 initialDelaySeconds: 10 37 periodSeconds: 10 38 39 livenessProbe: 40 httpGet: 41 path: / 42 port: 3000 43 initialDelaySeconds: 30 44 periodSeconds: 20 45 46 resources: 47 requests: 48 cpu: "200m" 49 memory: "256Mi" 50 51 limits: 52 cpu: "500m" 53 memory: "512Mi"
Now Kubernetes maintains:
1deploymart-frontend 2 │ 3 ├── Next.js Pod 1 4 └── Next.js Pod 2
Create the Next.js Service
Expose the frontend internally with a ClusterIP Service:
1apiVersion: v1 2kind: Service 3 4metadata: 5 name: deploymart-frontend 6 namespace: deploymart 7 8spec: 9 selector: 10 app: deploymart-frontend 11 12 ports: 13 - name: http 14 port: 3000 15 targetPort: 3000 16 17 type: ClusterIP
Notice that both application Services use:
1type: ClusterIP
This means they are reachable inside the Kubernetes cluster.
The Ingress will provide external HTTP/HTTPS routing.
How Kubernetes Service Discovery Works
Inside the cluster, Kubernetes provides DNS-based service discovery.
For example, the Django Service can be reached using:
1deploymart-backend.deploymart.svc.cluster.local
From another Pod in the same namespace, the shorter name usually works:
1http://deploymart-backend:8000
Similarly, the frontend Service is available internally as:
1http://deploymart-frontend:3000
This is a major difference from hardcoding container IP addresses.
Create the Ingress
Ingress provides HTTP routing from external traffic to internal Services.
A typical DeployMart routing strategy is:
1https://deploymart.example 2 │ 3 ├── /api/* ──→ Django Service 4 │ 5 └── /* ──────→ Next.js Service
Create the Ingress:
1apiVersion: networking.k8s.io/v1 2kind: Ingress 3 4metadata: 5 name: deploymart-ingress 6 namespace: deploymart 7 8spec: 9 ingressClassName: nginx 10 11 rules: 12 - host: deploymart.example 13 14 http: 15 paths: 16 - path: /api 17 pathType: Prefix 18 19 backend: 20 service: 21 name: deploymart-backend 22 23 port: 24 number: 8000 25 26 - path: / 27 pathType: Prefix 28 29 backend: 30 service: 31 name: deploymart-frontend 32 33 port: 34 number: 3000
The important point is that an Ingress resource alone does not magically create a web server.
You need an Ingress Controller installed in the cluster.
For this example, we use the NGINX Ingress Controller.
The architecture becomes:
1 Internet 2 │ 3 ▼ 4 ┌──────────────────┐ 5 │ NGINX Ingress │ 6 │ Controller │ 7 └────────┬─────────┘ 8 │ 9 ┌────────────┴────────────┐ 10 │ │ 11 /api/* /* 12 │ │ 13 ▼ ▼ 14 Backend Service Frontend Service 15 :8000 :3000 16 │ │ 17 ┌────┴────┐ ┌────┴────┐ 18 ▼ ▼ ▼ ▼ 19 Pod 1 Pod 2 Pod 1 Pod 2
Why Use Ingress Instead of NodePort?
You could expose applications using NodePort, but it is generally not the cleanest architecture for a web application.
With NodePort:
1Internet 2 ↓ 3Node IP:30080 4 ↓ 5Backend Service
You may need separate ports for different applications.
With Ingress:
1Internet 2 ↓ 3https://deploymart.example 4 ↓ 5Ingress 6 ├── /api → Django 7 └── / → Next.js
This provides host-based and path-based HTTP routing from a centralized entry point.
Complete DeployMart Manifest
Instead of maintaining many separate files while learning, you can initially combine the resources into one file.
For example:
1apiVersion: v1 2kind: Namespace 3metadata: 4 name: deploymart 5 6--- 7apiVersion: v1 8kind: ConfigMap 9metadata: 10 name: deploymart-config 11 namespace: deploymart 12 13data: 14 DJANGO_DEBUG: "False" 15 DJANGO_ALLOWED_HOSTS: "api.deploymart.example" 16 NEXT_PUBLIC_API_URL: "https://deploymart.example/api" 17 18--- 19apiVersion: v1 20kind: Secret 21metadata: 22 name: deploymart-secret 23 namespace: deploymart 24 25type: Opaque 26 27stringData: 28 DJANGO_SECRET_KEY: "replace-this-value" 29 DATABASE_URL: "postgresql://deploymart:change-me@postgres:5432/deploymart" 30 31--- 32apiVersion: apps/v1 33kind: Deployment 34metadata: 35 name: deploymart-backend 36 namespace: deploymart 37 38spec: 39 replicas: 2 40 41 selector: 42 matchLabels: 43 app: deploymart-backend 44 45 template: 46 metadata: 47 labels: 48 app: deploymart-backend 49 50 spec: 51 containers: 52 - name: django 53 image: your-registry/deploymart-backend:1.0.0 54 55 ports: 56 - containerPort: 8000 57 58 envFrom: 59 - configMapRef: 60 name: deploymart-config 61 - secretRef: 62 name: deploymart-secret 63 64 readinessProbe: 65 httpGet: 66 path: /health/ 67 port: 8000 68 initialDelaySeconds: 10 69 periodSeconds: 10 70 71 livenessProbe: 72 httpGet: 73 path: /health/ 74 port: 8000 75 initialDelaySeconds: 30 76 periodSeconds: 20 77 78--- 79apiVersion: v1 80kind: Service 81metadata: 82 name: deploymart-backend 83 namespace: deploymart 84 85spec: 86 selector: 87 app: deploymart-backend 88 89 ports: 90 - port: 8000 91 targetPort: 8000 92 93 type: ClusterIP 94 95--- 96apiVersion: apps/v1 97kind: Deployment 98metadata: 99 name: deploymart-frontend 100 namespace: deploymart 101 102spec: 103 replicas: 2 104 105 selector: 106 matchLabels: 107 app: deploymart-frontend 108 109 template: 110 metadata: 111 labels: 112 app: deploymart-frontend 113 114 spec: 115 containers: 116 - name: nextjs 117 image: your-registry/deploymart-frontend:1.0.0 118 119 ports: 120 - containerPort: 3000 121 122 envFrom: 123 - configMapRef: 124 name: deploymart-config 125 126 readinessProbe: 127 httpGet: 128 path: / 129 port: 3000 130 131 livenessProbe: 132 httpGet: 133 path: / 134 port: 3000 135 136--- 137apiVersion: v1 138kind: Service 139metadata: 140 name: deploymart-frontend 141 namespace: deploymart 142 143spec: 144 selector: 145 app: deploymart-frontend 146 147 ports: 148 - port: 3000 149 targetPort: 3000 150 151 type: ClusterIP 152 153--- 154apiVersion: networking.k8s.io/v1 155kind: Ingress 156metadata: 157 name: deploymart-ingress 158 namespace: deploymart 159 160spec: 161 ingressClassName: nginx 162 163 rules: 164 - host: deploymart.example 165 166 http: 167 paths: 168 - path: /api 169 pathType: Prefix 170 171 backend: 172 service: 173 name: deploymart-backend 174 port: 175 number: 8000 176 177 - path: / 178 pathType: Prefix 179 180 backend: 181 service: 182 name: deploymart-frontend 183 port: 184 number: 3000
The --- separator allows multiple Kubernetes resources to exist in a single YAML file.
Recommended Project Structure
Once the application becomes larger, separate manifests are easier to maintain.
A practical structure is:
1k8s/ 2├── namespace.yaml 3├── configmap.yaml 4├── secret.yaml 5├── backend-deployment.yaml 6├── backend-service.yaml 7├── frontend-deployment.yaml 8├── frontend-service.yaml 9├── ingress.yaml 10└── hpa.yaml
You can also organize them into directories:
1k8s/ 2├── base/ 3│ ├── namespace.yaml 4│ ├── configmap.yaml 5│ ├── backend.yaml 6│ ├── frontend.yaml 7│ └── ingress.yaml 8│ 9└── overlays/ 10 ├── development/ 11 └── production/
This structure becomes particularly useful when you later learn Kustomize or Helm.
Apply the Manifests
Assuming your files are inside the k8s directory:
1kubectl apply -f k8s/
Kubernetes should report resources being created or configured.
Check the namespace:
1kubectl get all -n deploymart
You should see resources similar to:
1NAME READY 2pod/deploymart-backend-xxxxxxxxx-xxxxx 1/1 3pod/deploymart-backend-xxxxxxxxx-xxxxx 1/1 4pod/deploymart-frontend-xxxxxxxxx-xxxxx 1/1 5pod/deploymart-frontend-xxxxxxxxx-xxxxx 1/1
Inspect Deployments
Run:
1kubectl get deployments -n deploymart
Expected result:
1NAME READY UP-TO-DATE AVAILABLE 2deploymart-backend 2/2 2 2 3deploymart-frontend 2/2 2 2
The important value is:
12/2
It means two desired replicas are currently ready.
Inspect Services
Run:
1kubectl get services -n deploymart
You should see:
1NAME TYPE CLUSTER-IP 2deploymart-backend ClusterIP 10.x.x.x 3deploymart-frontend ClusterIP 10.x.x.x
These ClusterIP addresses are internal Kubernetes networking addresses.
You normally do not need to manually manage them.
Inspect Ingress
Run:
1kubectl get ingress -n deploymart
You should see your Ingress resource.
For more information:
1kubectl describe ingress deploymart-ingress -n deploymart
This is one of the most useful commands when debugging HTTP routing.
Inspect Pods
Run:
1kubectl get pods -n deploymart -o wide
The -o wide option gives additional information such as:
- Pod IP.
- Node.
- Readiness.
- Status.
For a specific Pod:
1kubectl describe pod <pod-name> -n deploymart
Read Application Logs
Django logs:
1kubectl logs deployment/deploymart-backend -n deploymart
Next.js logs:
1kubectl logs deployment/deploymart-frontend -n deploymart
For continuous logs:
1kubectl logs -f deployment/deploymart-backend -n deploymart
This is similar to:
1docker compose logs -f backend
but Kubernetes provides the logs through the workload resources.
Debug a Failing Pod
If a Pod is not starting:
1kubectl get pods -n deploymart
If you see:
1CrashLoopBackOff
inspect it:
1kubectl describe pod <pod-name> -n deploymart
Then check logs:
1kubectl logs <pod-name> -n deploymart
If the container restarted and you need logs from the previous instance:
1kubectl logs <pod-name> -n deploymart --previous
A useful debugging sequence is:
1Pod status 2 ↓ 3kubectl describe pod 4 ↓ 5Events 6 ↓ 7kubectl logs 8 ↓ 9Application configuration 10 ↓ 11Service connectivity
Test the Backend Service Internally
Before debugging Ingress, test the Service itself.
Create a temporary Pod:
1kubectl run curl-test \ 2 --rm \ 3 -it \ 4 --image=curlimages/curl \ 5 -n deploymart \ 6 -- sh
Inside the temporary container:
1curl http://deploymart-backend:8000/health/
If the backend responds successfully, the Service and Pods are probably working.
You can also test the frontend:
1curl http://deploymart-frontend:3000
This gives you a powerful debugging principle:
Test one layer at a time.
1Container 2 ↓ 3Pod 4 ↓ 5Service 6 ↓ 7Ingress 8 ↓ 9Internet
Do not immediately assume that an Ingress problem is an application problem.
Add Horizontal Pod Autoscaling
Once the basic deployment works, Kubernetes can scale the backend based on resource utilization.
Example:
1apiVersion: autoscaling/v2 2kind: HorizontalPodAutoscaler 3 4metadata: 5 name: deploymart-backend 6 namespace: deploymart 7 8spec: 9 scaleTargetRef: 10 apiVersion: apps/v1 11 kind: Deployment 12 name: deploymart-backend 13 14 minReplicas: 2 15 maxReplicas: 10 16 17 metrics: 18 - type: Resource 19 20 resource: 21 name: cpu 22 23 target: 24 type: Utilization 25 averageUtilization: 70
The architecture changes from:
1Backend 2 ├── Pod 3 └── Pod
to something that can dynamically become:
1Backend 2 ├── Pod 3 ├── Pod 4 ├── Pod 5 ├── Pod 6 └── ...
depending on workload and the configured autoscaling rules.
Remember that HPA requires the cluster to have the necessary resource metrics available.
Kubernetes Rolling Updates
One major advantage of Deployments is controlled application updates.
Suppose the current image is:
1image: your-registry/deploymart-backend:1.0.0
You build a new version:
11.1.0
Then update the Deployment:
1image: your-registry/deploymart-backend:1.1.0
Apply it:
1kubectl apply -f backend-deployment.yaml
Watch the rollout:
1kubectl rollout status deployment/deploymart-backend -n deploymart
Kubernetes can gradually replace old Pods with new Pods.
Conceptually:
1Version 1.0 2Pod 1 ──────────────→ removed 3Pod 2 ──────────────→ running 4 5Version 1.1 6Pod 3 ──────────────→ running 7Pod 4 ──────────────→ running
This is one reason Deployments are preferable to manually creating Pods.
Roll Back a Deployment
If the new release is broken:
1kubectl rollout undo deployment/deploymart-backend \ 2 -n deploymart
Check the rollout:
1kubectl rollout status deployment/deploymart-backend \ 2 -n deploymart
View rollout history:
1kubectl rollout history deployment/deploymart-backend \ 2 -n deploymart
This gives you a basic deployment safety mechanism.
Common Kubernetes Mistakes
Mistake 1: Service Selector Does Not Match Pod Labels
Pod:
1labels: 2 app: deploymart-backend
Service:
1selector: 2 app: backend
These do not match.
Result:
1Service 2 ↓ 3No matching endpoints
Always verify:
1kubectl get endpoints -n deploymart
Mistake 2: Confusing port and targetPort
Consider:
1ports: 2 - port: 8000 3 targetPort: 8000
port is the Service port.
targetPort is the port exposed by the application container.
Conceptually:
1Client 2 ↓ 3Service :8000 4 ↓ 5Pod :8000
They can be different:
1port: 80 2targetPort: 8000
which means:
1Service :80 2 ↓ 3Pod :8000
Mistake 3: Putting Secrets in Git
Avoid:
1stringData: 2 DATABASE_PASSWORD: "my-real-password"
inside a public Git repository.
Even if the repository is private, credentials should be handled carefully.
Rotate credentials immediately if they are accidentally committed.
Mistake 4: Using latest
Avoid relying on:
1image: deploymart-backend:latest
for controlled production deployments.
Prefer immutable version tags:
1image: your-registry/deploymart-backend:1.2.0
or, for stronger reproducibility, pin images by digest.
This makes it much easier to determine exactly which application version is running.
Mistake 5: Forgetting the Ingress Controller
Creating:
1kind: Ingress
does not by itself provide an HTTP proxy.
You need a compatible Ingress Controller running in the cluster.
Always check:
1kubectl get pods -A
and verify that your chosen Ingress Controller is installed and healthy.
Production Improvements
The example above intentionally focuses on the Kubernetes fundamentals.
A production deployment would normally need additional considerations.
Database
Do not automatically place a production PostgreSQL database inside the same simple application manifest just because it is convenient for learning.
For production, consider:
- Managed PostgreSQL.
- Automated backups.
- High availability.
- Persistent storage.
- Database monitoring.
- Disaster recovery.
- Credential rotation.
The application can connect to a managed database through its connection configuration.
TLS
Production websites should use HTTPS.
Your Ingress can later be configured with TLS:
1spec: 2 tls: 3 - hosts: 4 - deploymart.example 5 secretName: deploymart-tls
The exact certificate-management approach depends on your cluster and certificate tooling.
Resource Requests and Limits
Resources should be specified deliberately:
1resources: 2 requests: 3 cpu: "250m" 4 memory: "256Mi" 5 6 limits: 7 cpu: "500m" 8 memory: "512Mi"
Requests help Kubernetes schedule workloads.
Limits place an upper bound on resource usage according to Kubernetes resource semantics.
Security Context
For production workloads, also consider:
- Running containers as non-root.
- Dropping unnecessary Linux capabilities.
- Read-only root filesystems where practical.
- Seccomp profiles.
- NetworkPolicies.
- Minimal container images.
- RBAC with least privilege.
Kubernetes deployment is not just about making containers run; it is also about running them safely.
Complete Request Flow
Let's follow a real request.
A user visits:
1https://deploymart.example/products
The request travels through:
1Browser 2 │ 3 ▼ 4DNS 5 │ 6 ▼ 7Load Balancer 8 │ 9 ▼ 10Ingress Controller 11 │ 12 ▼ 13Ingress Rule 14 │ 15 ├── /api → Django 16 │ 17 └── / → Next.js 18 │ 19 ▼ 20 Next.js Service 21 │ 22 ┌─────┴─────┐ 23 ▼ ▼ 24 Pod 1 Pod 2
For an API request:
1https://deploymart.example/api/products 2 │ 3 ▼ 4 Ingress Controller 5 │ 6 ▼ 7 Backend Service 8 │ 9 ┌──────┴──────┐ 10 ▼ ▼ 11 Django Django 12 Pod 1 Pod 2
This is the core Kubernetes networking model you should understand before moving to more advanced topics.
Docker Compose to Kubernetes Mental Model
If you already understand Docker Compose, use this mapping:
| Docker Compose | Kubernetes |
|---|---|
| Service | Deployment + Service |
| Container | Container inside Pod |
| Replica count | Deployment replicas |
| Environment variables | ConfigMap / Secret |
| Port mapping | Service |
| Reverse proxy | Ingress Controller |
| Healthcheck | Readiness/Liveness Probe |
| Restart behavior | Kubernetes controller |
| Scaling | Deployment / HPA |
.env configuration | ConfigMap / Secret |
docker compose logs | kubectl logs |
The mapping is not perfectly one-to-one, but it is a useful learning bridge.
Essential Commands
Keep these commands available while working with the module:
1kubectl get nodes
Check cluster nodes.
1kubectl get all -n deploymart
View the application's major resources.
1kubectl get pods -n deploymart
View Pods.
1kubectl get deployments -n deploymart
View Deployments.
1kubectl get services -n deploymart
View Services.
1kubectl get ingress -n deploymart
View Ingress resources.
1kubectl describe pod <pod-name> -n deploymart
Inspect a Pod.
1kubectl logs <pod-name> -n deploymart
Read application logs.
1kubectl apply -f k8s/
Apply manifests.
1kubectl delete -f k8s/
Remove resources described by the manifests.
Use the delete command carefully, especially when working against shared or production clusters.
Module 10 Checkpoint
Before moving to the next module, you should be able to explain the following architecture without looking at your notes:
1 Internet 2 │ 3 ▼ 4 Ingress 5 / \ 6 / \ 7 /api / 8 ▼ ▼ 9 Django Service Next.js Service 10 │ │ 11 ┌────┴────┐ ┌───┴────┐ 12 ▼ ▼ ▼ ▼ 13 Django Django Next.js Next.js 14 Pod Pod Pod Pod
You should also understand:
- Why Pods should not normally be accessed directly.
- Why a Service provides stable networking.
- Why Deployments manage replicas.
- Why ConfigMaps exist.
- Why Secrets should contain sensitive configuration.
- How Ingress routes HTTP traffic.
- How readiness and liveness probes differ.
- How to inspect Pods and logs.
- How to perform a rolling update.
- How to roll back a Deployment.
- How HPA can scale workloads.
Hands-On Challenge
Create the DeployMart Kubernetes application yourself.
Your final directory should contain:
1k8s/ 2├── namespace.yaml 3├── configmap.yaml 4├── secret.yaml 5├── backend-deployment.yaml 6├── backend-service.yaml 7├── frontend-deployment.yaml 8├── frontend-service.yaml 9├── ingress.yaml 10└── hpa.yaml
Then run:
1kubectl apply -f k8s/
Verify:
1kubectl get all -n deploymart 2kubectl get ingress -n deploymart
Then test the application through the Ingress.
Finally, deliberately scale the backend:
1kubectl scale deployment deploymart-backend \ 2 --replicas=4 \ 3 -n deploymart
Verify:
1kubectl get pods -n deploymart
You should now see four Django Pods.
Scale it back:
1kubectl scale deployment deploymart-backend \ 2 --replicas=2 \ 3 -n deploymart
This exercise demonstrates one of Kubernetes' central ideas:
You declare the desired state, and Kubernetes works continuously to make the cluster match that state.
What You Learned
In this module, you moved from running individual containers to describing an entire application platform.
You created:
1Namespace 2 ↓ 3ConfigMap + Secret 4 ↓ 5Deployments 6 ↓ 7Pods 8 ↓ 9Services 10 ↓ 11Ingress 12 ↓ 13External HTTP traffic
The most important concept is not memorizing YAML syntax.
It is understanding the relationships between Kubernetes resources:
1Deployment 2 ↓ 3creates/manages 4 ↓ 5Pods 6 ↓ 7selected by 8 ↓ 9Service 10 ↓ 11routed by 12 ↓ 13Ingress
Once this architecture becomes intuitive, Kubernetes manifests stop looking like random YAML and start looking like a structured description of your application.
Module 10 Final Checkpoint
1kubectl get nodes 2 3kubectl apply -f k8s/ 4 5kubectl get all -n deploymart 6 7kubectl get ingress -n deploymart 8 9kubectl get pods -n deploymart 10 11kubectl get services -n deploymart 12 13kubectl rollout status deployment/deploymart-backend \ 14 -n deploymart 15 16kubectl rollout status deployment/deploymart-frontend \ 17 -n deploymart
If the Pods are healthy, Services have endpoints, the Ingress Controller is available, and requests reach both Django and Next.js, your DeployMart Kubernetes deployment is working.
Next: In Module 11, take this application further by learning persistent storage, PostgreSQL, Redis, StatefulSets, and production data management in Kubernetes.