All posts
flaskpythontutorialquickstartpreview-environments

Flask Preview Environment in 5 Minutes — a Live URL for Every Branch

PreviewDrop Team·August 24, 2026·7 min read

Your Flask app boots in under a second. Your test suite runs in 20. And then your product manager asks for a link to the branch you pushed this morning, and you're still editing a Docker Compose override so two PRs don't fight over port 5000.

Flask's simplicity is the whole point — a app.py and a requirements.txt and you're serving. But the preview workflow around it usually isn't. Most Flask teams still share one staging server that one person deploys to at a time, or hand-roll a CI script that provisions a VM, wires up DNS, and hopes the cleanup cron didn't silently fail three weeks ago.

PreviewDrop gives every Flask branch its own live URL — automatic deploys, HTTPS by default, database isolation, and zero CI configuration. Here's how to set it up in five minutes.

Why Flask previews are different from frontend previews

Vercel and Netlify built "push a branch, get a URL" for JavaScript frontends. That workflow assumes your app compiles to static files or serverless functions. Flask compiles to nothing — it's a long-lived Python process that listens on a port and needs a container, not a static host.

The tools that do handle container-based previews — Railway, Render, Northflank — charge per resource or per second. A Flask service with a Postgres dependency can cost $40–80/month in preview infrastructure alone for a team of three engineers. The meter runs whether anyone is reviewing the branch or not.

PreviewDrop takes a different route: if your app has a Dockerfile (or if PreviewDrop detects one from your requirements.txt), you get a live URL on every branch. Flat pricing, no per-resource metering, automatic teardown when the PR merges.

Step-by-step: your first Flask preview

Step 1 — Add a Dockerfile. If you already have one, skip to step 2. Here's a minimal multi-stage Dockerfile for a Flask app served with gunicorn:

# Dockerfile
FROM python:3.12-slim
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
# PreviewDrop injects PORT; gunicorn binds to it
CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-8000} app:app"]

Two things matter here, and each is a real gotcha the first time you skip it.

Bind to 0.0.0.0, not 127.0.0.1

Flask's dev server defaults to 127.0.0.1, which means the container won't accept traffic from outside itself. gunicorn's --bind 0.0.0.0 fixes that. If you run Flask's built-in server instead, pass --host=0.0.0.0:

flask run --host=0.0.0.0 --port=${PORT:-8000}

Read the injected PORT, don't hard-code it

PreviewDrop routes traffic to whatever port your app listens on via the PORT env var. Hard-coding 5000 still works if it matches, but reading PORT is the safe default — it's what keeps the preview routable the moment the container starts.

Step 2 — Connect your repo. Install the PreviewDrop GitHub App on your Flask repo. It takes about 60 seconds. The app receives push and PR webhooks automatically — no workflow files to write.

Step 3 — Set environment variables. In the PreviewDrop dashboard, add the variables your app needs:

PORT=8000
DATABASE_URL=postgres://user:pass@host:5432/preview_db
SECRET_KEY=your-preview-secret
FLASK_ENV=preview

Use a dedicated development database. Previews are ephemeral — point DATABASE_URL at a shared dev Postgres instance, or use a database-branching service for per-branch isolation.

Step 4 — Push a branch. That's it. PreviewDrop detects Flask from your requirements.txt, builds the image, starts the container, and posts a live HTTPS URL to your PR:

git checkout -b feature/new-endpoint
git push origin feature/new-endpoint
# URL posted to your PR automatically:
# https://pr-127.previewdrop.dev

What PreviewDrop detects in your Flask project

When PreviewDrop scans your repo, it looks for:

  • requirements.txt or pyproject.toml — confirms it's a Python project, reads dependencies
  • app.py, wsgi.py, or wsgi.py:app — identifies the entry point
  • Dockerfile — if present, uses it directly; if not, generates a build from the dependencies
  • migrations/ or Alembic config — detects schema migrations to run at build time

The build installs your dependencies, compiles nothing (Python is interpreted), and starts the container. If you use Flask-Migrate or Alembic, you can run migrations as part of the deploy so the preview database always matches the branch's schema.

Database strategy for Flask previews

Flask apps typically connect to Postgres via SQLAlchemy or the lighter psycopg2 directly, or use SQLite for smaller services. The database question is the same as any backend stack: share a dev database, or isolate per branch?

Option 1 — Shared dev database. Point all previews at one Postgres instance. Simplest setup, fine for read-heavy testing. The trade-off: two branches running conflicting migrations will step on each other.

Option 2 — Per-branch isolation. Use a database-branching service that creates lightweight Postgres clones in under a second. Set the branch name as an environment variable, and each preview gets its own isolated database. Here's how your Flask app reads it:

# config.py
import os

class Config:
    DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///app.db")
    SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret")
    # PreviewDrop injects PREVIEW_URL at deploy time
    PREVIEW_URL = os.getenv("PREVIEW_URL", "http://localhost:8000")

Option 3 — SQLite for previews. For simpler Flask apps, SQLite avoids external database dependencies entirely and works well for services that don't need Postgres-specific features.

Add a health check endpoint

Preview platforms need a health check to verify the container is ready. Flask makes this trivial:

# app.py
from flask import Flask, jsonify
from sqlalchemy import text

app = Flask(__name__)

@app.route("/health")
def health():
    try:
        from app import db
        db.session.execute(text("SELECT 1"))
        return jsonify(status="ok", database="connected"), 200
    except Exception:
        return jsonify(status="unhealthy"), 503

PreviewDrop polls /health after container start. When it returns 200, the preview is marked ready and the URL is posted to the PR.

Flask-specific gotchas

The dev server vs. a real WSGI server. Flask's built-in server is single-threaded and meant for development. For a preview that multiple reviewers will click at once, use gunicorn (as in the Dockerfile above) or waitress. It's the difference between "works when I test it" and "works when the whole team clicks it."

Secret keys. Flask's session cookies are signed with SECRET_KEY. Never reuse your production key in a preview. Generate a throwaway one for previews — the Flask CLI does it in one line:

python -c "import secrets; print(secrets.token_hex(32))"

CORS for a separate frontend. If a React or Vue frontend calls your Flask API, set CORS_ORIGINS to the preview URL so the browser allows the cross-origin request. Flask-CORS reads it from the environment:

from flask_cors import CORS

CORS(app, origins=[os.getenv("PREVIEW_URL", "http://localhost:3000")])

What it costs

PreviewDrop is flat workspace pricing, not per-seat. The Free plan gives you two live previews at a time, one preview per project, and up to three projects, with a four-hour preview lifetime. Starter is $19/mo for five concurrent previews, three previews per project, up to ten projects, and a 24-hour lifetime. Pro and Team extend the lifetime to three and seven days respectively, with more concurrent previews and larger container memory.

The point is that a Flask team can stop merging blind. Every pull request gets a real, running instance of the app — not a screenshot, not a staging server shared across the whole team.

Start your first Flask preview

PreviewDrop works with any Flask app — from a single app.py API to a multi-service monorepo with shared packages. The free tier gives you 2 concurrent previews with no credit card required. If you're new to the platform, the quickstart guide walks through connecting your first repository end to end.

Connect your Flask repo now →

Ready to give every branch a live URL?

Free tier — 2 concurrent previews, no credit card required.

Start free