Back to Blog
Tutorial9 min read2026-07-02

How to Deploy a Flask App with Gunicorn

Flask's dev server isn't for production. Learn how to deploy Flask properly with Gunicorn, the right worker model, a Dockerfile, health checks, and a managed database.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

If you've ever seen Flask print WARNING: This is a development server. Do not use it in a production deployment — that's the whole reason this post exists. Flask's built-in server is single-threaded and not hardened. Production means a real WSGI server. Here's how to do it right.

Why Gunicorn

Flask apps speak WSGI. Gunicorn is a battle-tested WSGI server that manages multiple worker processes, restarts crashed workers, and handles graceful shutdown. The basic production command:

gunicorn 'app:create_app()' \
  --workers 4 \
  --bind 0.0.0.0:8000 \
  --timeout 60

If you use the application factory pattern, point Gunicorn at the factory call as shown. Otherwise it's just gunicorn app:app.

Choosing a worker model

Gunicorn supports several worker types, and picking the right one is the single biggest performance decision:

Worker classBest forNotes
sync (default)CPU-bound, simple appsOne request per worker at a time
gthreadMixed I/OThreads per worker; set --threads
gevent / eventletHigh-concurrency I/OAsync via greenlets; needs monkey-patching

For a typical CRUD API talking to a database, gthread with a few threads per worker is a solid default:

gunicorn 'app:create_app()' --workers 3 --threads 4 --worker-class gthread --bind 0.0.0.0:8000

Start with (2 * cores) + 1 workers and adjust based on real latency and memory metrics.

A production Dockerfile

FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "app:create_app()", "--workers", "3", "--threads", "4", \
     "--worker-class", "gthread", "--bind", "0.0.0.0:8000"]

Running as a non-root user is a cheap, important hardening step.

Configuration and secrets

Flask reads config from environment variables cleanly:

import os

class Config:
    SECRET_KEY = os.environ["SECRET_KEY"]
    SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
    DEBUG = False

Never run with DEBUG=True in production — it exposes the Werkzeug interactive debugger, which is a remote code execution risk if reachable. Set a real SECRET_KEY so sessions and CSRF tokens are secure.

A managed database

With Flask-SQLAlchemy, point at DATABASE_URL and run migrations with Flask-Migrate (Alembic under the hood):

flask db upgrade

Run this as a deploy step before traffic hits the new code. Mind your connection pool size relative to workers * threads so you don't exceed your database's connection limit.

Health checks

@app.get("/healthz")
def healthz():
    return {"status": "ok"}, 200

Keep liveness cheap; add a separate readiness route that pings the database if you want the orchestrator to gate traffic on DB availability.

Deploying on PandaStack

  1. 1Create a PostgreSQL (or MySQL) database — DATABASE_URL is injected automatically.
  2. 2Connect your repo as a container app. PandaStack detects Python and your Dockerfile; without one, buildpacks install requirements.txt and run Gunicorn.
  3. 3Set SECRET_KEY and other secrets in the dashboard.
  4. 4Add flask db upgrade as a release command and push.

Builds run in rootless BuildKit; you get live logs, automatic SSL, rollbacks, and deploy history.

Common pitfalls

  • Shipping the dev server — never flask run or app.run() in production.
  • DEBUG=True — exposes the interactive debugger; massive security hole.
  • Default sync worker for I/O-heavy apps — they block; use gthread or gevent.
  • Pool size mismatchworkers * threads must fit your DB connection limit.
  • Binding to 127.0.0.1 — unreachable from outside the container; use 0.0.0.0.

References

  • Flask deployment options: https://flask.palletsprojects.com/en/stable/deploying/
  • Gunicorn design & worker types: https://docs.gunicorn.org/en/stable/design.html
  • Flask-Migrate: https://flask-migrate.readthedocs.io/
  • Werkzeug debugger security note: https://werkzeug.palletsprojects.com/en/stable/debug/
  • Flask configuration handling: https://flask.palletsprojects.com/en/stable/config/

---

PandaStack's free tier includes container apps and a managed database with DATABASE_URL auto-injected — deploy your Gunicorn-served Flask app with automatic SSL and live logs. Start at https://dashboard.pandastack.io

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also