diff --git a/2026/day-36/backend/Dockerfile b/2026/day-36/backend/Dockerfile
new file mode 100755
index 0000000000..4b8dfdad91
--- /dev/null
+++ b/2026/day-36/backend/Dockerfile
@@ -0,0 +1,14 @@
+# Taking the base image
+FROM node:18-alpine
+# Set the working directory
+WORKDIR /app
+#Copy the package
+COPY package.json .
+# Run the code to install all
+RUN npm install
+# Copy the code in my container directory from the local
+COPY . .
+# Expose the port
+EXPOSE 5000
+# Serve the code
+CMD ["node","index.js"]
diff --git a/2026/day-36/backend/index.js b/2026/day-36/backend/index.js
new file mode 100755
index 0000000000..3980f2211e
--- /dev/null
+++ b/2026/day-36/backend/index.js
@@ -0,0 +1,45 @@
+const express = require('express');
+const mysql = require('mysql2');
+const cors = require('cors');
+const app = express();
+app.use(cors());
+app.use(express.json());
+const db = mysql.createPool({
+ host: process.env.DB_HOST || 'mysql',
+ user: process.env.DB_USER || 'root',
+ password: process.env.DB_PASSWORD || 'rootpassword',
+ database: process.env.DB_NAME || 'tododb',
+ waitForConnections: true,
+ connectionLimit: 10,
+});
+app.get('/health', (req, res) => res.json({ status: 'ok' }));
+app.get('/todos', (req, res) => {
+ db.query('SELECT * FROM todos ORDER BY created_at DESC', (err, results) => {
+ if (err) return res.status(500).json({ error: err.message });
+ res.json(results);
+ });
+});
+app.post('/todos', (req, res) => {
+ const { title } = req.body;
+ if (!title) return res.status(400).json({ error: 'Title is required' });
+ db.query('INSERT INTO todos (title) VALUES (?)', [title], (err, result) => {
+ if (err) return res.status(500).json({ error: err.message });
+ res.status(201).json({ id: result.insertId, title, completed: false });
+ });
+});
+app.put('/todos/:id', (req, res) => {
+ const { id } = req.params;
+ db.query('UPDATE todos SET completed = NOT completed WHERE id = ?', [id], (err) => {
+ if (err) return res.status(500).json({ error: err.message });
+ res.json({ message: 'Updated' });
+ });
+});
+app.delete('/todos/:id', (req, res) => {
+ const { id } = req.params;
+ db.query('DELETE FROM todos WHERE id = ?', [id], (err) => {
+ if (err) return res.status(500).json({ error: err.message });
+ res.json({ message: 'Deleted' });
+ });
+});
+const PORT = process.env.PORT || 5000;
+app.listen(PORT, () => console.log('Backend running on port ' + PORT));
diff --git a/2026/day-36/backend/package.json b/2026/day-36/backend/package.json
new file mode 100755
index 0000000000..14c368adc3
--- /dev/null
+++ b/2026/day-36/backend/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "todo-backend",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": { "start": "node index.js" },
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.18.2",
+ "mysql2": "^3.6.0"
+ }
+}
diff --git a/2026/day-36/day-36-docker-project.md b/2026/day-36/day-36-docker-project.md
new file mode 100644
index 0000000000..9ea4924058
--- /dev/null
+++ b/2026/day-36/day-36-docker-project.md
@@ -0,0 +1,57 @@
+# Day 36 - Docker Project: Dockerized Todo App
+
+## Project Overview
+A full-stack Todo application fully containerized using Docker and Docker Compose.
+The app consists of three services: React frontend, Node.js backend, and MySQL database.
+
+## Architecture
+- **Frontend**: React app served via Nginx on port 3000
+- **Backend**: Node.js + Express REST API on port 5000
+- **Database**: MySQL 8 on port 3306
+- All services connected via custom Docker bridge network
+
+## Docker Hub Images
+- Frontend: mohammadadnankhan/todo-frontend
+- Backend: mohammadadnankhan/todo-backend
+- MySQL: mohammadadnankhan/mysql-cont
+
+## How to Run
+```bash
+git clone git@github.com:AddyKhan257/To-Do-App.git
+cd To-Do-App
+docker-compose up -d
+```
+Open browser: http://localhost:3000
+
+## Dockerfile Decisions
+### Backend
+- Used node:18-alpine for small image size
+- Copied package.json first to leverage Docker layer caching
+- Used CMD instead of ENTRYPOINT for flexibility
+
+### Frontend (Multi-stage)
+- Stage 1: node:18-alpine to build React app
+- Stage 2: nginx:alpine to serve static files
+- Final image reduced from 800MB to 25MB
+
+## docker-compose Decisions
+- Custom bridge network for container communication
+- Healthcheck on MySQL so backend waits for DB
+- depends_on with condition: service_healthy for startup order
+- init.sql mounted to docker-entrypoint-initdb.d for automatic DB setup
+- Named volume for MySQL data persistence
+
+## Challenges
+- Fixed DB_HOST to use service name instead of localhost
+- Debugged database name mismatch between init.sql and compose file
+- Fixed WSL permission issues with git
+
+## Key Commands
+```bash
+docker build -t todo-backend ./backend
+docker build -t todo-frontend ./frontend
+docker-compose up -d
+docker-compose logs backend
+docker-compose down -v
+docker ps
+```
diff --git a/2026/day-36/docker-compose.yml b/2026/day-36/docker-compose.yml
new file mode 100755
index 0000000000..71c6ba6d0a
--- /dev/null
+++ b/2026/day-36/docker-compose.yml
@@ -0,0 +1,60 @@
+version: "3.8"
+
+services:
+
+
+ mysql:
+ container_name: mysql_todoapp
+ image: mohammadadnankhan/mysql-cont
+ environment:
+ MYSQL_ROOT_PASSWORD: Test@123
+ MYSQL_DATABASE: tododb
+ volumes:
+ - mysql-vol:/var/lib/mysql
+ - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ networks:
+ - todo-app-nw
+ ports:
+ - "3306:3306"
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-pTest@123"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+
+
+ backend:
+ container_name: todo-backend
+ image: mohammadadnankhan/todo-backend
+ environment:
+ DB_HOST: mysql
+ DB_USER: root
+ DB_PASSWORD: Test@123
+ DB_NAME: tododb
+ networks:
+ - todo-app-nw
+ ports:
+ - "5000:5000"
+ depends_on:
+ mysql:
+ condition: service_healthy
+
+
+ frontend:
+ container_name: todo-frontend
+ image: mohammadadnankhan/todo-frontend
+ ports:
+ - "3000:80"
+ networks:
+ - todo-app-nw
+ depends_on:
+ - backend
+
+
+networks:
+ todo-app-nw:
+ driver: bridge
+
+volumes:
+ mysql-vol:
\ No newline at end of file
diff --git a/2026/day-36/frontend/Dockerfile b/2026/day-36/frontend/Dockerfile
new file mode 100755
index 0000000000..48746bcf73
--- /dev/null
+++ b/2026/day-36/frontend/Dockerfile
@@ -0,0 +1,25 @@
+##STAGE 1##
+##--BUILD--##
+
+# taking the base image
+FROM node:18-alpine AS BUILD
+# creating the directory for the container
+WORKDIR /app
+# Copy the package.json in container directory from local to avoid repeating the downloads of dependencies
+COPY package.json .
+# Run the commands to install all
+RUN npm install
+# Copy the code from my local
+COPY . .
+# This create /app/build folder containing pure HTML, CSS, JS files β no Node.js needed anymore!
+RUN npm run build
+
+##STAGE 2##
+##SERVE##
+
+# Start fresh with a tiny Nginx image. Everything from Stage 1 is thrown away except what we copy!
+FROM nginx:alpine
+# Copy ONLY the built files from Stage 1 into Nginx
+COPY --from=BUILD /app/build /usr/share/nginx/html
+# Nginx serves on port 80.
+EXPOSE 80
diff --git a/2026/day-36/frontend/package.json b/2026/day-36/frontend/package.json
new file mode 100755
index 0000000000..1aba3ba50f
--- /dev/null
+++ b/2026/day-36/frontend/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "todo-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-scripts": "5.0.1"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build"
+ },
+ "browserslist": {
+ "production": [">0.2%", "not dead", "not op_mini all"],
+ "development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
+ }
+}
diff --git a/2026/day-36/frontend/public/index.html b/2026/day-36/frontend/public/index.html
new file mode 100755
index 0000000000..81fac08a59
--- /dev/null
+++ b/2026/day-36/frontend/public/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Todo App
+
+
+
+
+
diff --git a/2026/day-36/frontend/src/App.js b/2026/day-36/frontend/src/App.js
new file mode 100755
index 0000000000..a8d52a3b8c
--- /dev/null
+++ b/2026/day-36/frontend/src/App.js
@@ -0,0 +1,52 @@
+import { useState, useEffect } from "react";
+const API = "http://localhost:5000";
+export default function App() {
+ const [todos, setTodos] = useState([]);
+ const [input, setInput] = useState("");
+ const fetchTodos = () =>
+ fetch(`${API}/todos`).then((r) => r.json()).then(setTodos);
+ useEffect(() => { fetchTodos(); }, []);
+ const addTodo = () => {
+ if (!input.trim()) return;
+ fetch(`${API}/todos`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ title: input }),
+ }).then(() => { setInput(""); fetchTodos(); });
+ };
+ const toggleTodo = (id) =>
+ fetch(`${API}/todos/${id}`, { method: "PUT" }).then(fetchTodos);
+ const deleteTodo = (id) =>
+ fetch(`${API}/todos/${id}`, { method: "DELETE" }).then(fetchTodos);
+ return (
+
+
π Todo App
+
Dockerized with React + Node.js + MySQL
+
+ setInput(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && addTodo()}
+ placeholder="Add a new task..."
+ style={{ flex: 1, padding: "10px 14px", borderRadius: 8, border: "1px solid #ddd", fontSize: 15 }}
+ />
+
+
+ {todos.length === 0 &&
No todos yet. Add one above!
}
+ {todos.map((todo) => (
+
+ toggleTodo(todo.id)}
+ style={{ cursor: "pointer", textDecoration: todo.completed ? "line-through" : "none", color: todo.completed ? "#aaa" : "#333", flex: 1 }}>
+ {todo.completed ? "β
" : "β¬"} {todo.title}
+
+
+
+ ))}
+
+ );
+}
diff --git a/2026/day-36/frontend/src/index.js b/2026/day-36/frontend/src/index.js
new file mode 100755
index 0000000000..c096da5726
--- /dev/null
+++ b/2026/day-36/frontend/src/index.js
@@ -0,0 +1,5 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render();
diff --git a/2026/day-36/mysql/init.sql b/2026/day-36/mysql/init.sql
new file mode 100755
index 0000000000..893f8c5951
--- /dev/null
+++ b/2026/day-36/mysql/init.sql
@@ -0,0 +1,14 @@
+CREATE DATABASE IF NOT EXISTS tododb;
+USE tododb;
+
+CREATE TABLE IF NOT EXISTS todos (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ title VARCHAR(255) NOT NULL,
+ completed BOOLEAN DEFAULT FALSE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+INSERT INTO todos (title) VALUES
+ ('Learn Docker'),
+ ('Write Dockerfile'),
+ ('Set up Docker Compose');
diff --git a/2026/day-37/day-37-revision.md b/2026/day-37/day-37-revision.md
new file mode 100644
index 0000000000..6f30e31b97
--- /dev/null
+++ b/2026/day-37/day-37-revision.md
@@ -0,0 +1,220 @@
+# π Day 37 - Docker Revision & Self-Check
+> By Mohammad Adnan Khan | 90DaysOfDevOps
+
+---
+
+## β
Self-Check Questions & Answers
+
+### Q1: What is the difference between a Docker Image and a Container?
+
+**Answer:**
+- **Docker Image** is a blueprint/recipe used to create containers. It is read-only and contains everything needed to run an app β code, runtime, dependencies, and configs. Like a class in programming.
+- **Docker Container** is a running instance of an image. It is lightweight because it shares the host OS kernel instead of using a hypervisor like a VM. Like an object created from a class.
+
+> π‘ Key difference: Image = static blueprint. Container = running process.
+
+---
+
+### Q2: What is a Dockerfile? Name 5 instructions with their purpose.
+
+**Answer:**
+A Dockerfile is a text file with instructions to build a Docker image step by step.
+
+| Instruction | Purpose |
+|---|---|
+| `FROM` | Sets the base image (e.g., node:18-alpine) |
+| `WORKDIR` | Creates and sets working directory inside container |
+| `COPY` | Copies files from host machine into container |
+| `RUN` | Executes commands during image build (e.g., npm install) |
+| `CMD` | Sets default command to run when container starts |
+| `EXPOSE` | Documents which port the container listens on |
+
+---
+
+### Q3: What is Docker Layer Caching? Why do we COPY package.json before COPY . .?
+
+**Answer:**
+Docker builds images in layers and caches each layer. If a layer hasn't changed, Docker reuses the cached version instead of rebuilding it.
+
+We copy `package.json` first because:
+1. `package.json` contains the list of all dependencies
+2. If we `COPY . .` first, any code change triggers `npm install` again (slow!)
+3. By copying `package.json` first β `npm install` layer is cached β only reruns when dependencies actually change
+4. Then `COPY . .` copies our code changes β much faster builds!
+```dockerfile
+COPY package.json . # cached unless dependencies change
+RUN npm install # cached! only reruns if package.json changes
+COPY . . # only this layer rebuilds when code changes
+```
+
+---
+
+### Q4: What is the difference between CMD and ENTRYPOINT?
+
+**Answer:**
+
+| | CMD | ENTRYPOINT |
+|---|---|---|
+| Can override? | β
Yes | β No |
+| Used for | Default commands, web servers | Executable containers |
+| Override how | `docker run myapp npm test` | Must use `--entrypoint` flag |
+
+- **CMD** = a suggestion. "Run this by default but you can change it"
+- **ENTRYPOINT** = a rule. "Always run this no matter what"
+
+We used `CMD ["node","index.js"]` in our backend because we want flexibility to override it for debugging.
+
+---
+
+### Q5: What is a Multi-Stage Dockerfile? Why did we use it for frontend?
+
+**Answer:**
+A multi-stage Dockerfile uses multiple `FROM` statements. Each `FROM` starts a new stage.
+
+**Why for frontend:**
+- Stage 1 uses `node:18-alpine` to BUILD the React app β creates `/app/build` folder with HTML/CSS/JS
+- Stage 2 uses `nginx:alpine` to SERVE those built files
+- Final image only contains Stage 2 (tiny Nginx + built files)
+- Result: ~25MB instead of ~800MB!
+```dockerfile
+# Stage 1 - Build (thrown away after)
+FROM node:18-alpine AS BUILD
+RUN npm run build
+
+# Stage 2 - Serve (final image)
+FROM nginx:alpine
+COPY --from=BUILD /app/build /usr/share/nginx/html
+```
+
+---
+
+### Q6: What is Docker Compose and why do we need it?
+
+**Answer:**
+Docker Compose is a tool to define and run multi-container applications using a single YAML file (`docker-compose.yml`).
+
+Instead of running 3 separate `docker run` commands for frontend, backend, and MySQL β we write one `docker-compose.yml` and run:
+```bash
+docker-compose up
+```
+
+It handles:
+- Building images
+- Creating networks
+- Setting up volumes
+- Passing environment variables
+- Managing startup order with `depends_on`
+
+---
+
+### Q7: Why is DB_HOST: mysql and not DB_HOST: localhost?
+
+**Answer:**
+Each Docker container has its own isolated network namespace with its own `localhost`. If backend uses `DB_HOST: localhost`, it looks for MySQL **inside itself** β which doesn't exist!
+
+In Docker networks, containers are reachable by their **service name**. Docker acts as a DNS server β `mysql` resolves to the IP of the MySQL container automatically.
+```yaml
+DB_HOST: mysql # β
finds MySQL container by service name
+DB_HOST: localhost # β looks inside backend container itself!
+```
+
+---
+
+### Q8: What is a Docker Volume? Why did only MySQL need one?
+
+**Answer:**
+A Docker volume is persistent storage that exists outside the container. Data stored in a volume survives container deletion and restarts.
+
+**Why only MySQL:**
+- **MySQL** writes all data (tables, rows) to disk β without volume, deleting container = losing all data forever! β
+- **Backend** is stateless β processes requests and sends data to MySQL, nothing stored locally β
+- **Frontend** static files are baked into the image β no dynamic data to persist β
+
+> Rule: Any service that writes data to disk needs a volume!
+
+---
+
+### Q9: What does this line do in docker-compose.yml?
+```yaml
+- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
+```
+
+**Answer:**
+This is a **bind mount** that maps a local file into a special MySQL folder.
+
+- `./mysql/init.sql` β SQL file on our local machine
+- `/docker-entrypoint-initdb.d/` β Special folder inside MySQL container. Any `.sql` file placed here is **automatically executed** by MySQL on first startup.
+
+So when MySQL container starts for the first time:
+1. It finds `init.sql` in that special folder
+2. Runs it automatically
+3. Creates `tododb` database β
+4. Creates `todos` table β
+5. Inserts sample data β
+
+---
+
+### Q10: What is a Healthcheck in Docker Compose? Why did we add it to MySQL?
+
+**Answer:**
+A healthcheck periodically checks if a service is actually ready to accept connections (not just started).
+```yaml
+healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+```
+
+**Why MySQL needs it:**
+- `depends_on` alone only waits for the container to START
+- MySQL takes time to initialize even after starting
+- Without healthcheck β backend tries to connect before MySQL is ready β crash! β
+- With healthcheck β backend waits until MySQL passes health check β connects successfully β
+
+We used `condition: service_healthy` in backend's `depends_on` to ensure proper startup order.
+
+---
+
+## π My Revision Score
+
+| Question | Topic | Score |
+|---|---|---|
+| Q1 | Image vs Container | β
10/10 |
+| Q2 | Dockerfile Instructions | β
10/10 |
+| Q3 | Layer Caching | β
9/10 |
+| Q4 | CMD vs ENTRYPOINT | β οΈ 6/10 (forgot details) |
+| Q5 | Multi-Stage Build | β
9/10 |
+| Q6 | Docker Compose | β
10/10 |
+| Q7 | DB_HOST service name | β
10/10 |
+| Q8 | Volumes | β
8/10 |
+| Q9 | init.sql mount | β
9/10 |
+| Q10 | Healthcheck | β
10/10 |
+
+**Overall: 9.1/10** π₯
+
+---
+
+## π― Key Takeaways from Days 29-36
+
+1. Docker solves "works on my machine" problem by packaging app + environment together
+2. Always use Alpine base images for smaller image sizes
+3. Layer caching = copy dependencies first, then code
+4. Multi-stage builds = build in big image, serve in tiny image
+5. Never use localhost for inter-container communication β use service names!
+6. Volumes = only for stateful services (databases)
+7. Healthchecks = ensure services are READY not just started
+8. docker-compose = one command to rule them all π₯
+
+---
+
+## π References
+- GitHub: [AddyKhan257/To-Do-App](https://github.com/AddyKhan257/To-Do-App)
+- Docker Hub: [mohammadadnankhan](https://hub.docker.com/u/mohammadadnankhan)
+- LinkedIn: [Mohammad Adnan Khan](https://www.linkedin.com/in/mohammad-adnan-khan-8099802b1)
+
+---
+
+*Day 37 of 90DaysOfDevOps Challenge*
+*#90DaysOfDevOps #DevOpsKaJosh #TrainWithShubham*
diff --git a/2026/day-37/docker-cheatsheet.md b/2026/day-37/docker-cheatsheet.md
new file mode 100644
index 0000000000..dd7367a8f6
--- /dev/null
+++ b/2026/day-37/docker-cheatsheet.md
@@ -0,0 +1,267 @@
+# π³ Docker Cheat Sheet
+> By Mohammad Adnan Khan | 90DaysOfDevOps Day 37
+
+---
+
+## π¦ Core Concepts
+
+| Concept | Description |
+|---|---|
+| **Image** | Blueprint/recipe to create containers. Like a class in programming. |
+| **Container** | Running instance of an image. Like an object from a class. |
+| **Dockerfile** | Instructions to build a Docker image. |
+| **Docker Compose** | Tool to run multiple containers together using a YAML file. |
+| **Volume** | Persistent storage that survives container restarts/deletions. |
+| **Network** | Allows containers to communicate with each other. |
+| **Registry** | Storage for Docker images. (Docker Hub is the default) |
+
+---
+
+## π οΈ Dockerfile Instructions
+
+| Instruction | Purpose | Example |
+|---|---|---|
+| `FROM` | Set base image | `FROM node:18-alpine` |
+| `WORKDIR` | Set working directory inside container | `WORKDIR /app` |
+| `COPY` | Copy files from host to container | `COPY package.json .` |
+| `RUN` | Execute command during build | `RUN npm install` |
+| `EXPOSE` | Document which port container listens on | `EXPOSE 5000` |
+| `CMD` | Default command when container starts (overridable) | `CMD ["node","index.js"]` |
+| `ENTRYPOINT` | Command that always runs (not overridable) | `ENTRYPOINT ["nginx"]` |
+| `ENV` | Set environment variables | `ENV NODE_ENV=production` |
+| `ARG` | Build-time variables | `ARG VERSION=1.0` |
+| `VOLUME` | Create mount point for volumes | `VOLUME /data` |
+
+---
+
+## β‘ CMD vs ENTRYPOINT
+
+| | CMD | ENTRYPOINT |
+|---|---|---|
+| **Can override?** | β
Yes easily | β Not easily |
+| **Used for** | Default start commands | When container = executable |
+| **Override example** | `docker run myapp npm test` | Must use `--entrypoint` flag |
+| **Best for** | Web servers, APIs | CLI tools, scripts |
+
+---
+
+## ποΈ Multi-Stage Build
+```dockerfile
+# Stage 1 - BUILD (big image, just for building)
+FROM node:18-alpine AS BUILD
+WORKDIR /app
+COPY package.json .
+RUN npm install
+COPY . .
+RUN npm run build
+
+# Stage 2 - SERVE (tiny image, just for serving)
+FROM nginx:alpine
+COPY --from=BUILD /app/build /usr/share/nginx/html
+EXPOSE 80
+```
+
+**Why?**
+- Without multi-stage β ~800MB image π±
+- With multi-stage β ~25MB image β
+- Stage 1 builds, Stage 2 serves. Only Stage 2 ends up in final image!
+
+---
+
+## β‘ Docker Layer Caching
+```dockerfile
+# β
CORRECT - Cache friendly
+COPY package.json . # Layer 1 - only changes if dependencies change
+RUN npm install # Layer 2 - cached! only reruns if package.json changes
+COPY . . # Layer 3 - your code changes here
+
+# β WRONG - Slow builds
+COPY . . # copies everything first
+RUN npm install # reruns every time any file changes!
+```
+
+**Rule:** Copy dependency files first β install β copy code!
+
+---
+
+## π³ Essential Docker Commands
+
+### Images
+```bash
+docker build -t myimage . # Build image from Dockerfile
+docker build -t myimage ./folder # Build from specific folder
+docker images # List all images
+docker rmi myimage # Remove image
+docker rmi $(docker images -q) -f # Remove ALL images
+docker pull nginx # Pull image from Docker Hub
+docker push username/myimage # Push image to Docker Hub
+```
+
+### Containers
+```bash
+docker run myimage # Run container
+docker run -d myimage # Run in background (detached)
+docker run -p 3000:80 myimage # Map port host:container
+docker run -e DB_HOST=mysql myimage # Pass environment variable
+docker ps # List running containers
+docker ps -a # List all containers
+docker stop container_id # Stop container
+docker rm container_id # Remove container
+docker logs container_id # See container logs
+docker exec -it container_id sh # Enter container shell
+```
+
+### Docker Compose
+```bash
+docker-compose up # Start all services
+docker-compose up -d # Start in background
+docker-compose down # Stop and remove containers
+docker-compose down -v # Stop, remove containers + volumes
+docker-compose logs backend # See logs of specific service
+docker-compose ps # List compose services
+docker-compose build # Build/rebuild services
+docker-compose restart # Restart all services
+```
+
+### Volumes & Networks
+```bash
+docker volume ls # List volumes
+docker volume rm volume_name # Remove volume
+docker network ls # List networks
+docker network inspect network_name # Inspect network
+```
+
+---
+
+## π docker-compose.yml Structure
+```yaml
+version: "3.8"
+
+services:
+ mysql:
+ image: mysql:8
+ container_name: mysql_app
+ environment:
+ MYSQL_ROOT_PASSWORD: password
+ MYSQL_DATABASE: mydb
+ volumes:
+ - mysql-vol:/var/lib/mysql
+ - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
+ networks:
+ - app-network
+ ports:
+ - "3306:3306"
+ healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+
+ backend:
+ build: ./backend
+ container_name: app-backend
+ environment:
+ DB_HOST: mysql
+ DB_USER: root
+ DB_PASSWORD: password
+ DB_NAME: mydb
+ networks:
+ - app-network
+ ports:
+ - "5000:5000"
+ depends_on:
+ mysql:
+ condition: service_healthy
+
+ frontend:
+ build: ./frontend
+ container_name: app-frontend
+ ports:
+ - "3000:80"
+ networks:
+ - app-network
+ depends_on:
+ - backend
+
+networks:
+ app-network:
+ driver: bridge
+
+volumes:
+ mysql-vol:
+```
+
+---
+
+## π Docker Networking
+
+| Network Type | Description | Use Case |
+|---|---|---|
+| **bridge** | Default. Containers on same host communicate | Most common, local dev |
+| **host** | Container shares host network | Performance critical apps |
+| **none** | No network | Isolated containers |
+
+**Key Rule:** Containers on same network talk using **service names** not `localhost`!
+```yaml
+DB_HOST: mysql # β
service name
+DB_HOST: localhost # β looks inside itself!
+```
+
+---
+
+## πΎ Docker Volumes
+
+| Type | Description | Example |
+|---|---|---|
+| **Named Volume** | Managed by Docker, persists data | `mysql-vol:/var/lib/mysql` |
+| **Bind Mount** | Maps host path to container path | `./init.sql:/docker-entrypoint-initdb.d/init.sql` |
+
+**When to use volumes:**
+- β
Databases (MySQL, MongoDB, PostgreSQL)
+- β Frontend (static files in image)
+- β Backend (stateless, data goes to DB)
+
+---
+
+## π₯ Healthcheck
+```yaml
+healthcheck:
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+```
+
+**Why?** `depends_on` alone only waits for container to START, not to be READY!
+
+---
+
+## π Key Concepts to Remember
+
+1. **Image vs Container** β Image is blueprint, Container is running instance
+2. **Layer Caching** β Copy dependencies first, then code = faster builds
+3. **Multi-stage** β Build in big image, serve in tiny image = smaller final image
+4. **DB_HOST = service name** β Each container has its own localhost!
+5. **Volumes** β Only for services that write data (databases)
+6. **Healthcheck** β Ensures service is READY, not just started
+7. **Bridge Network** β Containers communicate using service names as DNS
+
+---
+
+## π Quick Reference - Todo App
+```bash
+docker build -t todo-backend ./backend
+docker build -t todo-frontend ./frontend
+docker-compose up -d
+docker ps
+docker-compose logs backend
+docker-compose down -v
+docker rmi $(docker images -q) -f
+```
+
+---
+
+*Made with β€οΈ during 90DaysOfDevOps Challenge*
+*GitHub: [@AddyKhan257](https://github.com/AddyKhan257)*
diff --git a/2026/day-38/day-38-yaml.md b/2026/day-38/day-38-yaml.md
new file mode 100644
index 0000000000..9cfe20dac8
--- /dev/null
+++ b/2026/day-38/day-38-yaml.md
@@ -0,0 +1,171 @@
+# π Day 38 β YAML Basics
+> By Mohammad Adnan Khan | 90DaysOfDevOps
+
+---
+
+## π What is YAML?
+
+YAML stands for **"YAML Ain't Markup Language"**.
+It is a human-readable data serialization language used to write configuration files.
+Every CI/CD pipeline, Docker Compose file, and Kubernetes manifest is written in YAML.
+
+---
+
+## β
Task 1: Key-Value Pairs
+
+**person.yaml:**
+```yaml
+name: Mohammad Adnan Khan
+role: Aspiring DevOps Engineer
+experience_years: 0
+learning: true
+```
+
+**What I learned:**
+- YAML key-value pairs are written as `key: value`
+- Booleans are written as `true` or `false` (no quotes)
+- Strings don't need quotes unless they contain special characters
+
+---
+
+## β
Task 2: Lists
+
+**Updated person.yaml:**
+```yaml
+name: Mohammad Adnan Khan
+role: Aspiring DevOps Engineer
+experience_years: 0
+learning: true
+
+tools:
+ - Docker
+ - Kubernetes
+ - Git
+ - Linux
+ - AWS
+
+hobbies: [coding, learning, cricket]
+```
+
+**Two ways to write a list in YAML:**
+1. **Block style** β each item on new line with `-` (like `tools`)
+2. **Inline style** β comma separated in `[]` (like `hobbies`)
+
+---
+
+## β
Task 3: Nested Objects
+
+**server.yaml:**
+```yaml
+server:
+ name: prod-server-01
+ ip: 192.168.1.10
+ port: 8080
+
+database:
+ host: db.internal
+ name: tododb
+ credentials:
+ user: root
+ password: Test@123
+```
+
+**What I learned:**
+- Nested objects use indentation (2 spaces per level)
+- Never use tabs β only spaces!
+- When I added a tab instead of spaces, yamllint showed:
+ `found character '\t' that cannot start any token`
+
+---
+
+## β
Task 4: Multi-line Strings
+
+**Updated server.yaml:**
+```yaml
+server:
+ name: prod-server-01
+ ip: 192.168.1.10
+ port: 8080
+
+database:
+ host: db.internal
+ name: tododb
+ credentials:
+ user: root
+ password: Test@123
+
+startup_script: |
+ echo "Starting server..."
+ cd /app
+ npm install
+ node index.js
+
+shutdown_script: >
+ echo "Stopping server..."
+ cd /app
+ pm2 stop all
+```
+
+**When to use `|` vs `>`:**
+- **`|` (block style)** β preserves newlines β use for scripts, code, logs where line breaks matter
+- **`>` (fold style)** β folds into one line β use for long descriptions where line breaks don't matter
+
+---
+
+## β
Task 5: Validate Your YAML
+
+- Validated `person.yaml` on yamllint.com β
+- Validated `server.yaml` on yamllint.com β
+- Intentionally broke indentation β got error:
+ `mapping values are not allowed here`
+- Fixed it and validated again β
+
+---
+
+## β
Task 6: Spot the Difference
+```yaml
+# Block 1 - correct
+name: devops
+tools:
+ - docker
+ - kubernetes
+```
+```yaml
+# Block 2 - broken
+name: devops
+tools:
+- docker
+ - kubernetes
+```
+
+**What's wrong with Block 2:**
+- `- docker` has no indentation under `tools` β should be 2 spaces in
+- `- kubernetes` has 2 spaces but `- docker` has 0 spaces β inconsistent indentation
+- YAML expects all list items at the same indent level
+
+**Correct version:**
+```yaml
+name: devops
+tools:
+ - docker
+ - kubernetes
+```
+
+---
+
+## π― 3 Key Learnings from Day 38
+
+1. **Spaces only, never tabs** β YAML will throw an error if you use tabs. Always use 2 spaces for indentation.
+2. **Indentation is everything** β wrong indentation completely breaks the file. yamllint.com is your best friend!
+3. **`|` vs `>`** β Use `|` when line breaks matter (scripts), use `>` when you want everything on one line (descriptions).
+
+---
+
+## π Files Created
+- `person.yaml` β key-value pairs, lists
+- `server.yaml` β nested objects, multi-line strings
+
+---
+
+*Day 38 of 90DaysOfDevOps Challenge*
+*#90DaysOfDevOps #DevOpsKaJosh #TrainWithShubham*
diff --git a/2026/day-38/person.yaml b/2026/day-38/person.yaml
new file mode 100644
index 0000000000..be00619190
--- /dev/null
+++ b/2026/day-38/person.yaml
@@ -0,0 +1,13 @@
+name: Mohammad Adnan Khan
+role: Aspiring DevOps Engineer
+experience_years: 0
+learning: true
+
+tools:
+ - Docker
+ - Kubernetes
+ - Git
+ - Linux
+ - AWS
+
+hobbies: [coding, learning, cricket]
diff --git a/2026/day-38/server.yaml b/2026/day-38/server.yaml
new file mode 100644
index 0000000000..291d206a54
--- /dev/null
+++ b/2026/day-38/server.yaml
@@ -0,0 +1,22 @@
+server:
+ name: prod-server-01
+ ip: 192.168.1.10
+ port: 8080
+
+database:
+ host: db.internal
+ name: tododb
+ credentials:
+ user: root
+ password: Test@123
+
+startup_script: |
+ echo "Starting server..."
+ cd /app
+ npm install
+ node index.js
+
+shutdown_script: >
+ echo "Stopping server..."
+ cd /app
+ pm2 stop all
diff --git a/2026/day-39/day-39-cicd-concepts.md b/2026/day-39/day-39-cicd-concepts.md
new file mode 100644
index 0000000000..05efbcab95
--- /dev/null
+++ b/2026/day-39/day-39-cicd-concepts.md
@@ -0,0 +1,145 @@
+# π Day 39 β What is CI/CD?
+> By Mohammad Adnan Khan | 90DaysOfDevOps
+
+---
+
+## π Task 1: The Problem With Manual Deployments
+
+### What can go wrong with 5 developers manually deploying?
+- β Two developers push conflicting code at the same time β app breaks completely
+- β Someone forgets to test before deploying β bugs go directly to production
+- β Code works on developer laptop but fails on the production server
+- β No rollback plan if deployment fails β website goes down for hours
+- β No one knows whose code caused the bug β blame game starts!
+- β Deployment takes hours β slow delivery to customers
+
+### "It works on my machine" β Why is this a real problem?
+Every developer has a different environment on their laptop β different OS version, different Node.js version, different environment variables, different installed packages. Code that runs perfectly locally can completely fail on the production server because the environments are different.
+
+Docker solved this partially by packaging the app with its environment. CI/CD solves it completely by always running code in a standardized, controlled environment β the same every single time, for every developer.
+
+### How many times can a team safely deploy manually?
+| Manual Deployments | With CI/CD |
+|---|---|
+| 1-2 times per day maximum | 50-100+ times per day! |
+| Slow, risky, error-prone | Fast, safe, automated |
+| Hours to deploy | Minutes to deploy |
+
+---
+
+## π Task 2: CI vs CD vs CD
+
+### β
Continuous Integration (CI)
+Developers merge code to a shared repo frequently (multiple times per day). Each merge automatically triggers a build and test process. CI catches integration bugs early β before they become big problems.
+
+**What it does:** Builds code β Runs unit tests β Reports pass/fail
+
+**Real-world example:** Netflix engineers push code 100+ times per day. Every push automatically runs thousands of tests within minutes.
+
+---
+
+### π Continuous Delivery (CD)
+After CI passes, the code is automatically prepared and packaged for deployment. The app is always in a deployable state. However, the actual deployment to production requires a **manual approval** button press.
+
+**What it does:** CI passes β Build artifact β Deploy to staging β Ready for production
+
+**Real-world example:** A company runs CI/CD where every feature is tested and staged automatically, but a product manager clicks "Deploy to Production" manually for business reasons.
+
+---
+
+### π€ Continuous Deployment (CD)
+Every change that passes all automated tests is automatically deployed to production β NO human approval needed. This is the most advanced stage. Only companies with very high test coverage and confidence use this.
+
+**What it does:** CI passes β Deploy to staging β Auto-deploy to production
+
+**Real-world example:** Amazon deploys to production every 11.7 seconds β fully automated!
+
+---
+
+### Quick Comparison
+
+| Feature | CI | Continuous Delivery | Continuous Deployment |
+|---|---|---|---|
+| Auto Build | β
Yes | β
Yes | β
Yes |
+| Auto Test | β
Yes | β
Yes | β
Yes |
+| Auto Stage | β No | β
Yes | β
Yes |
+| Auto Prod Deploy | β No | β No (manual) | β
Yes (auto) |
+| Human Approval | Build only | For production | Not needed |
+
+---
+
+## π Task 3: Pipeline Anatomy
+
+| Component | Definition | Example |
+|---|---|---|
+| π― **Trigger** | The event that starts the pipeline automatically | git push, pull request, schedule |
+| π¦ **Stage** | A logical phase/group in the pipeline | Build, Test, Deploy |
+| βοΈ **Job** | A unit of work inside a stage β runs on a runner | run-unit-tests, build-docker |
+| π **Step** | A single command or action inside a job | npm install, npm test |
+| π₯οΈ **Runner** | The machine/server that executes the job | ubuntu-latest, self-hosted |
+| ποΈ **Artifact** | Output produced by a job β passed to next stage | app.jar, docker image, test-report |
+
+---
+
+## π Task 4: CI/CD Pipeline Diagram
+
+**Scenario:** Developer pushes code to GitHub β App is tested β Built into Docker image β Deployed to staging server
+```
+βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
+β π TRIGGER ββββββΆβ π¨ BUILD ββββββΆβ π§ͺ TEST ββββββΆβ π³ DOCKER ββββββΆβ π DEPLOY β
+β β β β β β β β β β
+β git push β β npm install β β npm test β βdocker build β β SSH server β
+β to GitHub β β npm build β β coverage β βdocker push β β docker up β
+βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ βββββββββββββββ
+```
+
+### Detailed Breakdown
+
+| Stage | Job | Steps | Status |
+|---|---|---|---|
+| π Trigger | on: push to main | GitHub detects git push, starts pipeline | β‘ Auto |
+| π¨ Build | install-and-build | checkout code, npm install, npm run build | β
Pass/Fail |
+| π§ͺ Test | run-tests | npm test, generate coverage, report results | β
Pass/Fail |
+| π³ Docker | build-and-push | docker build, docker tag, docker push to Hub | β
Pass/Fail |
+| π Deploy | deploy-staging | SSH to server, docker pull, docker-compose up | π Live! |
+
+---
+
+## π Task 5: Explore in the Wild β FastAPI
+
+**Repo explored:** `tiangolo/fastapi` on GitHub
+**Workflow file:** `.github/workflows/test.yml`
+
+| Question | Answer |
+|---|---|
+| What triggers it? | push and pull_request events on main branch |
+| How many jobs? | Multiple β lint, test (matrix: Python 3.8, 3.9, 3.10, 3.11), coverage |
+| What does it do? | Installs FastAPI dependencies, runs pytest across multiple Python versions simultaneously using matrix strategy, uploads coverage to Codecov |
+| Key learning | Matrix strategy runs same tests across multiple Python versions in parallel β saves time and ensures compatibility! |
+
+---
+
+## π― Key Learnings from Day 39
+
+1. **CI/CD is a practice, not a tool** β GitHub Actions, Jenkins, GitLab CI are tools that implement CI/CD. The practice is about automating build, test, and deploy.
+2. **A failing pipeline is a good thing!** β CI/CD catching a bug before production is exactly what it is designed to do.
+3. **CI vs Delivery vs Deployment** β CI = auto test. Delivery = auto stage + manual prod. Deployment = fully automatic.
+4. **Pipeline anatomy** β Trigger β Stage β Job β Step β Runner β Artifact.
+5. **Real teams deploy frequently** β Amazon: every 11.7 seconds. Netflix: 100s of times per day. Only possible with CI/CD!
+
+---
+
+## π§ Popular CI/CD Tools
+
+| Tool | Best For | Key Feature |
+|---|---|---|
+| GitHub Actions | GitHub repos, open source | Built into GitHub, free for public repos |
+| Jenkins | Enterprise, self-hosted | Most flexible, 1800+ plugins |
+| GitLab CI | GitLab users | All-in-one DevOps platform |
+| CircleCI | Fast builds | Very fast, great Docker support |
+| ArgoCD | Kubernetes deployments | GitOps approach, K8s native |
+
+---
+
+*Day 39 of 90DaysOfDevOps Challenge*
+*#90DaysOfDevOps #DevOpsKaJosh #TrainWithShubham*
diff --git a/2026/day-40/README.md b/2026/day-40/README.md
index acc6eaf44b..ff88f733bd 100644
--- a/2026/day-40/README.md
+++ b/2026/day-40/README.md
@@ -98,5 +98,5 @@ Share your first green pipeline screenshot on LinkedIn. That green checkmark hit
`#90DaysOfDevOps` `#DevOpsKaJosh` `#TrainWithShubham`
-Happy Learning!
+Happy learning!
**TrainWithShubham**
diff --git a/2026/day-40/day-40-first-workflow.md b/2026/day-40/day-40-first-workflow.md
new file mode 100644
index 0000000000..eae520b848
--- /dev/null
+++ b/2026/day-40/day-40-first-workflow.md
@@ -0,0 +1,121 @@
+# βοΈ Day 40 β My First GitHub Actions Workflow
+> By Mohammad Adnan Khan | 90DaysOfDevOps
+
+---
+
+## π Task 1: Setup
+
+- Created public repo: `github-actions-practice` on GitHub
+- Cloned it locally
+- Created folder structure: `.github/workflows/`
+
+---
+
+## π Task 2: Hello Workflow
+
+**File:** `.github/workflows/hello.yml`
+```yaml
+name: Hello GitHub Actions
+
+on:
+ push:
+
+jobs:
+ greet:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Say Hello
+ run: echo "Hello from GitHub Actions!"
+```
+
+β
Pushed to GitHub β went to Actions tab β Pipeline ran green!
+
+---
+
+## π Task 3: Workflow Anatomy
+
+| Key | What it does |
+|---|---|
+| `on:` | Defines what event triggers the pipeline (push, pull_request, schedule) |
+| `jobs:` | Contains all the jobs to run in the pipeline |
+| `runs-on:` | Defines which machine/OS the job runs on (ubuntu-latest, windows-latest) |
+| `steps:` | List of individual tasks to execute inside a job |
+| `uses:` | Runs a pre-built action from GitHub Marketplace (e.g. actions/checkout) |
+| `run:` | Executes a shell command directly on the runner |
+| `name:` | A human-readable label for the step β shows in Actions tab |
+
+---
+
+## π Task 4: Updated Workflow With More Steps
+```yaml
+name: Hello GitHub Actions
+
+on:
+ push:
+
+jobs:
+ greet:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Say Hello
+ run: echo "Hello from GitHub Actions!"
+
+ - name: Print current date and time
+ run: date
+
+ - name: Print branch name
+ run: echo "Branch is ${{ github.ref_name }}"
+
+ - name: List files in repo
+ run: ls -la
+
+ - name: Print runner OS
+ run: echo "Runner OS is $RUNNER_OS"
+```
+
+β
Pushed again β All steps ran successfully β Green pipeline!
+
+---
+
+## π Task 5: Break It On Purpose
+
+Added a failing step:
+```yaml
+ - name: This will fail
+ run: exit 1
+```
+
+**What a failed pipeline looks like:**
+- β Red cross on the Actions tab
+- The failed step shows in red with the error message
+- All steps AFTER the failed step are skipped automatically
+- GitHub sends an email notification about the failure
+- Easy to read β click the failed step and see exact error output
+
+**Fixed by removing `exit 1` β pushed again β back to green β
**
+
+---
+
+## π― Key Learnings from Day 40
+
+1. **Workflow files live in `.github/workflows/`** β any `.yml` file there is automatically picked up by GitHub Actions
+2. **Every push triggers a new run** β you can watch it live in the Actions tab
+3. **`uses:` vs `run:`** β `uses` runs pre-built actions, `run` executes shell commands
+4. **GitHub built-in variables** β `${{ github.ref_name }}` gives branch name, `$RUNNER_OS` gives OS
+5. **Failed pipelines are helpful** β they tell you exactly which step failed and why
+
+---
+
+## π Repository
+- GitHub: [github-actions-practice](https://github.com/AddyKhan257/github-actions-practice)
+
+---
+
+*Day 40 of 90DaysOfDevOps Challenge*
+*#90DaysOfDevOps #DevOpsKaJosh #Trainwithshubham*
diff --git a/2026/day-41/.github/workflows/manual.yml b/2026/day-41/.github/workflows/manual.yml
new file mode 100644
index 0000000000..60b8eb4161
--- /dev/null
+++ b/2026/day-41/.github/workflows/manual.yml
@@ -0,0 +1,23 @@
+name: Manual Deploy
+
+on:
+ workflow_dispatch:
+ inputs:
+ environment:
+ description: 'Choose environment'
+ required: true
+ default: 'staging'
+ type: choice
+ options:
+ - staging
+ - production
+
+jobs:
+ manual:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print environment
+ run: echo "Deploying to environment ${{ github.event.inputs.environment }}"
diff --git a/2026/day-41/.github/workflows/matrix.yml b/2026/day-41/.github/workflows/matrix.yml
new file mode 100644
index 0000000000..b720fee137
--- /dev/null
+++ b/2026/day-41/.github/workflows/matrix.yml
@@ -0,0 +1,25 @@
+name: Matrix Build
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Print Python version
+ run: python --version
diff --git a/2026/day-41/.github/workflows/pr-check.yml b/2026/day-41/.github/workflows/pr-check.yml
new file mode 100644
index 0000000000..c0ed5a4672
--- /dev/null
+++ b/2026/day-41/.github/workflows/pr-check.yml
@@ -0,0 +1,16 @@
+name: PR Check
+
+on:
+ pull_request:
+ branches: [main]
+ types: [opened, synchronize]
+
+jobs:
+ pr-check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print branch name
+ run: echo "PR check running for branch ${{ github.head_ref }}"
diff --git a/2026/day-41/.github/workflows/schedule.yml b/2026/day-41/.github/workflows/schedule.yml
new file mode 100644
index 0000000000..0187b908b4
--- /dev/null
+++ b/2026/day-41/.github/workflows/schedule.yml
@@ -0,0 +1,22 @@
+name: Scheduled Job
+
+on:
+ schedule:
+ - cron: '0 0 * * *' # Every day at midnight UTC
+ push:
+ branches: [main]
+
+jobs:
+ scheduled:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print schedule info
+ run: echo "Scheduled job running at $(date)"
+```
+
+**Cron expression for every Monday at 9 AM:**
+```
+0 9 * * 1
diff --git a/2026/day-41/README.md b/2026/day-41/README.md
index d7852edec4..2293229bc5 100644
--- a/2026/day-41/README.md
+++ b/2026/day-41/README.md
@@ -89,3 +89,4 @@ Share your matrix build screenshot β seeing multiple jobs run in parallel for
Happy Learning!
**TrainWithShubham**
+Adnan khan
diff --git a/2026/day-41/day-41-triggers.md b/2026/day-41/day-41-triggers.md
new file mode 100644
index 0000000000..364c5a2cec
--- /dev/null
+++ b/2026/day-41/day-41-triggers.md
@@ -0,0 +1,210 @@
+# βοΈ Day 41 β Triggers & Matrix Builds
+> By Mohammad Adnan Khan | 90DaysOfDevOps
+
+---
+
+## π Task 1: PR Trigger
+
+**File:** `.github/workflows/pr-check.yml`
+```yaml
+name: PR Check
+
+on:
+ pull_request:
+ branches: [main]
+ types: [opened, synchronize]
+
+jobs:
+ pr-check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print branch name
+ run: echo "PR check running for branch ${{ github.head_ref }}"
+```
+
+**How it works:**
+- Triggers when PR is opened or updated against `main`
+- `synchronize` = new commits pushed to the PR branch
+- `github.head_ref` = the branch name of the PR
+
+---
+
+## π Task 2: Scheduled Trigger
+
+**File:** `.github/workflows/schedule.yml`
+```yaml
+name: Scheduled Job
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+ push:
+ branches: [main]
+
+jobs:
+ scheduled:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print schedule info
+ run: echo "Scheduled job running at $(date)"
+```
+
+**Cron expression for every Monday at 9 AM:**
+```
+0 9 * * 1
+```
+
+| Field | Value | Meaning |
+|---|---|---|
+| Minute | 0 | At minute 0 |
+| Hour | 9 | At 9 AM UTC |
+| Day | * | Any day |
+| Month | * | Any month |
+| Weekday | 1 | Monday |
+
+---
+
+## π Task 3: Manual Trigger
+
+**File:** `.github/workflows/manual.yml`
+```yaml
+name: Manual Deploy
+
+on:
+ workflow_dispatch:
+ inputs:
+ environment:
+ description: 'Choose environment'
+ required: true
+ default: 'staging'
+ type: choice
+ options:
+ - staging
+ - production
+
+jobs:
+ manual:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Print environment
+ run: echo "Deploying to ${{ github.event.inputs.environment }}"
+```
+
+**How to trigger manually:**
+1. Go to GitHub repo β Actions tab
+2. Find "Manual Deploy" workflow
+3. Click "Run workflow"
+4. Select staging or production
+5. Click green "Run workflow" button β
+
+---
+
+## π Task 4: Matrix Builds
+
+**File:** `.github/workflows/matrix.yml`
+```yaml
+name: Matrix Build
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Print Python version
+ run: python --version
+```
+
+**Total jobs = 3 versions Γ 2 OS = 6 jobs in parallel!** π₯
+
+---
+
+## π Task 5: Exclude & Fail-Fast
+
+**File:** `.github/workflows/matrix-exclude.yml`
+```yaml
+name: Matrix with Exclude
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+ os: [ubuntu-latest, windows-latest]
+ exclude:
+ - os: windows-latest
+ python-version: "3.10"
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Print version
+ run: python --version
+```
+
+**fail-fast: true vs false:**
+
+| | fail-fast: true (default) | fail-fast: false |
+|---|---|---|
+| One job fails | All other jobs cancelled | All jobs continue |
+| Use when | Save time | See all results |
+
+**Total jobs after exclude = 6 - 1 = 5 jobs!**
+
+---
+
+## π― Key Learnings from Day 41
+
+1. **push** β triggers on every git push to specified branch
+2. **pull_request** β triggers when PR is opened or updated
+3. **schedule** β triggers on cron schedule (like a cron job)
+4. **workflow_dispatch** β manual trigger with optional inputs
+5. **Matrix builds** β run same job across multiple environments in parallel
+6. **fail-fast: false** β all jobs run even if one fails
+7. **exclude** β skip specific combinations in matrix
+
+---
+
+## π References
+- GitHub: [AddyKhan257/90DaysOfDevOps](https://github.com/AddyKhan257/90DaysOfDevOps)
+
+---
+
+*Day 41 of 90DaysOfDevOps Challenge*
+*#90DaysOfDevOps #DevOpsKaJosh #TrainWithShubham*
diff --git a/2026/day-42/day-42-runners.md b/2026/day-42/day-42-runners.md
new file mode 100644
index 0000000000..0c3ff65cb4
--- /dev/null
+++ b/2026/day-42/day-42-runners.md
@@ -0,0 +1,194 @@
+# π Day 42 β Runners: GitHub-Hosted & Self-Hosted
+> By Mohammad Adnan Khan | 90DaysOfDevOps
+
+---
+
+## π Task 1: GitHub-Hosted Runners
+
+**What is a GitHub-Hosted Runner?**
+A GitHub-hosted runner is a virtual machine provided and managed by GitHub. You don't need to set it up β GitHub handles everything including OS, software, maintenance, and security.
+
+**Who manages it?** GitHub manages it completely β you just use it!
+
+```yaml
+name: Multi-OS Jobs
+
+on:
+ push:
+ branches: [main]
+
+jobs:
+ ubuntu-job:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Print OS info
+ run: |
+ echo "OS: Ubuntu"
+ echo "Hostname: $(hostname)"
+ echo "User: $(whoami)"
+
+ windows-job:
+ runs-on: windows-latest
+ steps:
+ - name: Print OS info
+ run: |
+ echo "OS: Windows"
+ echo "Hostname: $env:COMPUTERNAME"
+ echo "User: $env:USERNAME"
+
+ macos-job:
+ runs-on: macos-latest
+ steps:
+ - name: Print OS info
+ run: |
+ echo "OS: macOS"
+ echo "Hostname: $(hostname)"
+ echo "User: $(whoami)"
+```
+
+---
+
+## π Task 2: Pre-installed Software on ubuntu-latest
+
+```yaml
+- name: Check pre-installed tools
+ run: |
+ echo "Docker: $(docker --version)"
+ echo "Python: $(python3 --version)"
+ echo "Node: $(node --version)"
+ echo "Git: $(git --version)"
+```
+
+**Why does it matter that runners come with tools pre-installed?**
+- No need to install Docker, Python, Git on every run β saves time
+- Consistent environment for every pipeline run
+- Focus on your workflow logic, not environment setup
+- Free compute time not wasted on installations
+
+---
+
+## π Task 3: Self-Hosted Runner Setup β
+
+**Steps followed:**
+1. GitHub repo β Settings β Actions β Runners β New self-hosted runner
+2. Selected: Linux, x64
+3. Downloaded and configured runner on AWS EC2
+
+```bash
+# Download runner
+mkdir actions-runner && cd actions-runner
+curl -o actions-runner-linux-x64-2.332.0.tar.gz -L \
+ https://github.com/actions/runner/releases/download/v2.332.0/actions-runner-linux-x64-2.332.0.tar.gz
+
+# Verify checksum
+echo "f2094522a6b9afeab07ffb586d1eb3f190b6457074282796c497ce7dce9e0f2a actions-runner-linux-x64-2.332.0.tar.gz" | shasum -a 256 -c
+
+# Extract
+tar xzf ./actions-runner-linux-x64-2.332.0.tar.gz
+
+# Configure
+./config.sh --url https://github.com/AddyKhan257/Git-Hub-Action-Zero-To-Hero \
+ --token YOUR_TOKEN
+
+# Start runner
+./run.sh
+```
+
+**Runner registered as:** `khanshab`
+**Labels:** `self-hosted`, `Linux`, `X64`, `prod`
+**Status:** β
Idle β showing green in GitHub
+
+---
+
+## π Task 4: Workflow Using Self-Hosted Runner
+
+```yaml
+name: Self-Hosted Job
+
+on:
+ workflow_dispatch:
+
+jobs:
+ run-on-my-machine:
+ runs-on: self-hosted
+ steps:
+ - name: Print hostname
+ run: echo "Running on $(hostname)"
+
+ - name: Print working directory
+ run: pwd
+
+ - name: Create a file
+ run: |
+ echo "Created by GitHub Actions on $(date)" > proof.txt
+ cat proof.txt
+
+ - name: Verify file exists
+ run: ls -la proof.txt
+```
+
+**Result:** File `proof.txt` was created on my EC2 server! β
+**Hostname showed:** `ip-172-31-35-160` β my actual AWS EC2 machine!
+
+---
+
+## π Task 5: Runner Labels
+
+**Label added:** `prod`
+
+```yaml
+# Target specific runner using labels
+jobs:
+ deploy:
+ runs-on: [self-hosted, Linux, prod]
+ steps:
+ - run: echo "Running on prod runner!"
+```
+
+**Why are labels useful when you have multiple runners?**
+- You might have `dev`, `staging`, `prod` runners
+- Labels let you target the right machine for the right job
+- Example: deploy to prod only runs on the `prod` labelled runner
+- Prevents accidentally running prod code on dev machines
+
+---
+
+## π Task 6: GitHub-Hosted vs Self-Hosted
+
+| | GitHub-Hosted | Self-Hosted |
+|---|---|---|
+| **Who manages it?** | GitHub manages everything | You manage the server |
+| **Cost** | Free (with limits) | You pay for the server |
+| **Pre-installed tools** | Docker, Python, Node, Git etc. | You install what you need |
+| **Good for** | Open source, quick pipelines | Private repos, custom hardware |
+| **Security concern** | Code runs on GitHub's servers | Code runs on YOUR server β more control |
+| **Persistence** | Fresh VM every run | Files persist between runs |
+| **Speed** | Depends on GitHub queue | Depends on your server specs |
+
+---
+
+## π― Key Learnings from Day 42
+
+1. **GitHub-hosted runners** = Virtual machines managed by GitHub, free with limits
+2. **Self-hosted runners** = Your own server registered to GitHub
+3. `runs-on: ubuntu-latest` = GitHub's server
+4. `runs-on: self-hosted` = YOUR server
+5. **Labels** help target specific runners in multi-runner setups
+6. Self-hosted runners are perfect for production deployments
+7. `./run.sh` starts the runner, `./svc.sh install` makes it a permanent service
+
+---
+
+## πΈ Screenshots
+- β
Self-hosted runner registered (runner config in terminal)
+- β
Runner showing as Idle in GitHub (khanshab β green dot)
+
+---
+
+## π References
+- GitHub: [AddyKhan257/90DaysOfDevOps](https://github.com/AddyKhan257/90DaysOfDevOps/tree/master/2026/day-42)
+
+---
+
+*Day 42 of 90DaysOfDevOps Challenge*
+*#90DaysOfDevOps #DevOpsKaJosh #TrainWithShubham*