# Modern Deployment & DevOps ## 1. Core Concepts | Concept | Description | | :--- | :--- | | **Containerization** | Packaging code with all its dependencies (Docker) | | **CI/CD** | Continuous Integration / Continuous Deployment (GitHub Actions) | | **PaaS** | Platform as a Service (Vercel, Render, Railway, Heroku) | | **IaaS** | Infrastructure as a Service (AWS EC2, GCP Compute Engine) | | **Infrastructure as Code** | Defining infrastructure in files (Dockerfile, YAML) | | **Idempotent** | Running the same operation multiple times gives same result | | **Stateless** | App stores no state internally; all data in DB or external storage | --- ## 2. Docker - Complete Reference ### Why Docker? (Exam Critical) - **Consistency**: Runs exactly the same on any machine (dev/test/prod) ✅ - **Isolation**: No dependency conflicts between different apps on same host - **Portability**: Works on any OS that runs Docker - **Reproducibility**: Exact environment is captured in Dockerfile ### Dockerfile Anatomy ```dockerfile # 1. Start from a base image (use slim to reduce size) ✅ FROM python:3.11-slim # 2. Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 # Don't write .pyc files ENV PYTHONUNBUFFERED=1 # Don't buffer stdout/stderr # 3. Set working directory WORKDIR /app # 4. Install dependencies FIRST (layer caching optimization) ✅ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 5. Copy application code AFTER dependencies COPY . . # 6. (Optional) Run as non-root user for security RUN useradd -m appuser USER appuser # 7. Run command CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ``` ### Layer Caching (Exam Critical) ``` COPY requirements.txt . <- Layer 1: only invalidated if requirements.txt changes RUN pip install ... <- Layer 2: expensive, only re-runs if Layer 1 changed COPY . . <- Layer 3: invalidated on ANY code change ✅ This order means code changes don't trigger re-installing packages ❌ If you COPY . . first, every code change reinstalls all packages ``` ### Essential Docker Commands ```bash # Build docker build -t my-app . # build image tagged 'my-app' docker build --no-cache -t my-app . # force rebuild without cache ✅ docker build -f Dockerfile.prod -t app . # use specific Dockerfile # Run docker run -p 8080:8000 my-app # map host 8080 -> container 8000 ✅ docker run -e API_KEY=abc my-app # pass env var at runtime ✅ docker run -v $(pwd)/data:/app/data app # mount volume docker run -d my-app # run in background (detached) docker run --rm my-app # remove container when it stops # Manage containers docker ps # list running containers docker ps -a # list all containers docker stop # graceful stop docker kill # force stop docker stop $(docker ps -aq) # stop all containers docker rm # remove container docker rm $(docker ps -aq) # remove all containers # Logs docker logs # view logs docker logs -f # follow logs in real-time ✅ docker logs --tail 100 # last 100 lines # Images docker images # list images docker rmi # remove image docker pull python:3.11-slim # pull image # Exec into running container docker exec -it bash # interactive shell inside container docker exec python -c "import sys; print(sys.version)" ``` ### Docker vs Virtual Machine | Feature | Docker | Virtual Machine (VM) | | :--- | :--- | :--- | | **Kernel** | Shared with host OS | Separate full OS per VM | | **Speed** | Starts in seconds | Starts in minutes | | **Size** | MBs | GBs | | **Isolation** | Process-level | Full OS-level | | **Best for** | Microservices, apps | Full OS isolation | --- ## 3. Port Mapping ``` docker run -p 8080:8000 my-app | | | +-- Container port (what app listens on internally) +--------- Host port (what you access from outside) Access: http://localhost:8080 -> maps to container port 8000 CRITICAL: Use --host 0.0.0.0 inside Docker (NOT 127.0.0.1) CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] ✅ CMD ["uvicorn", "main:app", "--host", "127.0.0.1", ...] ❌ NOT accessible ``` --- ## 4. FastAPI + Uvicorn Production Setup ```python from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware import os app = FastAPI(title="TDS API", version="1.0.0") # CORS - allow frontend to call API ✅ app.add_middleware( CORSMiddleware, allow_origins=["*"], # or specific: ["https://yoursite.com"] allow_methods=["*"], allow_headers=["*"], ) @app.get("/health") def health_check(): return {"status": "ok"} @app.get("/data/{item_id}") def get_item(item_id: int, q: str = None): return {"item_id": item_id, "query": q} ``` ```bash # Development (with auto-reload) uvicorn main:app --host 0.0.0.0 --port 8000 --reload # Production (no reload, multiple workers) uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 # ❌ Never use --reload in production (overhead + instability) ``` --- ## 5. Environment Variables ```python import os # Read env vars (never hardcode secrets!) API_KEY = os.getenv('API_KEY') # returns None if missing DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:///local.db') # with default # Guard pattern - fail fast if critical config missing REQUIRED_ENV = ["DATABASE_URL", "API_KEY", "SECRET_KEY"] for env in REQUIRED_ENV: if not os.environ.get(env): raise RuntimeError(f"Missing required environment variable: {env}") ``` ```bash # Setting env vars for Docker docker run -e API_KEY=abc -e DATABASE_URL=postgres://... my-app ✅ # Using env file docker run --env-file .env my-app ✅ # Never store in Dockerfile: ENV API_KEY=abc123 ❌ exposed in image layers! ``` --- ## 6. GitHub Actions - CI/CD ```yaml # .github/workflows/deploy.yml name: Deploy to Production on: push: branches: [main] # trigger on push to main jobs: test-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # checkout code - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies run: | pip install -r requirements.txt - name: Run tests run: | pytest tests/ -v # ✅ test before deploy - name: Deploy env: API_KEY: ${{ secrets.API_KEY }} # ✅ use GitHub secrets run: | echo "Deploying..." ``` ```yaml # GitHub Pages deploy name: Deploy Pages on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./ # or ./dist, ./build etc. ``` --- ## 7. GitHub Pages Deployment ``` Root site: username.github.io -> repo named "username.github.io" Subpath: username.github.io/repo-name -> any other repo name Steps: 1. Go to repo Settings -> Pages 2. Source: select "Deploy from a branch" or "GitHub Actions" 3. Branch: main -> / (root) or /docs folder 4. Save .nojekyll file: - Create empty file named ".nojekyll" in repo root - ✅ Tells GitHub to skip Jekyll processing and serve files directly - Required when using index.html without Jekyll front matter ``` --- ## 8. Monitoring & Observability (Exam Critical) | Metric | What to Monitor | Alert When | | :--- | :--- | :--- | | **Inference Time** | How long ML model takes to predict ✅ | Exceeds SLA | | **Queue Depth** | Number of pending requests | Growing unboundedly | | **Error Rate** | Percentage of 4xx/5xx responses | Above 1% | | **CPU/RAM Usage** | Container resource consumption | Approaching limits | | **Request Latency** | End-to-end response time | P95 too high | | **Throughput** | Requests per second | Drop from baseline | ### Finding Bottlenecks ``` Database-heavy app is slow? Check: 1. Query profiling - find slow SQL queries ✅ 2. Connection pool metrics - are you waiting for DB connections? 3. Missing indexes on frequently queried columns 4. N+1 query problem (use JOINs instead of loops) API is slow? Check: 1. Network tab in Chrome DevTools -> Timing tab -> TTFB 2. External API call latency 3. Serialization overhead High memory? Check: 1. Memory profiler (memory_profiler, tracemalloc) 2. Leaking connections or file handles 3. Loading large files into memory (use generators instead) ``` --- ## 9. Exposing Local Services ```bash # ngrok - expose local port to internet (development/demo only) ✅ ngrok http 8000 # expose localhost:8000 # Gives: https://abc123.ngrok.io -> localhost:8000 # cloudflared (Cloudflare tunnel) cloudflared tunnel --url http://localhost:8000 # ❌ ngrok and cloudflared are for development/demo only # ✅ Use proper hosting (Vercel, Render, Railway) for production ``` --- ## 10. Platform Comparison | Platform | Best For | Notes | | :--- | :--- | :--- | | **GitHub Pages** | Static sites, docs | Free, Jekyll or plain HTML | | **Vercel** | Next.js, React, static + serverless | Zero-config, free tier | | **Render** | Python APIs, Docker, background jobs | Free tier available | | **Railway** | Databases, full-stack apps | Easy deploys | | **Docker + VPS** | Full control, custom environments | More setup needed | | **Google Cloud Run** | Serverless containers | Pay per request | --- ## 11. Hotfix Workflow (Exam Critical) ```bash # Critical bug found in production - must fix immediately # Step 1: Branch from production (main) git checkout main git checkout -b hotfix/critical-bug # Step 2: Implement and test fix git add . git commit -m "Fix: critical routing bug in payment flow" # Step 3: Push and create PR for expedited review git push origin hotfix/critical-bug # Step 4: Merge into BOTH main AND develop ✅ (exam answer) git checkout main git merge hotfix/critical-bug git tag -a v1.0.1 -m "Hotfix: payment routing" git push origin main --tags git checkout develop # ✅ critical: keep develop in sync git merge hotfix/critical-bug git push origin develop # Step 5: Clean up git branch -d hotfix/critical-bug ``` - ✅ Must merge hotfix into BOTH main AND develop - ❌ Apply directly to main without review - ❌ Delay to next regular sprint release --- ## 12. `.dockerignore` ```dockerignore # Always include .dockerignore to keep images small and secure .git .gitignore __pycache__ *.pyc *.pyo .venv venv env .env # ✅ never include secrets in image .env.* *.log logs/ .pytest_cache .DS_Store README.md tests/ # optional: exclude test files from prod image docs/ ``` --- ## 13. Multi-Stage Docker Build ```dockerfile # Stage 1: Builder (has build tools) FROM python:3.11 AS builder WORKDIR /app COPY requirements.txt . RUN pip install --user --no-cache-dir -r requirements.txt # Stage 2: Runtime (minimal, no build tools) FROM python:3.11-slim WORKDIR /app COPY --from=builder /root/.local /root/.local # copy only installed packages COPY . . ENV PATH=/root/.local/bin:$PATH CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] # Result: much smaller final image ✅ ``` --- ## 14. Quick Reference Card ```bash # Full local dev -> docker -> deploy cycle # 1. Develop locally uvicorn main:app --reload --port 8000 # 2. Containerize docker build -t my-api . docker run -p 8000:8000 -e API_KEY=$API_KEY my-api # 3. Test container curl http://localhost:8000/health # 4. Push to registry docker tag my-api registry.example.com/my-api:v1.0 docker push registry.example.com/my-api:v1.0 # 5. Deploy (GitHub Actions handles this in CI/CD) # Useful debugging docker logs -f container_id # follow logs docker exec -it container_id bash # shell inside docker stats # resource usage docker inspect container_id # full config ``` --- ## 15. Exam Scenario Answers | Scenario | Answer | | :--- | :--- | | Deploy Python app, run everywhere consistently | Docker containerization ✅ | | App works locally, fails on server | "Works on my machine" problem -> Docker solves ✅ | | Critical production bug, fix needed now | Hotfix branch -> merge to main + develop ✅ | | Database app is slow, find bottleneck | Query profiling + connection pool metrics ✅ | | ML API has inconsistent latency | Monitor inference time, queue depth ✅ | | Expose local FastAPI to internet for demo | ngrok (development only) ✅ | | Code changes force re-installing packages | Wrong Dockerfile order; copy requirements.txt first ✅ | | Store secrets in Docker image | Never; use env vars at runtime ✅ | | GitHub Pages not showing HTML | Add `.nojekyll` file to root ✅ | | Uvicorn not accessible inside Docker | Use `--host 0.0.0.0` not `127.0.0.1` ✅ |