Module 7 · DevOps · Drills

Drills: Deploy & CI

Reading about deployment is not deploying. Write each config yourself, run it against a real free-tier host, and only then reveal the solution. The win here is a clickable URL.

How to use this page Each drill is a small, real task. Write it first — in an editor or straight into a host dashboard — then click “Show solution” to compare. If your YAML or origin list differs but works, that's fluency. Tick each box as you go; your progress is saved in this browser.

A · Warm-up reps Basic

Drill 1 github actions

Write a minimal .github/workflows/ci.yml that, on every push, checks out the code, sets up Python 3.12, installs requirements.txt, and runs pytest.

Show solution
.github/workflows/ci.yml
name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -r requirements.txt
      - run: pytest -q

The file lives at that exact path — GitHub discovers any .yml under .github/workflows/ automatically.

Drill 2 env vars

List the environment variables DocChat needs in production, split by where each one lives (frontend host vs backend host).

Show solution
# Vercel (frontend)
NEXT_PUBLIC_API_URL   # the public backend URL the browser calls

# Render / Railway / Fly.io (backend)
DATABASE_URL          # the Neon Postgres connection string
OPENAI_API_KEY        # RAG embeddings + LLM calls
JWT_SECRET            # signs/verifies auth tokens
FRONTEND_ORIGIN       # the Vercel domain, used for CORS

Only NEXT_PUBLIC_API_URL is browser-exposed. The rest are server-side secrets and must never carry the NEXT_PUBLIC_ prefix or land in git.

Drill 3 cors

The frontend is live at https://docchat.vercel.app. Write the FastAPI CORS middleware so the backend accepts requests from that origin.

Show solution
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://docchat.vercel.app"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Use the exact scheme + host, no trailing slash. Don't ship allow_origins=["*"] with credentials — browsers reject that combination.

B · Stretch Intermediate

Drill 4 vercel deploy

Outline the steps to deploy the Next.js frontend to Vercel from a GitHub repo, including where env vars go.

Show solution
# 1. Push the repo to GitHub.
# 2. vercel.com -> Import Project -> pick the repo.
#    Vercel auto-detects Next.js (no build config needed).
# 3. Settings -> Environment Variables:
#       NEXT_PUBLIC_API_URL = https://docchat-api.onrender.com
# 4. Deploy. Every later `git push` to main auto-deploys;
#    every pull request gets its own preview URL.

No deploy command by hand — the GitHub connection makes push the trigger.

Drill 5 backend deploy

Outline how you deploy the FastAPI backend as a Docker container (e.g. on Render), reusing the 7.1 Dockerfile.

Show solution
# 1. Ensure the repo has the Dockerfile from Lesson 7.1.
#    CMD: uvicorn app.main:app --host 0.0.0.0 --port $PORT
# 2. Render -> New -> Web Service -> connect the repo.
#    Render detects the Dockerfile and builds the image.
# 3. Set env vars in the dashboard:
#       DATABASE_URL, OPENAI_API_KEY, JWT_SECRET, FRONTEND_ORIGIN
# 4. Deploy -> Render gives an HTTPS URL (TLS automatic).
# 5. Run migrations once: alembic upgrade head against DATABASE_URL.

Bind to 0.0.0.0 and the host's $PORT — not 127.0.0.1:8000, which only works locally.

C · Build challenge Build

Mini-project Produce a complete deploy checklist for DocChat plus the CI workflow that guards it. This is the exact artifact you'd hand a reviewer — or follow yourself — to take the capstone from repo to live URL without missing a step.

Build · deploy checklist + CI

Write the ordered checklist and the ci.yml together.

Show solution
# DocChat go-live checklist
# [ ] .env is gitignored; no secrets committed anywhere
# [ ] Neon: create project, copy DATABASE_URL
# [ ] alembic upgrade head  (against the Neon DATABASE_URL)
# [ ] Render: connect repo, set DATABASE_URL, OPENAI_API_KEY,
#     JWT_SECRET, FRONTEND_ORIGIN -> deploy -> note API URL
# [ ] CORS allow_origins includes the Vercel domain -> push
# [ ] Vercel: import repo, set NEXT_PUBLIC_API_URL=API URL -> deploy
# [ ] Smoke test: sign up, upload a doc, ask a question end-to-end
# [ ] CI green on main
.github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Lint
        run: ruff check .
      - name: Run tests
        run: pytest -q

Order matters: database before backend, backend URL before the frontend env var, CORS before the first real browser call. The CI file makes every future push prove itself.

D · Rapid recall Flashcards

Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.

What is managed Postgres, and one provider?
A cloud-hosted Postgres someone else patches/backs-up — e.g. Neon (or Supabase). You get a DATABASE_URL.
click to flip
Where do env vars live in production?
In each host's dashboard as secrets — never in code or git.
click to flip
What triggers a GitHub Actions run?
The on: key — e.g. push and pull_request to the repo.
click to flip
What does CI run on each push?
Checkout, install deps, lint (ruff), then pytest. Red blocks the merge.
click to flip
The Vercel deploy flow?
Import the GitHub repo once; then every git push to main auto-deploys, PRs get preview URLs.
click to flip
Why does CORS bite only in production?
Frontend and backend are now on different domains, so the backend must allow_origins the prod frontend.
click to flip

E · Self-check before moving on

Tick each only if you can do it without looking:

Next All ticked and DocChat is on the open internet? That's the whole stack, shipped. Now we assemble and polish the capstone itself: Module 8 — Capstone Build.