Deploying Flask

Reviewed & published by Brayan K

Deployment is the process of moving your app from your laptop onto a live server so anyone on the internet can use it — replacing the development server with a robust production setup.

Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll pin your dependencies in a requirements file, serve your app with the Gunicorn WSGI server, and configure it safely using environment variables.

What You'll Learn in This Lesson

1 Why Not the Dev Server?

The server you start with flask run is a development server. It is convenient for coding, but it handles one request at a time, is slow under load, and is not hardened against attacks.

For real traffic you need a production WSGI server. WSGI is the standard interface between Python web apps and web servers. Flask is a WSGI app, so a server like Gunicorn can run it efficiently and serve many users at once.

🧩 Dev server vs production server:

2 Dependencies & the WSGI Entry Point

A server needs to install the exact packages your app uses. List them in requirements.txt so they can be installed with one command. You also need a module that exposes the app object Gunicorn will run.

flask==3.0.0
flask-sqlalchemy==3.1.1
flask-login==0.6.3
gunicorn==21.2.0

Generate it from your environment and install on the server:

# Freeze your current packages into the file
pip freeze > requirements.txt

# On the server, install everything at once
pip install -r requirements.txt

Now expose the app object Gunicorn will serve. In app.py you simply have a module-level app:

# app.py — the module Gunicorn will import
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return "Live in production!"

# Gunicorn imports this module and grabs the "app" object:
#   gunicorn app:app
#          (module:variable)
#
# The block below only runs the DEV server (python app.py).
# Gunicorn ignores it entirely.
if __name__ == "__main__":
    app.run()

print("App object ready for: gunicorn app:app")

3 Running Gunicorn & Setting Secrets

Start the production server with the gunicorn command. Add --workers to run several processes, and --bind to choose the address and port. Read your secrets from environment variables so they never live in your code.

# Set secrets in the environment (never hard-code them)
export SECRET_KEY="a-long-random-production-secret"
export DATABASE_URL="postgresql://user:pass@host/dbname"

# Run 4 worker processes, listening on all interfaces, port 8000
gunicorn --workers 4 --bind 0.0.0.0:8000 app:app

Your Flask code reads those variables with os.environ, so the same code is safe everywhere:

# config.py — production-safe settings from the environment
import os

class ProductionConfig:
    # Read secrets from the environment, never from source code.
    SECRET_KEY = os.environ.get("SECRET_KEY")
    SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
    DEBUG = False          # debug mode is OFF in production

# Simulate the server environment for this demo.
os.environ.setdefault("SECRET_KEY", "demo-secret-from-env")
os.environ.setdefault("DATABASE_URL", "sqlite:///prod.db")

cfg = ProductionConfig()
print("SECRET_KEY loaded:", bool(os.environ.get("SECRET_KEY")))
print("Database:", os.environ.get("DATABASE_URL"))
print("Debug mode:", ProductionConfig.DEBUG)

# ✅ Expected output:
# SECRET_KEY loaded: True
# Database: sqlite:///prod.db
# Debug mode: False

Hosting platforms like Render, Railway, or Fly.io let you set these environment variables in a dashboard and run your gunicorn command for you. The dev server's flask run stays on your laptop.

🎯 Your Turn: Fill in the Blanks

Complete the production config below. Replace each ___ so secrets come from the environment and debug is off.

# 🎯 YOUR TURN: replace each ___ for a safe production config
import ___                              # the module that reads env variables

class ProductionConfig:
    # Pull the secret key from the environment, not the source code
    SECRET_KEY = os.___.get("SECRET_KEY")
    SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL")
    DEBUG = ___                          # debug must be off in production

os.environ.setdefault("SECRET_KEY", "demo")
print("Secret loaded:", bool(os.environ.get("SECRET_KEY")))
print("Debug:", ProductionConfig.DEBUG)

Common Errors & Quick Reference

❌ Failed to find attribute 'app' in 'app'

Gunicorn could not find your app object. Check the module:variable name — for a factory use gunicorn "app:create_app()".

❌ The app works locally but crashes on the server

A missing package or unset environment variable is the usual cause. Confirm requirements.txt is complete and every os.environ.get value is set on the host.

TaskCommand / Code
Install Gunicornpip install gunicorn
Freeze dependenciespip freeze > requirements.txt
Install dependenciespip install -r requirements.txt
Run in productiongunicorn app:app
Local dev onlyflask run

🏆 Mini Challenge: A Deploy-Ready App

Make a tiny app deploy-ready by reading its port and secret from the environment.

# 🏆 Mini Challenge: a deploy-ready app
import os
from flask import Flask

app = Flask(__name__)
# Secret comes from the environment in production.
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-secret")

@app.route("/")
def home():
    return "Ready to deploy!"

# Hosts often provide the port via an env variable.
port = int(os.environ.get("PORT", 8000))

print("Secret configured:", bool(app.config["SECRET_KEY"]))
print(f"Run with: gunicorn --bind 0.0.0.0:{port} app:app")

❓ Frequently Asked Questions

Lesson complete — you can put Flask online!

You now know why the dev server stays local, how WSGI and Gunicorn serve real traffic, and how to pin dependencies and load secrets from the environment. Your app is ready for the world.

Practice quiz

Why should you not use 'flask run' in production?

  • It cannot read files
  • The dev server is single-threaded and not hardened
  • It does not support routes
  • It requires a database

Answer: The dev server is single-threaded and not hardened. The built-in dev server is for development; it is slow and insecure under real load.

What does WSGI stand for?

  • Web Server Gateway Interface
  • Web Socket Gateway Integration
  • Wide Scale Gunicorn Interface
  • Worker Service Gateway Instance

Answer: Web Server Gateway Interface. WSGI is the Web Server Gateway Interface, the standard between Python apps and servers.

Which is a production-grade WSGI server for Flask?

  • npm
  • webpack
  • Gunicorn
  • nodemon

Answer: Gunicorn. Gunicorn is a common production WSGI server you point at your app object.

What does the command 'gunicorn app:app' mean?

  • run two apps
  • app twice for redundancy
  • app database name
  • module app.py, variable app inside it

Answer: module app.py, variable app inside it. The first 'app' is the module/filename, the second is the Flask object.

What is requirements.txt used for?

  • pinning the exact dependencies to install
  • storing secrets
  • routing URLs
  • configuring the database schema

Answer: pinning the exact dependencies to install. It lists dependencies installed with pip install -r requirements.txt.

How should production secrets like SECRET_KEY be supplied?

  • hard-coded in app.py
  • from environment variables via os.environ
  • committed to git
  • in requirements.txt

Answer: from environment variables via os.environ. Read secrets from environment variables so they stay out of source code.

Which command freezes your installed packages into a file?

  • pip list
  • pip show
  • pip freeze > requirements.txt
  • pip dump

Answer: pip freeze > requirements.txt. pip freeze writes the current packages and versions to requirements.txt.

What should DEBUG be set to in production?

  • True
  • 1
  • 'on'
  • False

Answer: False. Debug mode must be off in production; it exposes a code-executing debugger.

Which Gunicorn flag chooses the address and port to listen on?

  • --bind
  • --port-only
  • --listen-addr
  • --socket-host

Answer: --bind. --bind 0.0.0.0:8000 sets the interface and port Gunicorn serves on.

Which Gunicorn flag runs several worker processes?

  • --threads
  • --workers
  • --procs
  • --forks

Answer: --workers. --workers N runs N worker processes to handle concurrent traffic.

Continue this course

Frequently asked questions

Why can't I use flask run in production?

Flask's built-in dev server is single-threaded, slow, and insecure. It is meant for development only. In production you run a real WSGI server like Gunicorn that handles many users at once safely.

What is WSGI?

WSGI (Web Server Gateway Interface) is the standard that lets Python web apps talk to web servers. Flask is a WSGI app, so any WSGI server such as Gunicorn or uWSGI can run it.

What is Gunicorn?

Gunicorn is a popular, production-grade WSGI server for Python. You point it at your app object — for example 'gunicorn app:app' — and it serves your Flask app to real users.

What is a requirements.txt file?

It lists your project's exact dependencies so the server can install them with 'pip install -r requirements.txt'. This guarantees the same packages run in production as on your machine.