KONG

Kong (often called Kong API Gateway) is a tool that sits in front of your APIs and manages all incoming requests—kind of like a smart gatekeeper for APIs.


Simple explanation

Instead of clients calling your backend services directly, they go through Kong first:

Client → Kong → Your APIs

Kong decides:

  • where the request goes
  • whether it’s allowed
  • how it should be handled

🔧 What Kong actually does

1. Routing (like Traefik, but API-focused)

Image
Image
  • Routes requests to the correct backend service
  • Supports paths, hosts, headers, etc.

Example:

/users → user-service
/orders → order-service

2. Authentication & Security

  • API keys
  • OAuth2 / JWT
  • Rate limiting (prevent abuse)

3. Plugins (this is Kong’s superpower)

Kong uses plugins to add features like:

  • logging
  • caching
  • transformations
  • analytics

4. Load balancing

  • Distributes traffic across multiple service instances

5. Observability

  • Logs requests
  • Tracks usage
  • Helps debug API issues

Kong vs Traefik

FeatureKongTraefik
FocusAPIsGeneral web traffic
PluginsVery powerfulMore limited
AuthBuilt-in strongBasic
Use caseMicroservices APIsContainers & routing

Quick takeaway:

  • Traefik → routing + infrastructure
  • Kong → API management + security

Where Kong fits in a system

Frontend / Mobile App
Kong
Microservices (Node, Python, etc.)
Database

Example use case

Imagine you’re building an app with:

  • user service
  • payment service
  • order service

Kong can:

  • route requests to each service
  • require authentication
  • limit requests per user
  • log all API calls

In DevOps terms

Kong is part of:

  • API Gateway layer
  • Often used with:
    • Kubernetes
    • Docker

In one sentence

Kong is an API gateway that controls, secures, and manages traffic to your backend services.


Here’s a working Kong Docker example you can compare directly with Traefik.

The cleanest starter setup is Kong Gateway in DB-less mode. In this mode, Kong runs without a database and reads its routes/services/plugins from a single declarative YAML file, which Kong documents as a supported deployment mode and a good fit for automation and CI/CD. (Kong Docs)

What you’ll build

Client → Kong → Your app

Kong will:

  • listen on port 8000 for proxied API traffic
  • expose an Admin API on port 8001 for local management/testing
  • route /api to your Node app
  • optionally apply plugins like rate limiting or key auth later

Kong’s Docker docs show Compose-based installs, and Kong’s gateway overview describes it as sitting in front of upstream services to control, analyze, and route requests. (Kong Docs)


Project structure

kong-starter/
├── app/
│ ├── package.json
│ └── server.js
├── kong/
│ └── kong.yml
├── Dockerfile
└── compose.yml

1) app/package.json

{
  "name": "kong-starter",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  }
}



2) app/server.js

const http = require("http");
const PORT = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
const body = {
ok: true,
message: "Hello from app behind Kong",
method: req.method,
url: req.url,
host: req.headers.host,
time: new Date().toISOString()
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(body, null, 2));
});
server.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});

3) Dockerfile

FROM node:20-alpine
WORKDIR /app
COPY app/package.json ./
RUN npm install --omit=dev
COPY app/server.js ./
ENV PORT=3000
EXPOSE 3000
CMD ["npm", "start"]

4) kong/kong.yml

This is the declarative config Kong loads in DB-less mode.

_format_version: "3.0"
services:
- name: app-service
url: http://app:3000
routes:
- name: app-route
paths:
- /api

This tells Kong:

  • there is an upstream service at http://app:3000
  • requests hitting /api should be proxied there

Kong’s DB-less docs explain that entities are configured through a declarative YAML or JSON file when database=off. (Kong Docs)


5) compose.yml

services:
kong:
image: kong:3.10
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: 0.0.0.0:8001
ports:
- "8000:8000" # proxy
- "8001:8001" # admin api
volumes:
- ./kong/kong.yml:/kong/declarative/kong.yml:ro
app:
build:
context: .
dockerfile: Dockerfile

Kong’s Docker install docs support Docker Compose installs, and Kong’s read-only/DB-less docs show using database=off with a declarative config file passed into the container. (Kong Docs)


6) Run it

docker compose up -d --build

Then test it:

curl http://localhost:8000/api

You should get JSON back from your Node app.

You can also inspect Kong locally through the Admin API:

curl http://localhost:8001/services

One important note: in DB-less mode, Kong documents that you cannot use the Admin API to write configuration the normal way, because config comes from the declarative file instead. (Kong Docs)


7) Add rate limiting

One of Kong’s main strengths is plugins. Kong’s overview emphasizes its plugin-based approach for implementing API traffic policies. (Kong Docs)

Update kong/kong.yml like this:

_format_version: "3.0"
services:
- name: app-service
url: http://app:3000
routes:
- name: app-route
paths:
- /api
plugins:
- name: rate-limiting
config:
minute: 5
policy: local

Then reload the stack:

docker compose up -d

Now Kong will rate-limit requests through the gateway.


8) Kong vs Traefik in this exact setup

Traefik version

You used labels on the app container:

- "traefik.http.routers.app.rule=Host(`app.localhost`)"

Traefik discovers Docker containers automatically and builds routing from labels. That is the core of its Docker provider model.

Kong version

You define a service and route in kong.yml:

services:
- name: app-service
url: http://app:3000
routes:
- paths:
- /api

So the practical difference is:

  • Traefik feels more infrastructure-native and auto-discovery-driven
  • Kong feels more API-platform-driven, with explicit services, routes, and plugins

Kong’s docs center services, routes, plugins, and deployment modes as the main model for managing API traffic. (Kong Docs)


9) When to use which

Use Traefik when you want:

  • simple reverse proxying
  • automatic Docker/Kubernetes discovery
  • quick app routing
  • built-in HTTPS for web apps

Use Kong when you want:

  • API gateway features
  • auth, rate limiting, transformations, analytics
  • a plugin-heavy API management layer
  • more explicit API governance

That’s an inference from how each product is documented: Traefik emphasizes reverse proxying and dynamic service discovery, while Kong emphasizes API traffic policies through plugins and gateway entities. (Kong Docs)


10) The easiest mental model

  • Traefik = “send traffic to my containers”
  • Kong = “manage and secure my APIs”

11) Resume-worthy project line

Built a containerized API service behind Kong Gateway in DB-less mode using declarative configuration for routing and traffic policy management.


Here’s the same Kong project, but now with API key auth + rate limiting — which is where Kong starts to feel very different from Traefik.

Kong’s Key Authentication plugin can require clients to send an API key in a header, query string, or request body, and Kong’s Rate Limiting plugin can throttle requests by time window. In DB-less mode, you define all of that declaratively in the config file Kong loads at startup. (Kong Docs)

What this version does

Requests to your app will:

  • go through Kong on http://localhost:8000
  • require an API key
  • be limited to 5 requests per minute
  • route to your Node app on /api

In Kong’s rate-limiting docs, if there is an auth layer, the plugin uses the authenticated Consumer for identifying clients; otherwise it falls back to client IP. (Kong Docs)

Updated kong/kong.yml

_format_version: "3.0"
services:
- name: app-service
url: http://app:3000
routes:
- name: app-route
paths:
- /api
plugins:
- name: key-auth
service: app-service
config:
key_names:
- apikey
- name: rate-limiting
service: app-service
config:
minute: 5
policy: local
consumers:
- username: demo-client
keyauth_credentials:
- key: super-secret-demo-key

Why this works:

  • key-auth protects the service with API key authentication. (Kong Docs)
  • key_names: [apikey] tells Kong to look for the API key under that name. Kong documents that keys can be supplied in headers, query params, or request body. (Kong Docs)
  • rate-limiting enforces request quotas over periods like seconds, minutes, hours, and more. (Kong Docs)
  • policy: local stores counters in-memory on the node; Kong notes this has minimal performance impact but is less accurate across multiple nodes. (Kong Docs)
  • consumers plus keyauth_credentials gives the client an identity and an API key in DB-less declarative config. That fits Kong’s DB-less model where config is the source of truth. (Kong Docs)

compose.yml

You can keep the same Compose file structure as before:

services:
kong:
image: kong:3.10
environment:
KONG_DATABASE: "off"
KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: 0.0.0.0:8001
ports:
- "8000:8000"
- "8001:8001"
volumes:
- ./kong/kong.yml:/kong/declarative/kong.yml:ro
app:
build:
context: .
dockerfile: Dockerfile

Kong’s Docker install docs support Compose installs, and DB-less deployments use KONG_DATABASE=off plus a declarative config file path. (Kong Docs)

Start it

docker compose up -d --build

Test without an API key

curl -i http://localhost:8000/api

This should fail because the route is protected by key-auth. Kong’s Key Auth plugin requires a valid key for access. (Kong Docs)

Test with the API key

Send the key in the apikey header:

curl -i \
-H "apikey: super-secret-demo-key" \
http://localhost:8000/api

That should succeed.

You can also pass the key as a query string because Kong’s Key Auth plugin supports query string auth too. (Kong Docs)

curl -i "http://localhost:8000/api?apikey=super-secret-demo-key"

Test the rate limit

Run this several times quickly:

for i in {1..6}; do
curl -s -o /dev/null -w "%{http_code}\n" \
-H "apikey: super-secret-demo-key" \
http://localhost:8000/api
done

You should see the first few succeed and then a 429 once you exceed the per-minute limit. Kong’s rate-limiting plugin is designed to cap requests over configured windows like minute: 5. (Kong Docs)

Why this is more “API gateway” than reverse proxy

With Traefik, the main idea was: “route traffic to the right service.” With this Kong setup, the gateway is also enforcing who can call the API and how often they can call it. Kong’s docs frame plugins like Key Auth and Rate Limiting as first-class traffic policy features for services and routes. (Kong Docs)

A practical mental model

  • Traefik: “Send requests to the right app.”
  • Kong: “Control access to the API, then send requests to the app.”

That is an inference from their documented feature emphasis: Traefik centers dynamic routing and service discovery, while Kong centers API traffic policy through gateway entities and plugins. (Kong Docs)

Good next upgrades

The next Kong features that are most worth learning are:

  • JWT auth
  • request/response transformation
  • ACLs by consumer group
  • logging plugins
  • declarative config managed from Git

Those all build naturally on Kong’s plugin model and DB-less configuration workflow. (Kong Docs)

to build a project (code + config) – production ready

Here’s the production version of the starter project: real domain, automatic HTTPS, HTTP→HTTPS redirect, and a secured Traefik dashboard.

This uses Traefik’s Docker provider with labels for routing, a Let’s Encrypt certificate resolver for TLS, and the dashboard in secure mode rather than api.insecure=true. Traefik’s docs recommend securing the dashboard and show Docker Compose setups for HTTPS with ACME. (Traefik Docs)

Before you start

You need:

  • a Linux server with Docker and Docker Compose
  • a domain or subdomain pointing to that server
  • ports 80 and 443 open to the internet

For the HTTP-01 challenge, Traefik’s ACME guide requires the app to be reachable publicly and the domain to point to the Traefik instance. (Traefik Docs)


Recommended structure

devops-starter/
├── app/
│ ├── package.json
│ └── server.js
├── letsencrypt/
│ └── acme.json
├── .github/
│ └── workflows/
│ └── publish.yml
├── .env
├── Dockerfile
└── compose.yml

1) app/package.json

{
  "name": "devops-starter",
  "version": "1.0.0",
  "description": "Node app behind Traefik with HTTPS",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "license": "MIT"
}

2) app/server.js

const http = require("http");
const PORT = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
if (req.url === "/healthz") {
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ ok: true }));
}
const body = {
ok: true,
message: "Hello from production",
method: req.method,
url: req.url,
hostname: req.headers.host,
time: new Date().toISOString()
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(body, null, 2));
});
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

3) Dockerfile

FROM node:20-alpine

WORKDIR /app

COPY app/package.json ./
RUN npm install --omit=dev

COPY app/server.js ./

ENV PORT=3000
EXPOSE 3000

CMD ["npm", "start"]


4) .env

Replace these with your real values:

DOMAIN=app.yourdomain.com
TRAEFIK_DASHBOARD_HOST=traefik.yourdomain.com
LETSENCRYPT_EMAIL=you@example.com

# Generate this with: htpasswd -nb admin 'your-strong-password'
# Then double the $ signs when putting it here for docker labels
TRAEFIK_BASIC_AUTH=admin:$$apr1$$replace$$with-real-hash

Traefik’s BasicAuth middleware supports htpasswd-style hashes, and its docs note that when using Docker labels, dollar signs need escaping. (Traefik Docs)


5) Create the certificate storage file

Run this once on the server:

mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

Traefik’s ACME examples store certificates in acme.json, and the file should be writable by Traefik while remaining protected. (Traefik Docs)


6) compose.yml

services:
  traefik:
    image: traefik:v3.4
    restart: unless-stopped
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"

      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"

      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"

      - "--certificatesresolvers.le.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"

      - "--accesslog=true"
      - "--log.level=INFO"

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"

    labels:
      - "traefik.enable=true"

      # Secure dashboard
      - "traefik.http.routers.dashboard.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.tls.certresolver=le"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-auth"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_BASIC_AUTH}"

  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=le"

      # Tell Traefik which internal port the app listens on
      - "traefik.http.services.app.loadbalancer.server.port=3000"

Why these labels and flags matter:

  • Traefik uses Docker labels as dynamic config when Docker is the provider. (Traefik Docs)
  • entrypoints.web and entrypoints.websecure define listeners on ports 80 and 443. (Traefik Docs)
  • The web entrypoint redirects all traffic to websecure, which is the standard Traefik redirect pattern. (Traefik Docs)
  • tls.certresolver=le tells the router to request and renew certificates through the Let’s Encrypt resolver you defined. (Traefik Docs)
  • The dashboard can be exposed securely through api@internal and protected with BasicAuth instead of insecure mode. (Traefik Docs)

7) DNS records

Create DNS records like:

  • A app.yourdomain.com -> your_server_ip
  • A traefik.yourdomain.com -> your_server_ip

If you use IPv6, add AAAA records too. The names used in your router Host(...) rules must resolve to the server running Traefik for ACME issuance to work. (Traefik Docs)


8) First deploy

From the project folder on your server:

docker compose up -d --build

Then open:

  • https://app.yourdomain.com
  • https://traefik.yourdomain.com

On first startup, Traefik should obtain certificates automatically via Let’s Encrypt as requests arrive for matching routers using the resolver. (Traefik Docs)

Useful commands:

docker compose logs -f traefik
docker compose logs -f app
docker compose ps


9) Generate the dashboard password hash

If htpasswd is installed:

htpasswd -nb admin 'your-strong-password'

Put the result in .env as TRAEFIK_BASIC_AUTH=..., but replace every $ with $$ so Docker Compose does not treat them as variable substitutions. Traefik’s BasicAuth docs explicitly mention escaping dollar signs in Docker label contexts. (Traefik Docs)


10) Publish the image from GitHub Actions

If you want Actions to build and push your app image to GHCR, use this workflow.

.github/workflows/publish.yml

name: Build and publish image
on:
push:
branches: ["main"]
env:
IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/devops-starter
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest
type=sha
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

GitHub’s docs recommend docker/build-push-action for building and publishing images, and GHCR uses the Container registry at ghcr.io. Workflows can authenticate with GITHUB_TOKEN when package permissions are configured appropriately. (GitHub Docs)

If you switch to pulling the published image on the server, replace the app service in Compose with:

  app:
    image: ghcr.io/YOUR_GITHUB_USERNAME/devops-starter:latest
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=le"
      - "traefik.http.services.app.loadbalancer.server.port=3000"


11) What makes this “production enough” for a first real project

This version is much closer to a real deployment because it has:

  • automatic HTTPS
  • secure dashboard access
  • HTTP→HTTPS redirect
  • restart policy
  • access logs
  • a health endpoint
  • optional CI image publishing

Those pieces line up with Traefik’s Docker standalone guidance and dashboard/ACME docs. (Traefik Docs)


12) Common failure points

If it does not work, the usual causes are:

  • DNS not pointing at the server
  • ports 80/443 blocked by firewall or cloud security group
  • invalid TRAEFIK_BASIC_AUTH hash formatting
  • acme.json missing or wrong permissions
  • router hostnames not matching the browser request

Those are the most common things that prevent Traefik from issuing certs or matching routers in Docker setups. (Traefik Docs)


13) Resume line for this project

Deployed a containerized Node.js service behind Traefik with automatic Let’s Encrypt TLS, secure reverse-proxy routing, and GitHub Actions image publishing to GHCR.

That is solid, real DevOps experience.

step-by-step to build a project (code + config)

here’s a copyable starter project you can build end to end.

It gives you:

  • a tiny Node app in Docker
  • Traefik in front of it
  • hostname-based routing on localhost
  • a GitHub Actions workflow that builds the image
  • a path to deploy the same stack to a server later

This matches Traefik’s current Docker provider pattern, where Traefik watches Docker and picks up routing config from container labels. (Traefik Labs Documentation)


Project structure

devops-starter/
├── app/
│ ├── package.json
│ └── server.js
├── .github/
│ └── workflows/
│ └── docker.yml
├── .dockerignore
├── Dockerfile
└── compose.yml

1) app/package.json

{
"name": "devops-starter",
"version": "1.0.0",
"description": "Simple Node app behind Traefik",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"license": "MIT"
}

2) app/server.js

const http = require("http");
const PORT = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
const body = {
ok: true,
message: "Hello from the app behind Traefik",
method: req.method,
url: req.url,
hostname: req.headers.host,
time: new Date().toISOString()
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(body, null, 2));
});
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

3) Dockerfile

This uses an official Node image and a fixed major version tag, which is in line with GitHub’s Dockerfile guidance. (GitHub Docs)

FROM node:20-alpine
WORKDIR /app
COPY app/package.json ./
RUN npm install --omit=dev
COPY app/server.js ./
ENV PORT=3000
EXPOSE 3000
CMD ["npm", "start"]

4) .dockerignore

node_modules
npm-debug.log
.git
.github
Dockerfile
compose.yml

5) compose.yml

This follows the same core idea as Traefik’s Docker Compose examples: enable the Docker provider, disable exposing containers by default, define an HTTP entrypoint, and add labels to the app container so Traefik creates the router automatically. (Traefik Labs Documentation)

services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
app:
build:
context: .
dockerfile: Dockerfile
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.localhost`)"
- "traefik.http.routers.app.entrypoints=web"

A couple of notes:

  • api.insecure=true is fine for learning locally, but not for a public server. Traefik’s dashboard docs treat this as something to secure for real deployments. (Traefik Labs Documentation)
  • Because both services are in the same Compose stack, Docker networking handles connectivity between Traefik and the app. That is the same pattern used in Docker and Traefik quick-start examples. (Traefik Labs Documentation)

6) .github/workflows/docker.yml

GitHub’s docs show Docker builds in Actions using actions/checkout and docker/build-push-action. This workflow keeps it simple: it builds on every push to main, and you can later extend it to push to Docker Hub or GHCR. (GitHub Docs)

name: Build Docker image
on:
push:
branches: ["main"]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: false
tags: devops-starter:latest

7) Run it locally

From the project root:

docker compose up -d --build

Then open:

  • http://app.localhost
  • http://localhost:8080 for the Traefik dashboard

Traefik’s Docker quick-start uses the same localhost-style host rule pattern, and the dashboard is commonly exposed on port 8080 in the getting-started setup. (Traefik Labs Documentation)

To stop:

docker compose down

To view logs:

docker compose logs -f

8) What’s happening

When you visit http://app.localhost:

  1. your browser sends a request to port 80
  2. Traefik receives it
  3. Traefik checks Docker-discovered labels
  4. the router rule Host(\app.localhost`)` matches
  5. Traefik forwards the request to the app container

That “dynamic config from Docker labels” model is a central part of Traefik’s configuration overview and Docker provider docs. (Traefik Labs Documentation)


9) Make it feel more real

Add a second app to prove routing works.

Update compose.yml like this:

services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
app:
build:
context: .
dockerfile: Dockerfile
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.localhost`)"
- "traefik.http.routers.app.entrypoints=web"
whoami:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
- "traefik.http.routers.whoami.entrypoints=web"

Then:

  • http://app.localhost → your app
  • http://whoami.localhost → sample Traefik test service

That mirrors Traefik’s own examples for exposing services with Docker labels. (Traefik Labs Documentation)


10) How to deploy this later

For a simple first deployment:

  • get a Linux VM
  • install Docker and Docker Compose
  • copy this project to the server
  • point a domain at the server IP
  • swap localhost routing for your real domain
  • add HTTPS with Traefik + Let’s Encrypt

Traefik documents Docker standalone setup, HTTPS entrypoints, and ACME/Let’s Encrypt support as part of its normal production path. (Traefik Labs Documentation)

Your production router label would look more like:

- "traefik.http.routers.app.rule=Host(`app.yourdomain.com`)"

11) Resume-worthy version of this project

Once this is live, you can honestly describe it like this:

Built and deployed a containerized Node.js service using Docker and Traefik with hostname-based routing and automated image builds via GitHub Actions.

That is a real DevOps project, not tutorial-only practice.


12) Best next upgrades

After this works, do these in order:

  1. add /healthz endpoint
  2. add a test job to GitHub Actions
  3. push built images to GHCR or Docker Hub
  4. deploy on a small cloud VM
  5. add HTTPS with Let’s Encrypt
  6. add Prometheus/Grafana later

GitHub’s Actions docs already provide the build-and-publish direction if you want to turn your build-only workflow into a registry-pushing workflow. (GitHub Docs)


13) The shortest possible checklist

Create files → run:

docker compose up -d --build

Visit:

http://app.localhost
http://localhost:8080

Push to GitHub → Actions builds the image definition automatically.


The 2026 Guide to DevOps Careers

The 2026 Guide to DevOps Careers

DevOps isn’t just a job title anymore—it’s a core engineering mindset that companies rely on to ship software faster, safer, and at scale. If you’re thinking about getting into it (or leveling up), here’s a clear, realistic guide to where things stand in 2026.


What DevOps Actually Means (Now)

DevOps sits at the intersection of:

  • Software development
  • Infrastructure / cloud
  • Automation
  • Reliability & monitoring

In practice, you’re:

  • Building CI/CD pipelines
  • Managing cloud infrastructure
  • Improving deployment speed & reliability
  • Fixing production issues
  • Automating everything repetitive

Common DevOps Roles (2026)

DevOps Engineer

  • Focus: CI/CD, automation, infrastructure
  • Tools: GitHub Actions, Jenkins, Terraform
  • Entry → Mid-level role

Cloud Engineer

  • Focus: Cloud platforms, networking, scalability
  • Platforms: AWS, Google Cloud Platform, Microsoft Azure
  • Heavy on infrastructure + cost optimization

Site Reliability Engineer (SRE)

  • Focus: uptime, performance, incident response
  • Origin: Google
  • More coding + systems thinking than typical DevOps

Platform Engineer (fastest-growing)

  • Focus: building internal developer platforms
  • Tools: Kubernetes, Backstage
  • Think: “DevOps as a product”

Core Skills You Need

1. Linux & Networking

  • SSH, processes, file systems
  • HTTP, DNS, load balancing

2. Containers & Orchestration

Image
Image
  • Docker → package apps
  • Kubernetes → run them at scale

3. CI/CD Pipelines

  • Automate build → test → deploy
  • Tools: GitLab CI, CircleCI

4. Infrastructure as Code (IaC)

  • Manage infra like code
  • Tools: Terraform, Ansible

5. Observability

  • Logs, metrics, tracing
  • Tools: Prometheus, Grafana

6. Networking & Routing (where Traefik fits)

  • Reverse proxies like Traefik or NGINX
  • TLS, domains, load balancing

Learning Roadmap (Beginner → Job Ready)

Stage 1: Foundations (2–4 weeks)

  • Linux basics
  • Git + GitHub
  • Basic networking (HTTP, DNS)

Stage 2: Build Stuff (1–2 months)

  • Learn Docker
  • Deploy a simple app locally
  • Add Traefik or NGINX

Stage 3: Cloud + Automation (2–3 months)

  • Use AWS or similar
  • Write basic Terraform
  • Create CI/CD pipeline

Stage 4: Production-Level Skills

  • Learn Kubernetes
  • Add monitoring (Prometheus + Grafana)
  • Practice debugging failures

Salaries (2026 rough ranges)

(varies by country & experience)

  • Entry: $70k–$100k
  • Mid: $100k–$150k
  • Senior: $150k–$220k+
  • Platform/SRE at big tech: even higher

What’s Changed in 2026

  • Platform Engineering > DevOps titles
  • More focus on developer experience (DX)
  • AI is assisting pipelines, but you still need fundamentals
  • Kubernetes is still dominant—but simplified tools are growing

What Actually Gets You Hired

Not certificates—projects:

  • Deploy a full app with:
    • Docker
    • CI/CD
    • HTTPS (Traefik or NGINX)
  • Show logs + monitoring
  • Break things and fix them

Example Project (highly recommended)

Build this:

  • App (Node/Python)
  • Containerized with Docker
  • Routed via Traefik
  • Deployed on AWS
  • Automated with CI/CD
  • HTTPS enabled

That one project alone can outperform most resumes.


Final Reality Check

DevOps is:

  • Less about tools
  • More about systems thinking + automation mindset

If you like:

  • solving messy problems
  • understanding how systems connect
  • building things that just work

—you’ll probably enjoy it.


realistic 30-day DevOps plan that ends with a real project you can show on your resume. No fluff, just what actually builds skill.


30-Day DevOps Roadmap (Hands-on)

Final Goal (what you’ll build)

By Day 30, you’ll have:

  • A containerized app using Docker
  • Routed with Traefik
  • CI/CD pipeline via GitHub Actions
  • Deployed on AWS (or similar)
  • HTTPS enabled
  • Public URL to show employers

Week 1 — Foundations (Linux + Networking)

Learn:

  • Basic Linux commands: cd, ls, grep, chmod
  • Processes & ports
  • What HTTP, DNS, and IP actually do

Do:

  • Install Linux (or use WSL)
  • Run a simple web server:
python3 -m http.server 8000

Outcome:

You understand how apps run and are accessed via ports.


Week 2 — Containers (Docker)

Learn:

  • What containers are
  • Images vs containers
  • Ports & volumes

Do:

Build your first container:

FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "app.js"]

Run it:

docker build -t myapp .
docker run -p 3000:3000 myapp

Visual idea

Image

Outcome:

You can package and run any app.


Week 3 — Reverse Proxy + Routing (Traefik)

Learn:

  • What a reverse proxy does
  • Routing by domain
  • Basic HTTPS concepts

Do:

Use Traefik with Docker Compose:

  • Route:
    • app.localhost → your app
  • Add second service to understand routing

Outcome:

You understand how real production traffic is routed.


Week 4 — CI/CD + Cloud Deployment

Learn:

  • CI/CD basics (build → test → deploy)
  • Cloud basics (VMs, ports, SSH)

Do:

1. CI/CD with GitHub Actions

Create .github/workflows/deploy.yml:

name: Deploy
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: docker build -t myapp .

2. Deploy to AWS

  • Launch EC2 instance
  • SSH in
  • Install Docker
  • Run your app + Traefik setup

3. Add HTTPS

  • Use Traefik + Let’s Encrypt
  • Point your domain to your server

Outcome:

Your app is LIVE on the internet.


Your Final Project Structure

project/
├── app/
├── Dockerfile
├── docker-compose.yml
├── traefik/
├── .github/workflows/

What to Put on Your Resume

Instead of “learning DevOps,” say:

Deployed a containerized application using Docker and Traefik with automated CI/CD via GitHub Actions on AWS, including HTTPS and reverse proxy routing.

That’s real experience.


Common Mistakes (avoid these)

  • ❌ Only watching tutorials
  • ❌ Skipping networking basics
  • ❌ Not deploying publicly
  • ❌ Over-focusing on Kubernetes too early

If You Want to Go Further (Day 31+)

Next steps:

  • Learn Kubernetes
  • Add monitoring with Prometheus + Grafana
  • Use Terraform

Final Advice

Don’t aim to “learn DevOps.”

Aim to:

Build → Break → Fix → Repeat

That loop is what actually makes you good.


I can guide you step-by-step to build the exact project (code + config) so you can literally copy, run, and understand everything.

I will create a separate post for this

Traefik

Traefik is an open-source reverse proxy and load balancer designed for modern cloud-native applications—especially those running in containers.


What that actually means

Think of Traefik as a smart traffic controller sitting in front of your apps:

  • It receives incoming requests (like someone visiting your website)
  • Then routes them to the correct service (e.g., your API, frontend, or another container)
  • It can also balance traffic across multiple instances of the same service

Key features

Automatic service discovery

Image
Image

Traefik integrates directly with tools like:

  • Docker
  • Kubernetes

It automatically detects new containers/services and routes traffic to them—no manual config needed.


Built-in HTTPS (SSL/TLS)

  • Automatically generates and renews certificates using Let’s Encrypt
  • Handles HTTPS setup for you (no manual certificate management)

Load balancing

  • Distributes requests across multiple instances
  • Helps keep your app fast and available

Dynamic configuration

  • Updates routes in real time when services start/stop
  • No restarts required

Dashboard & monitoring

  • Web UI shows routes, services, and traffic
  • Useful for debugging and observability

Simple example (Docker)

If you run a container with labels like:

labels:
- "traefik.http.routers.myapp.rule=Host(`myapp.local`)"

Traefik will:

  • Detect the container
  • Create a route for myapp.local
  • Start sending traffic there automatically

When people use Traefik

  • Microservices architectures
  • Docker or Kubernetes setups
  • Hosting multiple apps on one server
  • Replacing tools like:
    • NGINX
    • HAProxy

In one sentence

Traefik is a modern, automatic reverse proxy that makes routing traffic to containerized apps simple and dynamic.


Here’s a small working Docker Compose example you can run locally. It follows Traefik’s current Docker quick-start pattern: Traefik listens to Docker, exposes port 80 for app traffic and 8080 for the dashboard, and routes a sample whoami container using labels. (Traefik Docs)

version: "3.9"
services:
traefik:
image: traefik:v3.0
container_name: traefik
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
whoami:
image: traefik/whoami
container_name: whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)"
- "traefik.http.routers.whoami.entrypoints=web"

Run it with:

docker compose up -d

Then open:

  • http://whoami.localhost → sample app
  • http://localhost:8080 → Traefik dashboard

That hostname rule is the key idea: Traefik reads the Docker labels and creates a router so requests for whoami.localhost go to the whoami container. Traefik hot-reloads this dynamic routing config from Docker without restarting. (Traefik Docs)

How to read the important lines:

  • --providers.docker=true tells Traefik to watch Docker for containers/services. (Traefik Docs)
  • --providers.docker.exposedbydefault=false means only containers with traefik.enable=true get exposed. (Traefik Docs)
  • --entryPoints.web.address=:80 creates an HTTP entrypoint on port 80. (Traefik Docs)
  • traefik.http.routers.whoami.rule=Host(\whoami.localhost`)` matches incoming requests by hostname. (Traefik Docs)

A more realistic example is routing two apps:

version: "3.9"
services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entryPoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
app1:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.app1.rule=Host(`app1.localhost`)"
- "traefik.http.routers.app1.entrypoints=web"
app2:
image: traefik/whoami
labels:
- "traefik.enable=true"
- "traefik.http.routers.app2.rule=Host(`app2.localhost`)"
- "traefik.http.routers.app2.entrypoints=web"

Then:

  • http://app1.localhost → app1
  • http://app2.localhost → app2

That is basically the Traefik workflow: define a service, add labels, and Traefik discovers it automatically. The official docs also note that when both containers are in the same Compose file, Docker’s default network is enough for Traefik to reach them. (Traefik Docs)

A couple of useful notes:

  • The dashboard setting shown here is insecure and meant for local learning, not production. (Traefik Docs)
  • For production, people usually add TLS/HTTPS and often Let’s Encrypt certificate automation through Traefik’s config. The routing and certificates are part of Traefik’s dynamic config model. (Traefik Docs)

Want me to show the same thing with HTTPS + Let’s Encrypt, or a Kubernetes Ingress example?

Here’s a real HTTPS + Let’s Encrypt Docker Compose setup for Traefik.

It uses the HTTP-01 challenge, which means your server must be publicly reachable on ports 80 and 443, and your domain’s DNS must point at that server. Traefik’s docs also note that certificate resolvers are defined in static config, the router must have TLS enabled, and the router references the resolver by name. (Traefik Docs)

version: "3.9"
services:
traefik:
image: traefik:v3.4
container_name: traefik
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# entrypoints
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# redirect http -> https
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
# lets encrypt
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge=true"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
- "8080:8080" # optional dashboard
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
app:
image: traefik/whoami
container_name: whoami
labels:
- "traefik.enable=true"
# router
- "traefik.http.routers.app.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls=true"
- "traefik.http.routers.app.tls.certresolver=le"

Then run:

mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
docker compose up -d

Then open:

  • https://whoami.yourdomain.com
  • http://whoami.yourdomain.com will redirect to HTTPS

Why this works:

  • providers.docker=true makes Traefik watch Docker containers and labels. (Traefik Docs)
  • entrypoints.web and entrypoints.websecure create listeners on ports 80 and 443. (Traefik Docs)
  • certificatesresolvers.le.acme.* configures Let’s Encrypt and stores certs in acme.json. (Traefik Docs)
  • tls.certresolver=le tells that router to request and renew a cert through the le resolver. (Traefik Docs)

A couple of important gotchas:

  • Replace you@example.com with your real email.
  • Replace whoami.yourdomain.com with a real domain/subdomain you control.
  • That hostname needs an A or AAAA record pointing to your server. Traefik’s docs say ACME domains must point to Traefik. (Traefik Docs)
  • Port 8080 is just for the dashboard. Don’t leave it openly exposed on the internet unless you secure it. (Traefik Docs)

For Kubernetes, the usual Traefik-native route is an IngressRoute or standard Ingress; Traefik also documents a working Kubernetes + Let’s Encrypt example with CRDs. (Traefik Docs)

OCP vs EKS vs AKS — clear, real-world comparison

OCP vs EKS vs AKS — clear, real-world comparison

Image

You’re comparing three ways to run Kubernetes:

  • Red Hat OpenShift (OCP) → full enterprise platform
  • Amazon EKS → AWS-managed Kubernetes
  • Azure Kubernetes Service (AKS) → Azure-managed Kubernetes

One-line mental model

  • OCP = Kubernetes + platform + opinionated tooling
  • EKS / AKS = Kubernetes as a service

Core architecture difference

OpenShift (OCP)

  • You manage:
    • cluster (unless using ROSA/ARO)
    • nodes
  • Comes with:
    • registry
    • CI/CD
    • security policies
    • operators
  • Runs:
    • on-prem, cloud, hybrid

EKS (AWS)

  • AWS manages:
    • control plane
  • You manage:
    • worker nodes (or use Fargate)
  • Uses AWS ecosystem:
    • IAM
    • ALB / NLB
    • VPC networking

AKS (Azure)

  • Azure manages:
    • control plane
  • You manage:
    • node pools
  • Uses Azure ecosystem:
    • Azure AD
    • Load Balancer
    • VNets

Security model

FeatureOCPEKSAKS
Default security🔒 Very strictModerateModerate
Pod restrictionsSCC (strong)PSP/OPA (optional)Azure policies
IdentityRBAC + OAuthIAM rolesAzure AD

OCP is the most locked-down by default.


Networking & exposure

FeatureOCPEKSAKS
External accessRoutesIngress + ALBIngress + Azure LB
CNIOVN-KubernetesAWS VPC CNIAzure CNI / Kubenet

OCP’s Routes = simpler developer experience
EKS/AKS = more cloud-native integrations


CI/CD & Developer Experience

FeatureOCPEKSAKS
Built-in CI/CD✅ Yes (BuildConfig, pipelines)❌ No❌ No
Container registry✅ Built-in❌ (ECR external)❌ (ACR external)
Developer UI✅ StrongMinimalMinimal

OCP is a developer platform, not just infra.


Operations & automation

FeatureOCPEKSAKS
OperatorsCore conceptOptionalOptional
Cluster upgradesOperator-drivenAWS-managedAzure-managed
Add-onsBuilt-inAWS add-onsAzure add-ons

Cost model (important)

  • OCP
    • license + infra cost
  • EKS
    • control plane fee + AWS resources
  • AKS
    • control plane often free + Azure resources

OCP is usually the most expensive.


Where each shines

Use OpenShift when:

  • enterprise / regulated environments
  • on-prem or hybrid cloud
  • need built-in CI/CD + security
  • platform engineering teams

Use EKS when:

  • you’re deep in AWS ecosystem
  • want flexibility + AWS integrations
  • prefer DIY platform setup

Use AKS when:

  • you’re in Azure ecosystem
  • want simplest managed Kubernetes
  • using Azure AD, DevOps, etc.

Real-world differences that matter

1. Developer experience
  • OCP → “push code → app runs”
  • EKS/AKS → you wire everything yourself

2. Security defaults
  • OCP → restrictive (safe by default)
  • EKS/AKS → flexible (you configure security)

3. Lock-in
  • OCP → Red Hat ecosystem
  • EKS → AWS lock-in
  • AKS → Azure lock-in

Interview-ready answer

“OpenShift is a full Kubernetes platform with built-in CI/CD, registry, and strong security, while EKS and AKS are managed Kubernetes services where the cloud provider manages the control plane. OCP is more opinionated and enterprise-focused, whereas EKS and AKS provide more flexibility but require assembling additional components.”


OpenShift (OCP) – Ingress

In OpenShift (OCP), Ingress is the mechanism that allows external traffic (HTTP/HTTPS) to reach services inside your cluster. While Kubernetes has a standard “Ingress” resource, OpenShift has historically used its own evolved version called Routes.

As of 2026, the landscape has expanded to include the Gateway API, which is the modern successor to both.


1. The Three Ways to Expose Apps

FeatureRoute (Native OCP)Ingress (K8s Standard)Gateway API (The Future)
SimplicityHigh (Very easy to use)MediumMedium/High
FlexibilityGoodLimited (Needs annotations)Extreme (Fine-grained control)
StandardRed Hat ProprietaryKubernetes LegacyKubernetes Modern (GA 2026)
Best ForStandard OCP appsCross-platform migrationComplex routing/Canary/Blue-Green

2. How the “Router” Works

The Ingress Controller in OCP is an Operator-managed deployment of HAProxy (by default).

  • It sits at the edge of the cluster.
  • It watches for new Routes or Ingresses.
  • It automatically updates its configuration and starts proxying traffic to the correct Pods.

3. Key Concepts for Admins

Ingress Controller Sharding

In large clusters, a single router can become a bottleneck. You can “shard” your ingress traffic by creating multiple Ingress Controllers.

  • Example: Create one router for *.public.example.com and a separate, isolated router for *.internal.example.com.
  • Benefit: Performance isolation and security (e.g., PCI-compliant traffic on specific nodes).

TLS Termination Patterns

Routes support four types of security:

  1. Edge: SSL is decrypted at the Router. Traffic to the Pod is plain HTTP. (Most common).
  2. Passthrough: SSL is sent directly to the Pod. The Router doesn’t see the data.
  3. Re-encryption: SSL is decrypted at the Router, inspected, then re-encrypted before being sent to the Pod.
  4. None: Simple plain HTTP.

4. Interview “Pro” Tips

  • The “503 Service Unavailable” Error: If a developer sees this on their Route, it almost always means the Readiness Probe for the Pod is failing. The Router won’t send traffic to a Pod that isn’t “Ready.”
  • Host vs Path Routing: OCP Routes excel at host-based routing (app-a.com vs app-b.com). If you need complex path-based routing (e.g., app.com/v1/api going to one service and /v2/api to another), the Gateway API is now the recommended tool over standard Routes.
  • Wildcard DNS: OCP creates a default wildcard (e.g., *.apps.cluster.example.com). Every time you create a Route without a host, OCP generates one for you using this pattern.

5. Troubleshooting Command Cheat Sheet

# Check the status of the Ingress Operator
oc get ingresscontroller -n openshift-ingress-operator
# See the HAProxy pods actually doing the work
oc get pods -n openshift-ingress
# Check if a Route is "Admitted" (Successfully configured)
oc get route <route-name> -o yaml | grep -A 5 status
# Look at router logs to see traffic errors
oc logs -n openshift-ingress deployment/router-default
In a high-scale enterprise environment, you often want to isolate traffic. For example, your **Internal HR App**
shouldn't share the same entry point (the router) as your **Public Marketing Site**. This is called **Ingress Sharding**.
In OpenShift, we achieve this by creating a second **Ingress Controller** and using **Namespace Selectors**.
---
### 1. The Strategy: Router Sharding
By default, OpenShift has a `default` Ingress Controller that handles everything. To split traffic, we will:
1. Create a new Ingress Controller named `sharded-router`.
2. Tell it to only watch namespaces with a specific label (e.g., `type: public`).
3. Label our target namespace.
---
### 2. Implementation Steps
#### **Step A: Label the Namespace**
First, identify which projects should use the new, isolated router.
```bash
oc label namespace my-public-app type=public
```
#### **Step B: Create the New Ingress Controller**
This YAML creates a new set of HAProxy pods that only serve traffic for namespaces with the `type=public` label.
```yaml
apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
name: sharded-public
namespace: openshift-ingress-operator
spec:
domain: public.apps.mycluster.com # A dedicated subdomain
endpointPublishingStrategy:
type: LoadBalancerService # Creates a new Cloud Load Balancer
namespaceSelector:
matchLabels:
type: public # The magic filter
nodePlacement: # Optional: Run on specific "DMZ" nodes
nodeSelector:
matchLabels:
node-role.kubernetes.io/infra: ""
```
---
### 3. Verification
Once applied, OpenShift will spin up new pods in the `openshift-ingress` namespace.
* **Check Pods:** `oc get pods -n openshift-ingress` (You'll see `router-sharded-public-...`)
* **Check Load Balancer:** `oc get svc -n openshift-ingress` (You'll see a new service with a unique External IP).
Now, any Route created in the `my-public-app` namespace will be picked up by the **new** router and ignored by the **default** one.
---
### 💡 Interview Questions on Sharding
* **Q: Why would you shard an Ingress Controller?**
* **A:** For **Security** (isolating internal vs. external traffic), **Performance** (preventing a noisy neighbor from hogging the CPU/bandwidth of the router), and **Compliance** (ensuring certain data only flows through nodes that meet specific regulatory standards).
* **Q: How does the Route know which router to use?**
* **A:** The Route doesn't "choose." The **Ingress Controllers** choose the Routes based on the `namespaceSelector` or `routeSelector` defined in their configuration.
* **Q: Can a single Route be served by two different Routers?**
* **A:** Yes, if both Ingress Controllers have selectors that match that Route's namespace or labels. This is sometimes used during migrations.
---

OCP architecture

OpenShift (OCP) Architecture — Clear, Practical Breakdown

Red Hat OpenShift (OCP) is a Kubernetes-based platform with extra layers for:

  • security
  • developer workflows
  • enterprise operations

Think of it as:

Kubernetes + opinionated enterprise tooling + automation


High-Level Architecture

At the highest level, OpenShift has 3 main layers:

1. Control Plane (Master Nodes)

Manages the cluster

2. Worker Nodes

Run your applications

3. Infrastructure Layer

Networking, storage, registry, ingress


1. Control Plane (Master Nodes)

Core brain of the cluster:

Key components:
  • kube-apiserver
    • entry point for all API calls
  • etcd
    • stores cluster state
  • kube-scheduler
    • assigns pods to nodes
  • kube-controller-manager
    • maintains desired state

OpenShift-specific additions:
  • OpenShift API Server
    • adds OCP-specific APIs (routes, builds, etc.)
  • Controller Manager (OpenShift)
    • handles builds, deployments, image streams

2. Worker Nodes

Where workloads run.

Components:
  • kubelet
    • manages pods on node
  • Container runtime
    • usually CRI-O (default in OpenShift)
  • Pods
    • your apps + sidecars

3. Networking Layer

Key pieces:

  • Cluster Network
    • pod-to-pod communication
  • Service Network
    • stable virtual IPs
  • Ingress / Routes (OpenShift-specific)

OpenShift uses Routes instead of standard Ingress:

  • external traffic → router → service → pod

OpenShift Router (Ingress Controller)

  • based on HAProxy
  • handles:
    • TLS termination
    • load balancing
    • external exposure

4. Image & Build System (OCP unique)

This is where OpenShift stands out.

Image Registry
  • internal container registry
Image Streams
  • track image versions
  • trigger deployments automatically
BuildConfig
  • builds images from:
    • Git
    • Dockerfile
    • Source-to-Image (S2I)

5. Security Layer (very important)

OpenShift is stricter than Kubernetes.

Features:

  • Security Context Constraints (SCC)
    • control what pods can do
    • similar to Pod Security Policies
  • No root containers by default
  • SELinux enforced
  • integrated RBAC

6. Operators (Automation Engine)

OpenShift heavily uses Operators.

  • manage apps like:
    • databases
    • monitoring
    • logging

Built-in operators:

  • cluster version operator
  • ingress operator
  • etc.

7. Observability & Logging

Built-in:

  • Prometheus (monitoring)
  • Grafana (dashboards)
  • EFK / Loki stack (logging)

Full Flow Example

Deploying an app:

  1. Push code to Git
  2. BuildConfig builds image
  3. Image stored in registry
  4. Deployment created
  5. Pod runs on worker node
  6. Service exposes pod internally
  7. Route exposes app externally

OpenShift vs Kubernetes (quick view)

FeatureKubernetesOpenShift
IngressIngress resourceRoutes
Securityflexiblestrict by default
Buildsexternal toolsbuilt-in
Registryoptionalbuilt-in
UIoptionalstrong web console

Simple mental model

  • Kubernetes = engine
  • OpenShift = full platform

Interview-ready summary

OpenShift architecture is built on Kubernetes with control plane and worker nodes, but adds enterprise features like integrated registry, build pipelines, enhanced security via SCC, and a routing layer for external traffic. It also uses operators extensively to automate cluster management.


OCP troubleshooting

In an interview, the ability to walk through a logical “drilling down” process is more important than knowing the exact answer immediately. Here is a classic scenario for an OpenShift Admin role.


The Scenario: “The Disappearing Images”

The Symptom: You are paged because developers cannot push or pull images to the internal OpenShift registry. You run oc get co and see that the image-registry operator is Degraded.

Your Task: Walk me through how you find the root cause and fix it.


Your Mock Troubleshooting Response

1. The High-Level Check

“First, I’ll check the high-level error message provided by the ClusterOperator resource. This usually gives a hint if it’s a configuration issue or a backend failure.”

oc describe clusteroperator image-registry

Interview Result: The message says: “Progressing: Unable to apply resources: storage backend not configured” or “Degraded: error creating registry pod: persistentvolumeclaim “image-registry-storage” not found.”

2. Investigate the Operator Configuration

“Since the error mentions storage, I need to look at the Image Registry’s custom configuration to see where it’s trying to store data.”

oc get configs.imageregistry.operator.openshift.io cluster -o yaml

What you are looking for: Check the spec.storage section. Is it set to pvc, s3, azure, or emptyDir?

3. Deep Dive into the Namespace

“I’ll jump into the openshift-image-registry namespace to check the health of the actual registry pods and the status of the PVC.”

oc get pods,pvc -n openshift-image-registry

Case A (PVC is Pending): “If the PVC is Pending, I’ll run oc describe pvc <pvc-name>. Usually, this reveals that the requested StorageClass doesn’t exist or there is no capacity left in the storage provider.”

Case B (Pod is CrashLoopBackOff): “If the pod is crashing, I’ll check the logs: oc logs <pod_name>. Often, this is a permission issue where the registry container can’t write to the mounted volume due to UID mismatches.”

4. The Fix

“Depending on the find, I would:”

  • If storage was missing: Update the configs.imageregistry to point to a valid StorageClass.
  • If it’s a bare-metal install: Patch the registry to use emptyDir (for non-prod) or configure a manual PV.
  • If it’s Cloud (AWS/Azure): Check if the Operator has the right IAM permissions to create the S3 bucket or Blob storage.

Bonus “Pro” Answer: The Authentication Operator

If you want to impress the interviewer, mention the Authentication Operator and Certificates.

The Scenario: Authentication is degraded because of expired certificates.

The Pro Tip: “I would check the v4-0-config-system-router-certs secret in the openshift-authentication namespace. If the Ingress wildcard cert was manually replaced but the Auth operator wasn’t updated, it will go Degraded because it can no longer validate the OAuth callback URL. I’d fix this by ensuring the router-ca is correctly synced.”

Interviewer Follow-up:

“What if you fix the storage, but the Operator is still showing Degraded after 10 minutes?”

Your Answer: “Sometimes the Operator’s ‘Sync’ loop gets stuck. I would try a graceful restart of the operator pod itself by running oc delete pod -l name=cluster-image-registry-operator -n openshift-image-registry-operator. Since it’s a deployment, a new pod will spin up, re-scan the environment, and should clear the Degraded status if the underlying issue is resolved.”

A “Pending” pod is one of the most common issues you’ll face. In an interview, the key is to show you understand that Pending = A Scheduling Problem, whereas CrashLoopBackOff = An Application Problem.

Here is how to handle this scenario like a seasoned admin.


1. The Core Diagnostic: oc describe

The first thing you must say is: “I check the Events section.” The scheduler is very vocal about why it can’t place a pod.

oc describe pod <pod-name>

Look at the very bottom under “Events”. You will usually see a FailedScheduling warning with a specific reason.


2. Common Reasons (The “Big Four”)

A. Insufficient Resources (CPU/Memory)

  • The Message: 0/6 nodes are available: 3 Insufficient cpu, 3 Insufficient memory.
  • The Reality: Kubernetes schedules based on Requests, not actual usage. Even if a node looks idle, if other pods have “reserved” that space via high requests, the scheduler won’t touch it.
  • The Fix: Scale up the cluster (Autoscaler), add nodes, or ask the developer to lower their resources.requests.

B. Mismatched NodeSelectors / Affinity

  • The Message: 0/6 nodes are available: 6 node(s) didn't match node selector.
  • The Reality: The pod is looking for a label like disktype=ssd, but no nodes have that label.
  • The Fix: Label the nodes or fix the typo in the Deployment YAML.

C. Taints and Tolerations

  • The Message: 0/6 nodes are available: 6 node(s) had taints that the pod didn't tolerate.
  • The Reality: You might have “Infra” nodes or “GPU” nodes that are tainted to keep regular apps off them. If the pod doesn’t have a matching “Toleration,” it’s banned from those nodes.
  • The Fix: Add the correct tolerations to the pod spec.

D. Unbound PersistentVolumeClaims (PVC)

  • The Message: pod has unbound immediate PersistentVolumeClaims.
  • The Reality: The pod is waiting for a disk. Maybe the StorageClass is wrong, or the disk is in US-East-1a while the nodes are in US-East-1b.
  • The Fix: Check the PVC status with oc get pvc.

3. Advanced Troubleshooting: “Resource Quotas”

If oc describe doesn’t show a scheduling error, check the Namespace Quota.

oc get quota

The Scenario: If a project has a limit of 10 CPUs and existing pods are already using 9.5, a new pod requesting 1 CPU will stay Pending because it would violate the project’s “budget,” even if the physical nodes have plenty of room.


4. Summary for the Interviewer

“To summarize, if I see a Pending pod, I follow this hierarchy:”

  1. Check Events: Use oc describe to see the scheduler’s ‘FailedScheduling’ message.
  2. Check Resources: Compare pod requests against node allocatable capacity.
  3. Check Constraints: Verify nodeSelectors, Taints, and Affinity rules.
  4. Check Storage: Ensure the PVC is bound and in the correct zone.
  5. Check Quotas: Ensure the namespace hasn’t hit its hard limit.

In an OpenShift (OCP) admin interview, “Networking” is the area where theory meets reality. By 2026, the focus has shifted entirely to OVN-Kubernetes (the default network provider) and complex traffic patterns like Egress Control.

Here are the most common networking scenarios and questions you’ll encounter.


1. OVN-Kubernetes: The Modern Standard

OpenShift transitioned from the legacy “OpenShift SDN” to OVN-Kubernetes. Interviewers will expect you to know why.

  • Question: Why did OpenShift move to OVN-Kubernetes?
    • Answer: OVN-K is built on Open vSwitch (OVS) and provides better scalability for large clusters, native support for IPv6, and advanced features like Egress IPs and IPsec encryption for pod-to-pod traffic.
  • Troubleshooting Tip: If networking feels “sluggish,” check the OVN Northbound and Southbound databases. These are the “brain” of the network. If they get out of sync, pods might have IPs but can’t talk to each other.
    • Command: oc get pods -n openshift-ovn-kubernetes (Check for failing ovnkube-node or ovnkube-control-plane pods).

2. Egress Traffic: “How do we leave the cluster?”

In enterprise environments, security teams often demand that traffic leaving the cluster has a predictable, static IP for firewall whitelisting.

  • Question: How do you give a specific Project a dedicated external IP?
    • Answer: By using an Egress IP. You assign an IP to a Namespace, and any traffic leaving that namespace to the outside world will appear to come from that specific IP, rather than the node’s IP.
  • The “Egress Firewall” (EgressNetworkPolicy):
    • This is used to prevent pods from reaching specific external destinations (e.g., “Allow pods to talk to the corporate DB, but block all other internet access”).
    • Limit: You can only have one EgressNetworkPolicy per project.

3. Service vs. Route vs. Ingress

This is a classic “bread and butter” question.

  • The Problem: A developer says their application is unreachable from the internet.
  • The Admin Drill:
    1. Check the Route: Does it exist? Is it “Admitted” by the Ingress Controller? (oc get route)
    2. Check the Service: Does the Route point to a valid Service? Does that Service have Endpoints? (oc get endpoints)
    3. Check the Pod: Are the pods running? Are they passing their Readiness Probes? If a probe fails, the endpoint is removed, and the Route will return a 503 Service Unavailable.

4. Common Failure: MTU Mismatches

If you can ping a service but large data transfers (like file uploads) hang or fail, it is almost always an MTU (Maximum Transmission Unit) mismatch.

  • Scenario: You are running OCP on a platform (like Azure or a specific VPC) that uses encapsulation (VXLAN/GENEVE).
  • The Fix: The cluster network MTU must be smaller than the physical network MTU to account for the “header overhead.” If the physical network is 1500, your OVN-K network should usually be 1400.

5. Network Observability (The 2026 Edge)

In 2026, admins don’t just guess; they use the Network Observability Operator.

  • Question: How do you find out which pod is hogging all the bandwidth?
    • Answer: I use the Network Observability Operator (based on Loki). It provides a flow-collector that visualizes traffic in the OCP Console. I can see a “Top Talkers” graph to identify which pod or namespace is causing network congestion.

The “Pro” Interview Summary

If you want to sound like an expert, use these keywords:

  • East-West Traffic: Communication between pods (secured by NetworkPolicies).
  • North-South Traffic: Communication into or out of the cluster (managed by Routes/EgressIP).
  • Hairpinning: When a pod tries to reach itself via the external Route (can cause loops if not configured correctly).

In an OpenShift (OCP) interview, storage is a “Day 2” topic. By 2026, the discussion has moved from simply “how to attach a disk” to software-defined storage and data resilience.

Administrators are expected to understand the abstraction layers between the physical disk and the application.


1. The Core Abstraction (PV, PVC, and StorageClass)

Interviewers will start with the basics to ensure you know the “Kubernetes way” of handling state.

  • StorageClass (SC): The “template” for storage. It defines the provider (AWS EBS, VMware vSphere, Azure Disk) and parameters like reclaimPolicy (Delete vs. Retain).
  • PersistentVolumeClaim (PVC): The developer’s request. “I need 10GB of RWO storage.”
  • PersistentVolume (PV): The actual slice of storage that gets bound to the PVC.

2. OpenShift Data Foundation (ODF)

This is the “Enterprise” way to do storage in OCP. It is based on Ceph and Rook.

  • Question: Why use ODF instead of just direct cloud-native CSI drivers?
    • Answer: ODF provides a unified layer. It gives you Block (RWO), File (RWX), and Object (S3) storage regardless of where the cluster is running. It also enables advanced features like data replication, snapshots, and disaster recovery (DR) across clusters.
  • Key Component (NooBaa): Mention “Multicloud Object Gateway” (NooBaa). It allows you to store data across different cloud providers (e.g., AWS S3 and Azure Blob) while presenting a single S3 endpoint to the app.

3. Access Modes: RWO vs. RWX

This is a frequent “trap” question in interviews.

  • ReadWriteOnce (RWO): Can be mounted by a single node. Best for databases (PostgreSQL, MongoDB).
  • ReadWriteMany (RWX): Can be mounted by many nodes simultaneously. Essential for shared file systems or web servers serving the same static content.
    • Note: Cloud block storage (EBS/Azure Disk) is almost always RWO. To get RWX, you usually need ODF (CephFS) or a managed service like AWS EFS.

4. Critical Admin Tasks & Commands

An interviewer might ask: “A developer says their database is out of space. Walk me through the fix.”

  1. Check Capability: oc get sc <storage-class-name> -o yaml. Look for allowVolumeExpansion: true.
  2. The Fix: Edit the PVC directly: oc edit pvc <pvc-name>.
  3. The Result: If the CSI driver supports it, the PV will expand automatically, and the file system inside the pod will grow without a restart (usually).

5. Advanced: LVM Storage vs. Local Storage Operator

For bare metal or Single Node OpenShift (SNO):

  • LVM Storage Operator (LVMS): The modern (2025/2026) choice. It takes local disks and turns them into a Volume Group, allowing dynamic provisioning of small chunks of local storage.
  • Local Storage Operator (LSO): The “old” way. It binds a whole raw disk to a single PV. It’s less flexible than LVMS because it lacks dynamic resizing.

6. Storage Troubleshooting Checklist

  • PVC stuck in “Pending”:
    • Check oc describe pvc.
    • Cause: No PV available that matches the request, or the StorageClass doesn’t support “Wait For First Consumer” (scheduling issues).
  • Volume stuck in “Terminating”:
    • Cause: A pod is still using the volume. You must find the pod (oc get pods -A | grep <pvc-name>) and delete it before the storage can be released.
  • Multi-Zone Issues:
    • Cause: In AWS/Azure, a volume created in Zone A cannot be mounted by a node in Zone B. This is why “topology-aware” scheduling is critical.

Upgrade OCP cluster

Upgrading OpenShift is the ultimate “Day 2” test for an administrator. Because OCP 4.x is Operator-managed, the upgrade is not just a software update; it is a coordinated orchestration across the entire stack—from the Operating System (RHCOS) to the Control Plane and your worker nodes.

Here are the critical “interview-ready” concepts you need to know for OCP upgrades.


1. The Upgrade Flow (The Order Matters)

When you trigger an upgrade via the Web Console or oc adm upgrade, the cluster follows a strict sequence to ensure stability:

  1. Cluster Version Operator (CVO): First, the CVO updates itself. It is the “brain” that knows what the new version of every other operator should be.
  2. Control Plane Operators: The operators for the API server, Controller Manager, and Scheduler are updated.
  3. Etcd: The database is updated (usually one node at a time to maintain quorum).
  4. Control Plane Nodes: The Machine Config Operator (MCO) drains, updates the OS (RHCOS), and reboots the control plane nodes one by one.
  5. Worker Nodes: Finally, the MCO begins rolling updates through your worker node pools.

2. Update Channels

You must choose a “channel” that dictates how fast you receive updates:

  • Stable: Validated updates that have been out for a while.
  • Fast: Updates that are technically ready but might still be gaining “field experience.”
  • Candidate: Early access for testing.
  • EUS (Extended Update Support): Specific even-numbered versions (e.g., 4.14, 4.16, 4.18) that allow you to skip a minor version during upgrades (e.g., 4.14 → 4.16) to reduce the number of reboots.

3. The “Canary” Strategy (Custom MCPs)

In a large production cluster, you don’t want all 100 worker nodes to start rebooting at once.

  • MachineConfigPool (MCP) Pausing: You can “pause” a pool of nodes. This allows the Control Plane to upgrade, but keeps the Workers on the old version until you are ready.
  • Canary Testing: You can create a small “canary” MCP with only 2–3 nodes. Unpause this pool first, verify your apps work on the new version, and then unpause the rest of the cluster.

4. Critical Troubleshooting Questions

An interviewer will likely give you these scenarios:

  • “The upgrade is stuck at 57%.” What do you do?
    • Check ClusterOperators: Run oc get co. Look for any operator where AVAILABLE=False or PROGRESSING=True.
    • Check Node Status: Run oc get nodes. If a node is SchedulingDisabled, the MCO might be struggling to drain a pod (e.g., a pod without a PDB or a local volume).
  • “Can you roll back an OpenShift upgrade?”
    • NO. This is a trick question. OpenShift does not support rollbacks. Because the etcd database schema changes during upgrades, you can only “roll forward” by fixing the issue or, in a total disaster, by restoring the cluster from an etcd backup taken before the upgrade.

5. Best Practices for Admins

  • Check the Update Graph: Always use the Red Hat OpenShift Update Graph tool to ensure there is a supported path between your current version and your target.
  • Review Alerts: Clear all critical alerts before starting. If the cluster isn’t healthy before the upgrade, it definitely won’t be healthy after.
  • Pod Disruption Budgets (PDB): Ensure developers have set up PDBs so the upgrade doesn’t accidentally take down all replicas of a critical service at once.

The Canary Update strategy allows you to test an OpenShift upgrade on a small subset of nodes before rolling it out to the entire cluster. This is the gold standard for high-availability environments.

Here is the exact administrative workflow and commands you would use.


Step 1: Create a “Canary” MachineConfigPool (MCP)

First, you need a pool that targets only the nodes you want to test.

  1. Label your canary nodes:>
  2. Create the MCP:Save this as canary-mcp.yaml and run oc create -f canary-mcp.yaml.YAMLapiVersion: machineconfiguration.openshift.io/v1 kind: MachineConfigPool metadata: name: worker-canary spec: machineConfigSelector: matchExpressions: - {key: machineconfiguration.openshift.io/role, operator: In, values: [worker, worker-canary]} nodeSelector: matchLabels: node-role.kubernetes.io/worker-canary: ""

Step 2: Pause the Remaining Worker Pools

Before triggering the cluster upgrade, you must “pause” the main worker pool. This tells the Machine Config Operator (MCO): “Update the Control Plane, but do NOT touch these worker nodes yet.”

# Pause the standard worker pool
oc patch mcp/worker --type='merge' -p '{"spec":{"paused":true}}'

Step 3: Trigger the Upgrade

Now, start the cluster upgrade as usual (via Console or CLI).

oc adm upgrade --to=4.16.x

What happens now?

  • The Control Plane upgrades and reboots.
  • The Worker-Canary pool (which is NOT paused) updates and reboots.
  • The Worker pool (which IS paused) stays on the old version.

Step 4: Verify and Complete the Rollout

Once the Canary nodes are successfully updated and your applications are verified, you can roll out the update to the rest of the cluster by unpausing the main pool.

  1. Check status:Bashoc get mcp You should see worker-canary is UPDATED, but worker shows UPDATED=False.
  2. Unpause the main pool:

Critical Interview Warning: The “Pause” Alert

If an interviewer asks: “Is it safe to leave an MCP paused indefinitely?”

  • Answer: No. Starting in OCP 4.11+, a critical alert will fire if a pool is paused for more than 1 hour during an update.
  • Reason: Pausing an MCP prevents Certificate Rotation. If you leave it paused too long (usually >24 hours during an upgrade cycle), the nodes’ Kubelet certificates may expire, and the nodes will go NotReady, potentially breaking the cluster.

In OpenShift, Operators are the software managers that keep your cluster healthy. When an operator fails, it shows up as Degraded. As an admin, your job is to find the “who, why, and how” of the failure.

Here is the professional troubleshooting sequence for an OCP Operator failure.

1. Identify the Failing Operator

The first step is always to find which operator is complaining.

# Get the status of all cluster operators
oc get clusteroperators (or 'oc get co')

What to look for: Look for DEGRADED=True or AVAILABLE=False. Common ones that fail are authentication, console, image-registry, and machine-config.


2. The Investigation Sequence

Once you identify the degraded operator (e.g., authentication), follow this 4-step drill:

A. Describe the ClusterOperator

This gives you the “high-level” reason for the failure (often a specific error message from the operator itself).

oc describe clusteroperator authentication

B. Check the Operator’s Namespace

Every operator has its own namespace (usually starting with openshift-).

# Find the namespace and pods
oc get pods -A | grep authentication

C. Inspect the Pod Logs

The operator is just a pod. If it’s failing, it will tell you why in its logs.

oc logs -n openshift-authentication-operator deployment/authentication-operator

D. Check Events

Sometimes the problem isn’t the code, but the infrastructure (e.g., “Failed to pull image” or “Insufficient CPU”).

oc get events -n openshift-authentication-operator --sort-by='.lastTimestamp'

3. Common “Admin-Level” Failure Scenarios

In an interview, you can shine by mentioning these specific, real-world failures:

Failing OperatorTypical ReasonThe Fix
Machine-ConfigNode can’t drain because of a Pod Disruption Budget (PDB).Manually move the pod or adjust the PDB temporarily.
AuthenticationEtcd is slow or the internal OAuth secret is out of sync.Check etcd health; sometimes deleting the operator pod to force a restart helps.
Image-RegistryThe backend storage (S3, Azure Blob, NFS) is full or disconnected.Check the configs.imageregistry.operator.openshift.io resource and storage backend.
IngressPort 80/443 is blocked on the LoadBalancer or the Router deployment is scaling.Check the IngressController custom resource and cloud provider LB status.

4. The “Nuclear” Option: Must-Gather

If the API is behaving so poorly that you can’t even run these commands, or if you need to open a Red Hat Support ticket, use Must-Gather.

oc adm must-gather

Must-Gather is an admin’s best friend. It creates a local directory with every log, secret (redacted), and config file from the cluster. You can then use grep or ag locally to find the needle in the haystack.


5. Node-Level Debugging (When the API is down)

If the operator is failing because the node itself is unresponsive, you must go under the hood:

# Access the node via a debug pod (preferred)
oc debug node/<node-name>
# Once inside the debug pod, switch to host binaries
chroot /host
# Check the container runtime (CRI-O)
crictl ps
crictl logs <container_id>