πŸš€ Month 2 β€” Docker, Static Website, Full‑Stack Basics (with code)

Week 1 β€” Static Website (HTML + CSS + JS)

Goal

Understand how the web works; build a clean, responsive Personal Portfolio.

File tree

week1-portfolio/
β”œβ”€ index.html
β”œβ”€ styles.css
└─ script.js

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>My Portfolio</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
  <header class="container">
    <nav class="nav">
      <a href="#about">About</a>
      <a href="#skills">Skills</a>
      <a href="#projects">Projects</a>
      <a href="#contact">Contact</a>
    </nav>
    <h1>Hello, I'm <span class="accent">Your Name</span></h1>
    <p>Aspiring developer | Tech Enthusiast | Lifelong Learner</p>
    <button id="themeBtn">Toggle Theme</button>
  </header>

  <main class="container">
    <section id="about">
      <h2>About Me</h2>
      <p>I am a fresher learning web and Python. This is my first portfolio.</p>
    </section>

    <section id="skills">
      <h2>Skills</h2>
      <ul class="tags">
        <li>HTML</li><li>CSS</li><li>JavaScript</li><li>Python</li>
      </ul>
    </section>

    <section id="projects">
      <h2>Projects</h2>
      <article class="card">
        <h3>Portfolio Website</h3>
        <p>A responsive personal website built with HTML/CSS/JS.</p>
        <a href="#" target="_blank">View</a>
      </article>
    </section>

    <section id="contact">
      <h2>Contact</h2>
      <form id="contactForm">
        <label>
          Name
          <input name="name" required />
        </label>
        <label>
          Email
          <input name="email" type="email" required />
        </label>
        <label>
          Message
          <textarea name="message" rows="4" required></textarea>
        </label>
        <button type="submit">Send</button>
        <p id="formMsg" class="muted"></p>
      </form>
    </section>
  </main>

  <footer class="container muted">Β© 2025 Your Name</footer>
  <script src="script.js"></script>
</body>
</html>

styles.css

:root { --bg:#0f172a; --fg:#e2e8f0; --accent:#38bdf8; --card:#111827; }
* { box-sizing: border-box; }
body { margin:0; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background:var(--bg); color:var(--fg); }
.container { width:min(1000px, 92%); margin-inline:auto; padding:1.5rem 0; }
.nav { display:flex; gap:1rem; margin-bottom:1rem; flex-wrap:wrap; }
.nav a { color:var(--fg); opacity:.8; text-decoration:none; }
h1 { font-size:2.2rem; margin:.2rem 0 1rem; }
h2 { margin-top:2rem; }
.accent { color:var(--accent); }
.tags { display:flex; gap:.5rem; padding:0; list-style:none; flex-wrap:wrap; }
.tags li { background:#1f2937; padding:.4rem .7rem; border-radius:999px; }
.card { background:var(--card); padding:1rem; border-radius:14px; box-shadow:0 4px 20px rgba(0,0,0,.2); }
label { display:block; margin:.7rem 0; }
input, textarea { width:100%; padding:.7rem; border:1px solid #334155; border-radius:10px; background:#0b1220; color:var(--fg); }
button { padding:.7rem 1rem; border-radius:10px; background:var(--accent); color:#031018; border:0; cursor:pointer; }
.muted { color:#94a3b8; font-size:.9rem; }
@media (max-width:600px){ h1 { font-size:1.6rem; } }
.light { --bg:#f8fafc; --fg:#0b1220; --card:#ffffff; }

script.js

const themeBtn = document.getElementById("themeBtn");
themeBtn.addEventListener("click", () =>
  document.body.classList.toggle("light")
);

const form = document.getElementById("contactForm");
const msg = document.getElementById("formMsg");
form.addEventListener("submit", (e) => {
  e.preventDefault();
  const data = Object.fromEntries(new FormData(form).entries());
  // Simple front-end validation
  if (!data.name || !data.email || !data.message) {
    msg.textContent = "Please fill all fields.";
    return;
  }
  msg.textContent = "Thanks! Your message was not really sent (demo).";
  form.reset();
});

How to open: double‑click index.html (or use VSCode β€œGo Live” extension).


Week 2 β€” Flask (Python for Web)

Goal

Move from static β†’ dynamic pages, forms, and templates.

File tree

week2-flask-todo/
β”œβ”€ app.py
β”œβ”€ requirements.txt
β”œβ”€ templates/
β”‚  β”œβ”€ base.html
β”‚  └─ index.html
└─ static/
   └─ styles.css

requirements.txt

Flask==3.0.3

app.py

from flask import Flask, render_template, request, redirect, url_for

app = Flask(__name__)
tasks = []  # in-memory list (Week 3 we'll persist to DB)

@app.route("/")
def home():
    return render_template("index.html", tasks=tasks)

@app.route("/add", methods=["POST"])
def add():
    title = request.form.get("title", "").strip()
    if title:
        tasks.append({"title": title, "done": False})
    return redirect(url_for("home"))

@app.route("/toggle/<int:index>")
def toggle(index):
    if 0 <= index < len(tasks):
        tasks[index]["done"] = not tasks[index]["done"]
    return redirect(url_for("home"))

@app.route("/delete/<int:index>")
def delete(index):
    if 0 <= index < len(tasks):
        tasks.pop(index)
    return redirect(url_for("home"))

if __name__ == "__main__":
    app.run(debug=True)

templates/base.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>{% block title %}Flask To-Do{% endblock %}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
  <header class="container">
    <h1>Flask To‑Do</h1>
  </header>
  <main class="container">
    {% block content %}{% endblock %}
  </main>
  <footer class="container muted">Built in Month 2</footer>
</body>
</html>

templates/index.html

{% extends "base.html" %}
{% block title %}My Tasks{% endblock %}
{% block content %}
<form method="post" action="{{ url_for('add') }}" class="row">
  <input name="title" placeholder="New task..." required />
  <button>Add</button>
</form>

<ul class="list">
  {% for t in tasks %}
    <li class="item">
      <span class="{{ 'done' if t.done }}">{{ t.title }}</span>
      <a href="{{ url_for('toggle', index=loop.index0) }}">Toggle</a>
      <a href="{{ url_for('delete', index=loop.index0) }}">Delete</a>
    </li>
  {% else %}
    <li class="muted">No tasks yet.</li>
  {% endfor %}
</ul>
{% endblock %}

static/styles.css

/* Reuse aesthetics from Week1 quickly */
:root { --bg:#0f172a; --fg:#e2e8f0; --accent:#38bdf8; }
* { box-sizing: border-box; } body { margin:0; font-family: system-ui; background:var(--bg); color:var(--fg); }
.container { width:min(900px, 92%); margin-inline:auto; padding:1.2rem 0; }
.row { display:flex; gap:.5rem; }
input { flex:1; padding:.6rem; border-radius:10px; border:1px solid #334155; background:#0b1220; color:var(--fg); }
button { padding:.6rem 1rem; border-radius:10px; background:var(--accent); border:0; color:#031018; cursor:pointer; }
.list { list-style:none; padding:0; }
.item { display:flex; gap:.8rem; align-items:center; padding:.6rem 0; border-bottom:1px solid #1f2937; }
.item a { color:var(--accent); text-decoration:none; }
.done { text-decoration: line-through; opacity:.7; }
.muted { color:#94a3b8; }

Run

# in week2-flask-todo/
python -m venv .venv && source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python app.py
# Open http://127.0.0.1:5000

Week 3 β€” SQLite + Flask (CRUD)

Goal

Persist data with SQLite. Learn schema, parameterized queries, CRUD.

File tree

week3-notes-sqlite/
β”œβ”€ app.py
β”œβ”€ db.py
β”œβ”€ schema.sql
β”œβ”€ requirements.txt
β”œβ”€ templates/
β”‚  β”œβ”€ base.html
β”‚  └─ index.html
└─ static/
   └─ styles.css

requirements.txt

Flask==3.0.3

schema.sql

CREATE TABLE IF NOT EXISTS notes (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  content TEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

db.py

import sqlite3
from pathlib import Path

DB_PATH = Path("notes.db")

def get_conn():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row  # dict-like
    return conn

def init_db():
    with get_conn() as conn, open("schema.sql", "r", encoding="utf-8") as f:
        conn.executescript(f.read())

def add_note(title: str, content: str):
    with get_conn() as conn:
        conn.execute(
            "INSERT INTO notes (title, content) VALUES (?, ?)",
            (title, content)
        )

def list_notes():
    with get_conn() as conn:
        return conn.execute(
            "SELECT id, title, content, created_at FROM notes ORDER BY id DESC"
        ).fetchall()

def delete_note(note_id: int):
    with get_conn() as conn:
        conn.execute("DELETE FROM notes WHERE id = ?", (note_id,))

def search_notes(q: str):
    with get_conn() as conn:
        return conn.execute(
            "SELECT id, title, content, created_at FROM notes WHERE title LIKE ? OR content LIKE ? ORDER BY id DESC",
            (f"%{q}%", f"%{q}%")
        ).fetchall()

app.py

from flask import Flask, render_template, request, redirect, url_for
import db

app = Flask(__name__)

@app.before_first_request
def setup():
    db.init_db()

@app.get("/")
def home():
    q = request.args.get("q", "").strip()
    notes = db.search_notes(q) if q else db.list_notes()
    return render_template("index.html", notes=notes, q=q)

@app.post("/add")
def add():
    title = request.form.get("title", "").strip()
    content = request.form.get("content", "").strip()
    if title and content:
        db.add_note(title, content)
    return redirect(url_for("home"))

@app.get("/delete/<int:note_id>")
def delete(note_id: int):
    db.delete_note(note_id)
    return redirect(url_for("home"))

if __name__ == "__main__":
    app.run(debug=True)

templates/base.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>{% block title %}Notes{% endblock %}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
  <header class="container">
    <h1>SQLite Notes</h1>
    <form method="get" action="{{ url_for('home') }}" class="row">
      <input name="q" value="{{ q or '' }}" placeholder="Search notes..." />
      <button>Search</button>
    </form>
  </header>
  <main class="container">
    {% block content %}{% endblock %}
  </main>
  <footer class="container muted">SQLite + Flask | Parameterized queries prevent SQL injection βœ…</footer>
</body>
</html>

templates/index.html

{% extends "base.html" %}
{% block content %}
<form method="post" action="{{ url_for('add') }}" class="card">
  <h2>New Note</h2>
  <label>Title <input name="title" required /></label>
  <label>Content <textarea name="content" rows="4" required></textarea></label>
  <button>Add</button>
</form>

<section class="grid">
  {% for n in notes %}
    <article class="card">
      <h3>{{ n.title }}</h3>
      <p>{{ n.content }}</p>
      <small class="muted">{{ n.created_at }}</small><br />
      <a href="{{ url_for('delete', note_id=n.id) }}">Delete</a>
    </article>
  {% else %}
    <p class="muted">No notes yet.</p>
  {% endfor %}
</section>
{% endblock %}

static/styles.css

:root { --bg:#0f172a; --fg:#e2e8f0; --accent:#38bdf8; --card:#111827; }
* { box-sizing:border-box; } body { margin:0; font-family:system-ui; background:var(--bg); color:var(--fg); }
.container { width:min(900px, 92%); margin-inline:auto; padding:1.2rem 0; }
.row { display:flex; gap:.5rem; }
.card { background:var(--card); padding:1rem; border-radius:14px; margin:.7rem 0; }
.grid { display:grid; grid-template-columns: repeat(auto-fill,minmax(250px,1fr)); gap:1rem; }
label { display:block; margin:.5rem 0; }
input, textarea { width:100%; padding:.6rem; border-radius:10px; border:1px solid #334155; background:#0b1220; color:var(--fg); }
button { padding:.6rem 1rem; border-radius:10px; background:var(--accent); color:#031018; border:0; cursor:pointer; }
a { color:var(--accent); text-decoration:none; }
.muted { color:#94a3b8; font-size:.9rem; }

Run

# in week3-notes-sqlite/
python -m venv .venv && source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python app.py
# Open http://127.0.0.1:5000

Why parameterized queries? We used ? placeholders in SQL (db.py). This prevents SQL injection by separating code and data.


Week 4 β€” Docker (Containerize, then Compose)

Goal

Package the Flask+SQLite app in Docker. Optional: run with Docker Compose (named volume for DB).

We’ll containerize Week 3 project.

Add files to week3-notes-sqlite/

Dockerfile
.dockerignore
gunicorn.conf.py

Dockerfile

# 1) Base
FROM python:3.12-slim

# 2) Workdir & env
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1

# 3) System deps (optional but useful)
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl ca-certificates && rm -rf /var/lib/apt/lists/*

# 4) Copy deps first for better caching
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt gunicorn

# 5) Copy app code
COPY . /app

# 6) Expose port & command (use gunicorn)
EXPOSE 5000
CMD ["gunicorn", "-c", "gunicorn.conf.py", "app:app"]

gunicorn.conf.py

bind = "0.0.0.0:5000"
workers = 2
timeout = 60

.dockerignore

.venv
__pycache__/
*.pyc
.git
.idea
.vscode
notes.db

Build & run

# from week3-notes-sqlite/
docker build -t notes-app:1 .
docker run -p 5000:5000 -v $PWD/notes.db:/app/notes.db notes-app:1
# Windows PowerShell volume: -v ${PWD}\notes.db:/app/notes.db
# Open http://localhost:5000

Bonus: Docker Compose (app + persistent volume)

Add a new file in the same folder:

docker-compose.yml

version: "3.9"
services:
  web:
    build: .
    image: notes-app:compose
    ports:
      - "5000:5000"
    volumes:
      - notes_data:/app/notes.db
    restart: unless-stopped
volumes:
  notes_data:

Run with Compose

docker compose up --build
# Open http://localhost:5000

Why mount only the DB? So code is baked into the image (immutable), while data persists in a volume. Clean separation = fewer β€œit works on my machine” issues.


What interns submit each week


Stretch ideas (for Pro β˜…β˜…β˜…)


πŸ“‘ Tech Immersion Internship – Month 2

Weekly Assignment & Project Sheet


Week 1: Static Website Development (HTML, CSS, JS Basics)

Objective: Learn how the web works and build a static personal portfolio.

Assignments:

  1. Create a webpage with:

  2. Style the webpage with CSS:

  3. Add simple JavaScript:

Mini Project: πŸ‘‰ Personal Portfolio Website


Week 2: Python for Web (Flask Basics)

Objective: Move from static β†’ dynamic web apps.

Assignments:

  1. Install Flask and create a β€œHello World” route.
  2. Add a route /about that shows a custom HTML page.
  3. Use Jinja2 templates to insert dynamic content (e.g., {{ name }}).
  4. Create a simple HTML form and handle input in Flask.

Mini Project: πŸ‘‰ To-Do List App


Week 3: Databases (SQLite + Flask)

Objective: Store and manage data with a database.

Assignments:

  1. Create a SQLite database with a table notes(id, title, content).
  2. Insert and retrieve records using Python (sqlite3 module).
  3. Connect database to Flask app and display notes.
  4. Add a route to create a new note.

Mini Project: πŸ‘‰ Notes App with Database


Week 4: Docker Basics

Objective: Package and run apps in Docker.

Assignments:

  1. Install Docker and run hello-world.
  2. Run an official image (nginx) and access it via browser.
  3. Write a Dockerfile for a simple Python script and run it.
  4. Dockerize your Flask To-Do App.

Mini Project: πŸ‘‰ Dockerized Notes App


βœ… Deliverables (Month 2)

Submission Format:


Grading Rubric


Week 1: Static Website Development (HTML, CSS, JS Basics)

Objective: Learn how the web works and build a personal portfolio site.

Level Criteria
Beginner (β˜…) Writes simple HTML with headings and paragraphs but struggles with structure, CSS, or linking files.
Intermediate (β˜…β˜…) Builds a multi-section webpage with HTML + CSS (header, body, footer). Uses CSS selectors, classes, and styles. Completes Portfolio Page assignment.
Pro (β˜…β˜…β˜…) Adds interactivity with JavaScript (form validation, button actions). Designs a polished, responsive page (flexbox/grid). Includes creativity (e.g., animations, hover effects).

Week 2: Python for Web (Flask Basics)

Objective: Move from static β†’ dynamic content with a backend.

Level Criteria
Beginner (β˜…) Runs a basic Flask server with β€œHello World” route but struggles with templates or routing.
Intermediate (β˜…β˜…) Creates multiple Flask routes, uses HTML templates (Jinja2), and handles forms. Completes To-Do List App.
Pro (β˜…β˜…β˜…) Builds a clean, structured Flask app. Adds extra features (mark tasks done, delete tasks). Organizes code using blueprints or templates.

Week 3: Databases (SQLite + Flask)

Objective: Learn persistent storage with a database.

Level Criteria
Beginner (β˜…) Understands SQL basics, can create a table and insert rows manually.
Intermediate (β˜…β˜…) Connects Flask app to SQLite. Implements CRUD (Create, Read, Update, Delete). Completes Notes App with DB.
Pro (β˜…β˜…β˜…) Adds search, sorting, or authentication to the app. Uses parameterized queries to prevent SQL injection. Shows awareness of good database practices.

Week 4: Docker Basics (Deployment Mindset)

Objective: Package and run applications in Docker.

Level Criteria
Beginner (β˜…) Runs simple Docker images (hello-world, nginx). Struggles with custom Dockerfiles.
Intermediate (β˜…β˜…) Writes a Dockerfile for Flask app, builds and runs the container. Understands basic commands (docker run, ps, logs, stop).
Pro (β˜…β˜…β˜…) Dockerizes Flask + SQLite app with Docker Compose (app + DB). Optimizes Dockerfile (small image, .dockerignore). Explains container vs VM concepts clearly.

βœ… Final Evaluation (End of Month 2)

πŸ‘‰ Suggested Weightage:


πŸ“Š Example Final Deliverables (Month 2)