PythonLevel 4 · Real-World Software & DatabasesLesson 44

L4 · 44

Flask — Deployment Walk-Through

Time to make your blog accessible from anyone's phone. Three free-tier-friendly hosts walk you through gunicorn, environment variables and the "why isn't it working" checklist.

⏱ 1 hour🌐 DevOps lesson📚 After PY-L4-43💻 Browser · GitHub
01

Learning Goals

3 min
  • Prepare a Flask app for production — gunicorn, requirements.txt, env vars.
  • Push to GitHub.
  • Deploy to one free host (Render, Fly.io, or PythonAnywhere).
  • Debug the inevitable "500 in prod but works locally".
02

Warm-Up · Pre-Flight Checklist

5 min
  • requirements.txt — every pip dependency pinned.
  • app.run(debug=True) only inside if __name__ == "__main__".
  • SECRET_KEY, DB path read from environment.
  • ✅ A WSGI server (gunicorn) instead of the dev server.
  • ✅ Static files served by Flask is fine for small apps — large traffic moves to a CDN.
Today's big idea

"Deployment" is just: same code, different config, different process supervisor. Once you've done it once, every future deployment is the same shape.

03

New Concept · gunicorn, env, host

14 min

requirements.txt

pip freeze > requirements.txt

# typical contents:
Flask==3.0.3
gunicorn==22.0.0
bcrypt==4.2.0

gunicorn

Flask's built-in dev server is single-threaded and warns it's not for production. gunicorn is a real WSGI server:

pip install gunicorn

# Locally test it
gunicorn -w 2 -b 0.0.0.0:8000 app:app
# -w 2  = 2 worker processes
# app:app = filename:Flask-instance

Pick env vars carefully

import os
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET"]
DB_PATH = os.environ.get("DB_PATH", "blog.db")

Three friendly hosts

Render (render.com)
  - Free tier with cold starts
  - "Web Service" → connect GitHub → Python → start: gunicorn app:app
  - Set env vars in dashboard
  - Persistent disk (paid) for SQLite

Fly.io (fly.io)
  - Free allowance; CLI-driven (fly launch + fly deploy)
  - flyctl detects Flask, writes a Dockerfile
  - Volume mount for SQLite persistence

PythonAnywhere (pythonanywhere.com)
  - Easiest for absolute beginners; web UI, no Docker
  - "Web app" → Flask → point to wsgi.py
  - SQLite lives on their disk by default

The deployment shape

1. push code to GitHub
2. create new service on host
3. point host at your repo + branch
4. set env vars (FLASK_SECRET at minimum)
5. set start command (gunicorn app:app)
6. wait 60 s for first build
7. visit the URL
8. read logs when something is wrong
04

Walk-Through · Deploy the Blog to Render

12 min
  1. Push to GitHub.
    git init
    git add app.py blog_db.py templates/ static/ requirements.txt
    git commit -m "blog v1"
    git branch -M main
    gh repo create my-flask-blog --public --source=. --push   # or use the web UI
  2. Sign in to render.com → New → Web Service → Connect repo.
  3. Settings:
    Environment        : Python 3
    Build command      : pip install -r requirements.txt
    Start command      : gunicorn app:app
    Instance type      : Free
    
    Environment variables
      FLASK_SECRET = <paste a 64-hex secret>
  4. Click Deploy. Watch the build log. Two minutes later you have a URL like https://my-flask-blog.onrender.com.
  5. Visit the URL on your phone 🎉

Common 500-in-prod culprits

  • FLASK_SECRET not setKeyError on import.
  • SQLite file lost after each deploy on ephemeral disks. Use a volume (paid on Render) or migrate to Postgres.
  • Static URLs hardcoded as /static/... work, but url_for("static", ...) is safer.
  • Logs are gold — every host shows them; read them first.
05

Deploy Yours

13 min
01 🟢 Ship the blog

Deploy your blog to one of the three hosts. Tweet / DM the URL to a friend.

02 🟡 Custom domain

If you own a domain, point a subdomain (e.g., blog.yourname.com) at your deployment. Each host has a one-page guide.

03 🔴 GitHub Action: auto-deploy on push

Hook a webhook so every push to main triggers a redeploy. (Most hosts do this by default; just confirm.)

06

Mini-Challenge · From SQLite to Postgres

8 min

SQLite files don't survive on free ephemeral disks. Swap to Postgres on the host (Render & Fly both offer one). Change sqlite3.connect to psycopg.connect behind a tiny get_conn wrapper. The rest of your code shouldn't need to change much — most SQL is portable.

07

Recap

3 min

Production = gunicorn + env vars + a host. Push, configure, deploy, read logs. Once. Future apps follow the same shape. Tomorrow we step back into the "why" — data ethics.

08

Homework

4 min

Get your blog live. Send the URL to your teacher / classmate. Add a screenshot of the live site to your portfolio README.