Understand what developers build and deploy - REST APIs, HTTP, Nginx, databases, environment variables, health checks, and microservices - from an infrastructure perspective, not a developer one.
Most DevOps roadmaps go straight from Docker to Kubernetes. They skip something critical — what is actually running inside those containers? When you manage the platform that hundreds of developers deploy onto, you will constantly deal with questions like: * Why is Kubernetes killing this pod every 30 seconds? * Why does the service return 502 when traffic spikes? * Why does this app crash when we move it to a new environment? * What does this health check endpoint actually check? You cannot answer any of these without understanding how applications work — not as a developer, but as the infrastructure person responsible for running them reliably. This module does not make you a software engineer. It makes you an **infrastructure consumer** — someone who understands applications well enough to operate, debug, and build platforms for them. ### What You Will Learn * How the internet works at the HTTP level — requests, responses, status codes * What a REST API is and how to interact with one using curl * How Nginx sits in front of applications and why it exists * What databases do and how applications connect to them * Why environment variables exist and how Kubernetes manages them * What health check endpoints are and why Kubernetes depends on them * The difference between a monolith and microservices — and why it matters for your platform ---
Before APIs, Nginx, and databases — you need to understand the single concept that ties everything together: **the request-response cycle**. Every interaction on the internet follows the same pattern. A client wants something. It sends a request. A server processes it. The server sends back a response. ``` You open Swiggy in your browser | v Browser sends HTTP request to Swiggy's server GET https://swiggy.com/api/restaurants?city=bangalore | v Swiggy's server looks up restaurants in their database | v Server sends back HTTP response Status: 200 OK Body: [{"name": "Burger King", "rating": 4.2}, ...] | v Browser displays the restaurant list ``` This happens thousands of times per second across millions of users. Your platform is what keeps all of this running. ### The Three Parts of Every Web Interaction **The Client** — anything that makes requests. A browser, a mobile app, another microservice, a cron job, a CI/CD pipeline calling an API. The client does not care how the server works internally — it just sends a request and expects a response. **The Server** — receives the request, processes it (reads from database, runs business logic, calls other services), and sends back a response. In modern architectures this is almost always a containerised application running on your Kubernetes cluster. **The Protocol** — HTTP (HyperText Transfer Protocol) is the language both sides speak. It defines how requests and responses are structured, what status codes mean, and how data is transmitted. ---
HTTP is not just for websites. Every API call, every service-to-service communication, every webhook, every health check — all of it is HTTP. As a Platform Engineer you will read HTTP logs, debug HTTP errors, and configure Nginx to handle HTTP traffic every single day. ### The Anatomy of an HTTP Request Every HTTP request has four parts: ``` METHOD /path HTTP/1.1 Host: api.razorpay.com Content-Type: application/json Authorization: Bearer eyJhbGc... { "amount": 50000, "currency": "INR" } ``` * **Method** — what action the client wants to perform (GET, POST, PUT, PATCH, DELETE) * **Path** — which resource the client wants to interact with (/api/payments, /users/123) * **Headers** — metadata about the request (content type, authentication, caching) * **Body** — data sent with the request (only for POST, PUT, PATCH — not GET or DELETE) ### HTTP Methods — What Each One Does | Method | What It Does | Has Body? | Real Example | |:---|:---|:---|:---| | GET | Read/fetch a resource | No | Fetch user profile | | POST | Create a new resource | Yes | Place a new order | | PUT | Replace a resource completely | Yes | Update entire user profile | | PATCH | Update part of a resource | Yes | Change only the email address | | DELETE | Remove a resource | No | Delete a payment method | > 💡 **Tip:** GET requests should never change data on the server. They are read-only by definition. If you see a GET request causing data changes, that is a bad API design. **Idempotent** means you can call it multiple times and get the same result. GET, PUT, and DELETE are idempotent. POST is not — clicking "Place Order" three times creates three orders. ### HTTP Status Codes — The Most Important Thing to Know Every response has a three-digit status code. This is how the server communicates what happened. As a Platform Engineer you will read these in Nginx logs, Kubernetes events, and monitoring dashboards constantly. ``` 2xx — Success 200 OK Request succeeded, data returned 201 Created New resource was created (response to POST) 204 No Content Success but nothing to return (response to DELETE) 3xx — Redirection 301 Moved Permanently Resource has a new permanent URL 302 Found Temporary redirect 304 Not Modified Cached version is still valid 4xx — Client Error (the caller did something wrong) 400 Bad Request Malformed request, invalid parameters 401 Unauthorized Not authenticated — login required 403 Forbidden Authenticated but not allowed to do this 404 Not Found Resource does not exist 429 Too Many Requests Rate limit exceeded 5xx — Server Error (the server/platform did something wrong) 500 Internal Server Error Unhandled exception in application code 502 Bad Gateway Nginx could not reach the application 503 Service Unavailable Application is down or overloaded 504 Gateway Timeout Application took too long to respond ``` > 📌 **Remember:** When you see 502 or 504 in Nginx logs, the problem is almost never Nginx itself. The application behind Nginx is either crashed (502) or too slow (504). Your first debugging step is always to check the application pod logs, not the Nginx configuration. > 🔴 **Common Mistake:** Confusing 401 and 403. A 401 means "I don't know who you are — please authenticate." A 403 means "I know who you are but you're not allowed here." If an API returns 403 in staging but 200 in production, the issue is RBAC or permission configuration — not authentication. ### HTTP Headers — The Metadata You Ignore Until Something Breaks Headers carry context that both client and server need. ```bash ## Common request headers Content-Type: application/json ## tells server what format the body is in Authorization: Bearer eyJhbGc... ## authentication token Accept: application/json ## tells server what format you want back X-Request-ID: abc-123-def ## unique ID for tracing this request ## Common response headers Content-Type: application/json ## format of the response body Cache-Control: max-age=3600 ## how long clients can cache this response X-RateLimit-Remaining: 47 ## API rate limit tracking Location: /api/orders/12345 ## URL of newly created resource (after 201) ``` As a Platform Engineer you will configure Nginx to add, remove, and forward headers. The most important one to understand is `X-Real-IP` — when Nginx sits in front of your app, the app sees Nginx's IP, not the user's. You must configure Nginx to pass the real client IP in the `X-Real-IP` header so your application can do things like rate limiting and audit logging correctly. ---
REST (Representational State Transfer) is an architectural style for designing APIs. It is not a protocol or a tool — it is a set of conventions that, when followed, make APIs predictable and easy to use. Every major platform in India — Razorpay, Swiggy, Zerodha, PhonePe — exposes REST APIs. Your Kubernetes platform exposes one too. The Kubernetes API server is itself a REST API. ### The Core Idea — Resources and Actions In REST, everything is a **resource** — a user, an order, a payment, a pod. Each resource has a URL (called an endpoint). You interact with resources using HTTP methods. ``` Resource: Users GET /api/v1/users → list all users POST /api/v1/users → create a new user GET /api/v1/users/123 → get user with ID 123 PUT /api/v1/users/123 → replace user 123 completely PATCH /api/v1/users/123 → update part of user 123 DELETE /api/v1/users/123 → delete user 123 Resource: Orders GET /api/v1/orders → list all orders POST /api/v1/orders → create a new order GET /api/v1/orders/456 → get order 456 Resource: Kubernetes Pods (yes, Kubernetes is a REST API!) GET /api/v1/namespaces/production/pods → list pods GET /api/v1/namespaces/production/pods/my-pod → get specific pod DELETE /api/v1/namespaces/production/pods/my-pod → delete pod ``` Notice the pattern: the URL identifies WHAT you want, the HTTP method identifies WHAT TO DO with it. ### REST is Stateless The most important REST principle for Platform Engineers to understand is **statelessness**. Every request must contain all the information the server needs to process it. The server does not remember previous requests. There is no session state stored on the server between calls. This is why REST APIs scale so well. Any of your 10 application pods can handle any request from any user. You can add or remove pods freely. Load balancers can route requests to any pod. Nothing breaks because no server is storing session state. ``` Stateful (bad for scaling): Request 1: "Login" → server stores session in memory: user=rahul Request 2: "Get my orders" → must go to SAME server (it knows user=rahul) Pod crashes → user loses session → angry user Stateless (REST): Request 1: "Login" → server returns a JWT token to the client Request 2: "Get my orders" + JWT token → any pod can verify the token Pod crashes → request retried to another pod → no data lost ``` ### JSON — How APIs Exchange Data JSON (JavaScript Object Notation) is the standard format for REST API data. It is human-readable text that represents structured data. ```json { "order_id": "ORD-2024-001", "user": { "id": 12345, "name": "Rahul Sharma", "email": "rahul@example.com" }, "items": [ { "name": "Butter Chicken", "quantity": 2, "price": 350 } ], "total": 700, "status": "delivered", "delivered_at": "2024-01-15T14:30:00Z" } ``` JSON supports: strings (`"text"`), numbers (`700`), booleans (`true`/`false`), null, arrays (`[...]`), and objects (`{...}`). That is the entire data model. ### Using curl to Call APIs — Your Most Important Debugging Tool `curl` is a command-line tool for making HTTP requests. Every Platform Engineer uses it constantly — to test endpoints, debug issues, verify deployments, and check health. ```bash ## GET request — fetch data curl https://api.example.com/v1/users/123 ## GET with headers — authenticate curl -H "Authorization: Bearer your-token-here" \ https://api.example.com/v1/users/123 ## POST request — create data (send JSON body) curl -X POST \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-token-here" \ -d '{"name": "Priya", "email": "priya@example.com"}' \ https://api.example.com/v1/users ## See response headers (very useful for debugging) curl -i https://api.example.com/v1/users/123 ## See full request and response details curl -v https://api.example.com/v1/users/123 ## Test a Kubernetes pod's health check directly kubectl exec -n production deploy/payment-service -- \ curl -s localhost:8080/health ## Test if a service is reachable from inside the cluster kubectl run debug --image=curlimages/curl --rm -it -- \ curl http://payment-service.production.svc.cluster.local/health ``` > 💡 **Tip:** `curl -v` is your best friend when debugging API issues. It shows you the full request including all headers, and the full response including status code and headers. Most "it's not working" problems become obvious when you can see the actual HTTP conversation. ### API Versioning — Why /v1/ Matters You will see `/v1/`, `/v2/` in every production API URL. This is versioning — it lets the API evolve without breaking existing clients. ``` https://api.razorpay.com/v1/payments ← original API https://api.razorpay.com/v2/payments ← new version with different response format ``` Both exist simultaneously. Old clients use v1. New clients use v2. Nobody breaks. When you are writing Kubernetes ingress rules or configuring reverse proxies, you need to preserve these version paths correctly — routing `/v1/` to the old service and `/v2/` to the new one. ---
Nginx is one of the most important tools in a Platform Engineer's stack. It sits between the internet and your applications, handling traffic before it ever reaches your application code. Understanding Nginx is not about memorising configuration syntax. It is about understanding why it exists and what problem it solves. ### The Problem Nginx Solves Without a reverse proxy, clients talk directly to your application: ``` Client → Application (port 3000) ``` This is a problem because: * The application must handle SSL/TLS termination (expensive, complex) * One application instance must handle all traffic alone * The application's internal port and internal structure is exposed to the internet * No central place to add authentication, rate limiting, or logging * Each application must serve static files itself (inefficient) With Nginx as a reverse proxy: ``` Client → Nginx (port 443, handles SSL) → Application (port 3000, plain HTTP) ``` Nginx handles everything at the edge. Your application just handles business logic. ### How Nginx Works as a Reverse Proxy ``` +----------+ HTTPS +----------+ HTTP +-------------+ | Client | ------------> | Nginx | ------------> | Application | | Browser | port 443 | | port 3000 | (Node.js, | | Mobile | | Terminates| | Python, | | curl | | TLS here | | Go, etc.) | +----------+ +----------+ +-------------+ | Forwards request with added headers: X-Real-IP: client's IP X-Forwarded-Proto: https ``` The client never communicates directly with the application. From the client's perspective, Nginx IS the application. This is called a **reverse proxy** — it proxies requests on behalf of the server, not the client. ### Basic Nginx Configuration Nginx configuration has a clear hierarchy: `http` → `server` → `location`. ```nginx ## /etc/nginx/conf.d/myapp.conf server { listen 80; server_name api.swiggy.com; ## Forward all requests to the application running on port 3000 location / { proxy_pass http://localhost:3000; ## Pass real client IP to the application proxy_set_header X-Real-IP $remote_addr; ## Tell application the original protocol (https not http) proxy_set_header X-Forwarded-Proto $scheme; ## Pass original host header proxy_set_header Host $host; } } ``` That is the minimum configuration for a working reverse proxy. The `proxy_pass` directive tells Nginx where to forward the request. The `proxy_set_header` directives add context headers the application needs. ### Routing Different Paths to Different Services In a microservices architecture, one Nginx instance routes different URL paths to different backend services. This is one of Nginx's most common patterns in production. ```nginx server { listen 80; server_name api.zomato.com; ## User service handles /users/ requests location /users/ { proxy_pass http://user-service:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } ## Order service handles /orders/ requests location /orders/ { proxy_pass http://order-service:8002; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } ## Restaurant service handles /restaurants/ requests location /restaurants/ { proxy_pass http://restaurant-service:8003; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } ## Serve static files (images, CSS, JS) directly from disk ## Never let application code serve static files location /static/ { root /var/www/zomato; expires 30d; add_header Cache-Control "public, immutable"; } } ``` From the client's perspective, there is one API at api.zomato.com. They have no idea it is backed by three separate services. Nginx handles all the routing transparently. ### Load Balancing — Distributing Traffic Across Multiple Pods When you have multiple instances of an application (which you always should in production), Nginx distributes requests across them. This is load balancing. ```nginx ## Define a group of backend servers (called an upstream) upstream payment_service { ## Round-robin by default — each request goes to the next server server 10.0.1.10:8080; server 10.0.1.11:8080; server 10.0.1.12:8080; } server { listen 80; location / { ## Nginx picks one of the three servers for each request proxy_pass http://payment_service; } } ``` Nginx load balancing methods: * **Round Robin** — request 1 goes to server 1, request 2 to server 2, request 3 to server 3, request 4 to server 1 again. Default. * **Least Connections** — sends each request to the server with fewest active connections. Better for long-running requests. * **IP Hash** — same client always goes to the same server. Useful when the application is stateful (though you should avoid stateful applications). > 📌 **Remember:** In Kubernetes you rarely configure Nginx load balancing manually. Kubernetes Services handle load balancing across pods automatically. But understanding how it works under the hood helps you debug issues when traffic is not distributing evenly. ### SSL Termination — Why Applications Run on Plain HTTP You will notice that in every Nginx configuration, the application runs on plain HTTP (no SSL) even though users access it over HTTPS. This is called **SSL termination** — Nginx handles the expensive SSL handshake and decryption, then forwards plain HTTP to the application. ``` Client ----[HTTPS, encrypted]----> Nginx ----[HTTP, plain]----> Application TLS handled here No TLS needed here ``` Benefits of SSL termination at Nginx: * Application code does not need SSL libraries or certificates * SSL is computationally expensive — offloading it to Nginx frees application CPU * Certificate rotation happens at Nginx without touching application code * All SSL configuration in one place In Kubernetes, this is usually handled by the Ingress controller — another Nginx (or similar) that handles SSL at the cluster edge. ### Nginx in Kubernetes — The Ingress Controller In Kubernetes, the standard Nginx setup is an **Ingress controller** — a cluster-wide Nginx that handles all incoming traffic for all services. ```yaml ## Kubernetes Ingress resource — tells the Nginx Ingress controller what to route apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: production spec: rules: - host: api.hotstar.com http: paths: - path: /v1/content pathType: Prefix backend: service: name: content-service port: number: 8080 - path: /v1/users pathType: Prefix backend: service: name: user-service port: number: 8080 ``` You write Ingress resources. The Nginx Ingress controller reads them and automatically updates its configuration. No manual Nginx config editing required. ---
Every application stores data somewhere. Understanding how databases work from an operations perspective helps you debug connection issues, understand resource usage, and make the right infrastructure decisions. ### Two Families of Databases **Relational Databases (SQL)** — store data in structured tables with rows and columns. Data has a defined schema. Related data is linked through foreign keys. Strong consistency guarantees. Examples: PostgreSQL, MySQL, Amazon RDS. ``` users table: id | name | email | created_at ------|---------------|----------------------|------------------ 1 | Rahul Sharma | rahul@example.com | 2024-01-15 2 | Priya Patel | priya@example.com | 2024-01-16 orders table: id | user_id | amount | status | created_at ------|---------|--------|-----------|------------------ 101 | 1 | 350 | delivered | 2024-01-15 102 | 2 | 700 | pending | 2024-01-16 ``` **Non-Relational Databases (NoSQL)** — store data in flexible formats (documents, key-value pairs, graphs). No fixed schema. Horizontally scalable. Eventual consistency. Examples: MongoDB (documents), Redis (key-value), Cassandra (wide-column). ``` MongoDB document (looks like JSON): { "_id": "ORD-101", "user": { "id": 1, "name": "Rahul Sharma" }, "items": [{"name": "Butter Chicken", "qty": 2, "price": 350}], "total": 700, "status": "delivered" } ``` ### When to Use Which | Situation | Use SQL | Use NoSQL | |:---|:---|:---| | Financial transactions, payments | ✅ | ❌ | | User profiles, structured data | ✅ | ✅ | | High-speed caching | ❌ | ✅ Redis | | Product catalogues, flexible schemas | ✅ | ✅ | | Real-time analytics, logs | ❌ | ✅ | | Complex queries across related data | ✅ | ❌ | ### Connection Pooling — The Most Important Database Concept for DevOps Opening a database connection is expensive. It involves network negotiation, authentication, and resource allocation on the database server. If your application opens a new connection for every request and closes it when done, at 1000 requests per second you are opening and closing 1000 connections per second. The database collapses. **Connection pooling** solves this by maintaining a pool of open connections that requests share. ``` Without connection pooling: Request 1 arrives → open connection → query → close connection (100ms overhead) Request 2 arrives → open connection → query → close connection (100ms overhead) 1000 requests/sec → 1000 connection open/close per second → database dies With connection pooling: Application starts → opens 20 connections, keeps them open in a pool Request 1 arrives → borrows connection from pool → query → returns to pool (1ms overhead) Request 2 arrives → borrows connection from pool → query → returns to pool 1000 requests/sec → 20 connections handle everything → database happy ``` When you see errors like `connection pool exhausted` or `too many connections` in your application logs — this is the problem. Either the pool is too small, connections are being leaked (not returned after use), or a slow query is holding connections longer than expected. ### How Applications Connect to Databases Applications never hardcode database credentials. They read them from environment variables at runtime (more on this in the next section). A typical database connection looks like: ```python ## Python application connecting to PostgreSQL import psycopg2 import os ## Read all connection details from environment variables conn = psycopg2.connect( host=os.environ["DB_HOST"], ## database server hostname port=os.environ["DB_PORT"], ## port (PostgreSQL default: 5432) database=os.environ["DB_NAME"], ## which database to use user=os.environ["DB_USER"], ## database username password=os.environ["DB_PASSWORD"] ## database password ) ``` The application does not know or care whether it is running in development, staging, or production. The environment variables tell it where to connect. This is how the same container image runs in all environments. ### Redis — The Cache That Every Production App Uses Redis is an in-memory key-value store. It is not a replacement for your main database — it is a cache that sits in front of it. ``` Without Redis: User requests restaurant list → Application queries PostgreSQL (100ms) → Returns results 1000 users make same request → 1000 database queries per second → database under pressure With Redis: User 1 requests restaurant list → Check Redis cache: not found (cache miss) → Query PostgreSQL (100ms) → Store result in Redis with 5-minute expiry → Return results Users 2-1000 request same list within 5 minutes → Check Redis cache: found (cache hit) → Return cached results (1ms — 100x faster) → Database gets 1 query instead of 1000 ``` Common Redis use cases in production: * **Session storage** — store authenticated user sessions (stateless apps need this) * **Query caching** — cache expensive database query results * **Rate limiting** — track API request counts per user per minute * **Job queues** — queue background jobs (send emails, process images) * **Real-time leaderboards** — sorted sets for rankings and scores > ⚠️ **Security:** Redis by default has no authentication and no encryption. In production, always configure Redis with a password (`requirepass`), bind it to private network interfaces only (never expose Redis to the public internet), and use TLS for connections. A misconfigured Redis accessible from the internet is one of the most common sources of data breaches. ---
Most DevOps roadmaps go straight from Docker to Kubernetes. They skip something critical — what is actually running insi...
Before APIs, Nginx, and databases — you need to understand the single concept that ties everything together: the request...
HTTP is not just for websites. Every API call, every service-to-service communication, every webhook, every health check...
REST (Representational State Transfer) is an architectural style for designing APIs. It is not a protocol or a tool — it...
Nginx is one of the most important tools in a Platform Engineer's stack. It sits between the internet and your applicati...
Every application stores data somewhere. Understanding how databases work from an operations perspective helps you debug...
This is one of the most important concepts for Platform Engineers. You will configure this in every deployment, every Ku...
Health checks are one of the most important concepts for Platform Engineers to understand deeply. They are how Kubernete...
You will hear these terms constantly. Understanding the architectural difference helps you make the right platform decis...
Let us trace a single request from a Swiggy user placing an order through every concept you have learned in this module....
HTTP Methods Method Use Case Body Idempotent GET Fetch data No Yes POST Create new resource Yes No PUT Replace resource ...
These are the mistakes that appear in real postmortems at companies like Razorpay, Swiggy, and Zerodha. Learning them no...
A 502 from Nginx means Nginx received the request but could not get a valid response from the application behind it. The...
This project uses everything from this module. By the end you will have a containerised Flask API connected to PostgreSQ...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.