First-hand · verified against the real thing
How to deploy a Python app (the first-hand Render version)
In one line: A minimal Flask app, the three files that matter, secrets as environment variables, and the deploy pattern this very site uses in production — written by the person who runs it.
This site runs on Render, deployed by pushing a repository — and the same push-to-deploy pattern carries a Python app with almost no ceremony. This is the first-hand version: the three files that matter, the mistakes that cost time, and the config that works. (The static-site sibling of this story — how this very publication deploys — is in the Render static deploy walkthrough.)
The smallest honest app
A Python web app needs a framework entry point. Flask is the conventional first one — small, readable, installable in one line (pip install flask):
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return {"status": "ok", "service": "bryme-demo"}
Run it locally with flask --app app run and visit the printed address — you should see the JSON. That round-trip is the whole loop you are automating: code, run, check.
The three files that matter
One: requirements.txt — every import your app needs, pinned to the versions you actually tested with (flask==3.1.3 at the time of writing; pin what you tested, because unpinned dependencies are how a working app breaks months later without a single edit). Two: the code itself, in a repo. Three: the two commands your host will run — on Render, a build command (pip install -r requirements.txt) and a start command. The production-grade start command uses a real WSGI server rather than Flask's development server:
gunicorn -w 2 -b 0.0.0.0:$PORT app:app
app:app means "the variable app inside app.py"; -b 0.0.0.0:$PORT binds to every interface on the port the platform assigns — binding to 127.0.0.1 is the classic reason a deploy "succeeds" and serves nothing. Flask's own server prints a warning for a reason: it is single-purpose and unsuitable for production traffic; gunicorn (install with pip install gunicorn) is the boring, correct answer.
Secrets are environment variables
API keys never go in code or in the repo — the host's environment-variable settings inject them at runtime, and your code reads them as the first thing it does:
import os
API_KEY = os.environ["API_KEY"]
Using os.environ["KEY"] (rather than .get) is deliberate: the app should refuse to start without its secrets rather than run broken and discover it at 3am. The full discipline — scoping, rotation, never committing tokens — is in the token-hygiene guide.
The first-hand warnings
Trust the deploy log, not the homepage — a host can serve the last good build while today's build quietly failed, which is the number-one "why is my change not live" mystery (our deployment-failure field notes). Expect free-tier web services to sleep between requests and wake slowly on the first hit (static hosting doesn't sleep; app services do — different products, despite the same dashboard). And after the first deploy, verify with curl, not vibes: hit the URL, check the status code, confirm the JSON — the same rule as every piece on this desk.
Sources
Next