Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9dbd5c4
feat: day-36 docker project - Dockerized Todo App
AddyKhan257 Mar 9, 2026
6ddc8a8
Create Docker cheat sheet with core concepts and commands
AddyKhan257 Mar 10, 2026
e231415
Create Day 37 revision document on Docker
AddyKhan257 Mar 10, 2026
de78f29
Create day-38-yaml.md
AddyKhan257 Mar 11, 2026
951b332
Create person.yaml
AddyKhan257 Mar 11, 2026
cc32e5f
Add personal details for Mohammad Adnan Khan
AddyKhan257 Mar 11, 2026
dea32ce
Add tools and hobbies to person.yaml
AddyKhan257 Mar 11, 2026
9f2de3d
Create server.yaml
AddyKhan257 Mar 11, 2026
d2fdb58
Add server and database configuration to server.yaml
AddyKhan257 Mar 11, 2026
9b1d9ab
Modify server configuration and add scripts
AddyKhan257 Mar 11, 2026
967840d
Create Day 38 YAML Basics documentation
AddyKhan257 Mar 11, 2026
008615e
Merge branch 'TrainWithShubham:master' into master
AddyKhan257 Mar 12, 2026
1e6c527
added day-39-cicd-concepts.md
AddyKhan257 Mar 12, 2026
0d304ac
add: day-40 first github actions workflow
AddyKhan257 Mar 13, 2026
2e8ae18
add: readme
AddyKhan257 Mar 13, 2026
efe57e1
add: readme
AddyKhan257 Mar 13, 2026
b35bb3b
add: day-40-first-workflow.md
AddyKhan257 Mar 13, 2026
660e17d
Add PR check workflow for pull requests
AddyKhan257 Mar 15, 2026
7bee57c
Update README.md
AddyKhan257 Mar 15, 2026
9a8e309
test pr
AddyKhan257 Mar 15, 2026
d712113
Merge branch 'TrainWithShubham:master' into pr-test
AddyKhan257 Mar 15, 2026
703d517
Merge pull request #1 from AddyKhan257/pr-test
AddyKhan257 Mar 15, 2026
9d5caae
Fix title for Day 41 in README.md
AddyKhan257 Mar 15, 2026
af5ee87
Create schedule.yml
AddyKhan257 Mar 15, 2026
34bc631
Add manual deployment workflow for GitHub Actions
AddyKhan257 Mar 15, 2026
05fb127
Merge pull request #2 from AddyKhan257/pr-test
AddyKhan257 Mar 15, 2026
12c5cca
Create matrix.yml
AddyKhan257 Mar 15, 2026
9810257
Create day-41-triggers.md
AddyKhan257 Mar 15, 2026
541291e
Create day-42-runners.md with runner details
AddyKhan257 Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions 2026/day-36/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
45 changes: 45 additions & 0 deletions 2026/day-36/backend/index.js
Original file line number Diff line number Diff line change
@@ -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));
11 changes: 11 additions & 0 deletions 2026/day-36/backend/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
57 changes: 57 additions & 0 deletions 2026/day-36/day-36-docker-project.md
Original file line number Diff line number Diff line change
@@ -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 [email protected]: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
```
60 changes: 60 additions & 0 deletions 2026/day-36/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
25 changes: 25 additions & 0 deletions 2026/day-36/frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions 2026/day-36/frontend/package.json
Original file line number Diff line number Diff line change
@@ -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"]
}
}
11 changes: 11 additions & 0 deletions 2026/day-36/frontend/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Todo App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
52 changes: 52 additions & 0 deletions 2026/day-36/frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -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 (
<div style={{ maxWidth: 600, margin: "60px auto", fontFamily: "Arial, sans-serif", padding: "0 20px" }}>
<h1 style={{ textAlign: "center", color: "#333" }}>📝 Todo App</h1>
<p style={{ textAlign: "center", color: "#888", fontSize: 13 }}>Dockerized with React + Node.js + MySQL</p>
<div style={{ display: "flex", gap: 10, marginBottom: 30 }}>
<input value={input} onChange={(e) => 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 }}
/>
<button onClick={addTodo}
style={{ padding: "10px 20px", background: "#4CAF50", color: "#fff", border: "none", borderRadius: 8, cursor: "pointer", fontSize: 15 }}>
Add
</button>
</div>
{todos.length === 0 && <p style={{ textAlign: "center", color: "#aaa" }}>No todos yet. Add one above!</p>}
{todos.map((todo) => (
<div key={todo.id} style={{ display: "flex", alignItems: "center", justifyContent: "space-between",
padding: "12px 16px", marginBottom: 10, background: "#f9f9f9", borderRadius: 8, border: "1px solid #eee" }}>
<span onClick={() => toggleTodo(todo.id)}
style={{ cursor: "pointer", textDecoration: todo.completed ? "line-through" : "none", color: todo.completed ? "#aaa" : "#333", flex: 1 }}>
{todo.completed ? "✅" : "⬜"} {todo.title}
</span>
<button onClick={() => deleteTodo(todo.id)}
style={{ background: "#ff4d4d", color: "#fff", border: "none", borderRadius: 6, padding: "5px 12px", cursor: "pointer" }}>
Delete
</button>
</div>
))}
</div>
);
}
5 changes: 5 additions & 0 deletions 2026/day-36/frontend/src/index.js
Original file line number Diff line number Diff line change
@@ -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(<React.StrictMode><App /></React.StrictMode>);
14 changes: 14 additions & 0 deletions 2026/day-36/mysql/init.sql
Original file line number Diff line number Diff line change
@@ -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');
Loading