Published on · Updated on: · By Emily Carter
- 10 min read
How to Deploy a Flask App on Heroku in 2026: Step-by-Step Guide
Deploying a Flask app on Heroku takes about 15 minutes if your project is structured correctly. The process requires three files Heroku cannot work without: a Procfile that tells it how to start your app, a requirements.txt listing your dependencies, and gunicorn as your production WSGI server. Get those three right and the rest is just git push.
One important thing to know before you start: Heroku ended its free tier in November 2022. In 2026, the minimum cost to run a Flask app on Heroku is around $10 per month — $5 for an Eco dyno and $5 for a basic Postgres add-on if your app uses a database. If that is a concern, there are Heroku alternatives worth evaluating before you commit.
This guide covers every step from project structure to production deployment, plus how to connect Postgres, manage environment variables, and fix the most common Heroku Flask errors.
What You Need Before You Start
Before running a single command, make sure you have:
- Python 3.10 or higher and a working Flask app
- Git installed and your project already in a repository
- Heroku CLI installed — download from devcenter.heroku.com/articles/heroku-cli
- A Heroku account — sign up at heroku.com; a paid plan is required (minimum Eco dyno at $5/month). See the full Heroku pricing breakdown to understand what you will pay.
If your project is not in a Git repo yet, run git init && git add . && git commit -m "initial commit" before continuing.
Want to skip the manual setup entirely? Kuberns detects your Flask stack automatically, configures Gunicorn, and deploys with HTTPS and CI/CD enabled — no Procfile, no buildpack selection, no config files.
Step 1 — Prepare Your Flask Project
Heroku needs four files at the root of your project. Without them, the build will fail or the app will crash on startup.
app.py — Bind to the PORT Environment Variable
The most common reason Flask apps fail on Heroku is binding to a hardcoded port. Heroku assigns a dynamic port via the PORT environment variable. You must use it:
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "Hello from Flask on Heroku!"
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port)
The if __name__ == "__main__" block is for local development only. Heroku runs gunicorn directly from the Procfile, so this block is not executed in production — but binding to os.environ.get("PORT") is still important for correct behavior.
requirements.txt — List All Dependencies
flask==3.0.3
gunicorn==22.0.0
If your app uses SQLAlchemy and Postgres, add:
flask-sqlalchemy==3.1.1
psycopg2-binary==2.9.9
Generate your current environment’s requirements with pip freeze > requirements.txt. Review the output and remove any development-only packages.
runtime.txt — Pin Your Python Version
python-3.12.9
Heroku supports Python 3.10, 3.11, 3.12, and 3.13 as of 2026. Without this file, Heroku picks its default version, which may not match your local environment and can cause subtle dependency issues.
.gitignore — Keep Secrets Out of Git
venv/
__pycache__/
*.pyc
.env
Never commit your .env file or virtual environment folder. Environment variables go into Heroku Config Vars, not source control.
Step 2 — Create the Procfile
Create a file named Procfile at the root of your project. No file extension:
web: gunicorn app:app
This tells Heroku: start a web dyno and run the app object inside the app.py module using gunicorn.
Breaking down the format:
web— the process type (this is the dyno that receives HTTP traffic)gunicorn— the production WSGI serverapp:app— module name : Flask application object name
If your Flask app object is named differently (e.g., application = Flask(__name__)), update accordingly: web: gunicorn app:application.
Why Gunicorn and Not the Flask Dev Server?
Flask’s built-in dev server (flask run) is single-threaded and designed for local development only. It cannot handle concurrent requests. Gunicorn is a battle-tested production WSGI server that spawns multiple worker processes to handle real traffic.
For a slightly higher-traffic app, you can specify worker count directly in the Procfile:
web: gunicorn app:app --workers 2 --timeout 120
On Heroku’s standard dynos, 2 workers per dyno is a reasonable starting point.
Step 3 — Deploy to Heroku
With your project files in place, deployment is four commands:
# Log in to Heroku (opens browser)
heroku login
# Create a new Heroku app (auto-generates a name, or specify one)
heroku create your-flask-app-name
# Push your code
git push heroku main
# Open the live URL
heroku open
You should see output like this during the build:
remote: -----> Python app detected
remote: -----> Installing python-3.12.9
remote: -----> Installing pip 24.x
remote: -----> Installing requirements with pip
remote: Successfully installed flask-3.0.3 gunicorn-22.0.0
remote: -----> Discovering process types
remote: Procfile declares types -> web
remote: -----> Launching...
remote: Released v3
remote: https://your-flask-app-name.herokuapp.com/ deployed to Heroku
If the build completes but the app shows “Application Error”, jump to the troubleshooting section below.
You can also connect your GitHub repository for automatic deployments on every push — see the Heroku GitHub integration guide for the full setup.
Step 4 — Set Environment Variables
Never hardcode secrets, API keys, or configuration into your codebase. Heroku provides Config Vars for this:
heroku config:set SECRET_KEY=your-very-long-random-secret-key
heroku config:set FLASK_ENV=production
heroku config:set DEBUG=False
Access them in Flask the same way you would any environment variable:
import os
SECRET_KEY = os.environ.get("SECRET_KEY")
DEBUG = os.environ.get("DEBUG", "False").lower() == "true"
You can also set Config Vars in the Heroku dashboard: go to your app → Settings → Config Vars → Reveal Config Vars. Useful when you want to rotate a key without touching the CLI.
To see all current config vars: heroku config
Step 5 — Add Postgres to Your Flask App
If your Flask app needs a database, the Heroku Postgres add-on is the most straightforward option. The Essential-0 plan starts at $5/month.
Provision the Add-on
heroku addons:create heroku-postgresql:essential-0
Heroku automatically sets the DATABASE_URL environment variable. Verify it:
heroku config | grep DATABASE_URL
Connect Flask-SQLAlchemy
Update your requirements.txt:
flask==3.0.3
gunicorn==22.0.0
flask-sqlalchemy==3.1.1
psycopg2-binary==2.9.9
Configure SQLAlchemy in your Flask app:
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# Heroku sets DATABASE_URL as postgres:// but SQLAlchemy needs postgresql://
database_url = os.environ.get("DATABASE_URL", "").replace("postgres://", "postgresql://", 1)
app.config["SQLALCHEMY_DATABASE_URI"] = database_url
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
Note the replace("postgres://", "postgresql://", 1) — Heroku still uses the older postgres:// scheme in its DATABASE_URL, but SQLAlchemy 1.4+ requires postgresql://. This one-liner handles the mismatch.
Run Migrations
If you are using Flask-Migrate:
heroku run flask db upgrade
This runs the migration command inside a one-off dyno on Heroku’s infrastructure, against your production database.
Common Heroku Flask Errors and Fixes
H10 — App Crashed on Boot
Cause: Heroku tried to start your app and it exited immediately. Common causes: syntax error in app.py, missing import, or wrong Procfile entry.
Fix: Run heroku logs --tail immediately after deployment and look for the Python traceback. Fix the error and redeploy.
H14 — No Web Dynos Running
Cause: Heroku deployed your app but there are no web dynos scaled up to serve traffic.
Fix:
heroku ps:scale web=1
This scales one web dyno. Without it, your app is deployed but not running.
Application Error (Generic)
Cause: App is running but returning a 500 or crashing on a specific request.
Fix: heroku logs --tail while hitting the failing endpoint. The Python traceback will tell you exactly what broke.
ModuleNotFoundError: No module named ‘gunicorn’
Cause: gunicorn is missing from requirements.txt.
Fix: Add gunicorn==22.0.0 to your requirements.txt, commit, and redeploy.
Port Already in Use / App Not Starting
Cause: Your app.run() call uses a hardcoded port like 5000 instead of reading from the PORT environment variable.
Fix: Change to app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000))).
Build Succeeded But Wrong Python Version Behaviour
Cause: No runtime.txt in your repo — Heroku used a different Python version than your local environment.
Fix: Add a runtime.txt file with your exact version: python-3.12.9.
Heroku Flask Deployment Costs in 2026
Running a Flask app on Heroku with a database costs at minimum:
| Resource | Plan | Monthly Cost |
|---|---|---|
| Web Dyno | Eco | $5/month (1000 hours shared) |
| Postgres | Essential-0 | $5/month |
| Total | ~$10/month |
Eco dynos sleep after 30 minutes of inactivity — your app will have a cold start delay of 10–30 seconds on the first request after sleeping. Basic dynos ($7/month each) do not sleep.
At higher scale, costs increase quickly. A production Flask app on multiple Standard-2X dynos with a Postgres Standard-0 plan can run $200+/month. See the full Heroku pricing guide for a complete breakdown.
Is Heroku Still Worth It for Flask in 2026?
Heroku’s developer experience remains one of the best in the industry. Git push deployments, seamless add-ons, reliable uptime, and mature documentation make it genuinely easy to get a Flask app running in production.
The tradeoffs are real though. No free tier means even hobby projects cost money. Eco dynos sleep in ways that hurt user experience. And at scale, deploying a Python app on Heroku becomes expensive compared to alternatives that give you more compute per dollar.
For a side project or small team app, Heroku is still a solid choice. For anything where cost-per-request matters or where you need zero cold starts, the Heroku alternatives are worth a serious look before committing.
Frequently Asked Questions
Q: Can I deploy a Flask app on Heroku for free?
Heroku removed its free tier in November 2022. The cheapest option in 2026 is the Eco dyno at $5/month for 1000 shared dyno hours, plus separate add-on costs for databases. There is no free Flask hosting on Heroku anymore.
Q: What is a Procfile and why does Flask need one on Heroku?
A Procfile is a plain text file at the root of your project that tells Heroku how to start your application. Flask needs one because Heroku does not use the built-in Flask dev server in production. The standard entry is web: gunicorn app:app — no file extension, exact casing required.
Q: Why do I need Gunicorn to deploy Flask on Heroku?
Flask’s built-in server is single-threaded and explicitly documented as not safe for production use. Gunicorn is a production-grade WSGI server that handles concurrent HTTP requests across multiple worker processes. Heroku requires a production WSGI server and will not accept the Flask dev server.
Q: How do I connect a Postgres database to my Flask app on Heroku?
Run heroku addons:create heroku-postgresql:essential-0 to provision the database. Heroku automatically sets the DATABASE_URL environment variable. In Flask, read it with os.environ.get('DATABASE_URL') and pass it to SQLAlchemy. Remember to replace postgres:// with postgresql:// in the URL for SQLAlchemy 1.4+ compatibility.
Q: How do I set environment variables for a Flask app on Heroku?
Use the CLI: heroku config:set SECRET_KEY=your-value. Or set them in the Heroku dashboard under Settings → Config Vars. These are injected as standard environment variables at runtime, accessible via os.environ.get('SECRET_KEY') in your Flask app.
Q: What Python versions does Heroku support in 2026?
Heroku supports Python 3.10, 3.11, 3.12, and 3.13 as of 2026. Pin your exact version in a runtime.txt file at the project root — for example, python-3.12.9. Without it, Heroku chooses its own default, which may cause dependency mismatches.
Q: How do I fix the Application Error on a Heroku Flask deployment?
Run heroku logs --tail and look for the Python traceback. The most common causes are: missing gunicorn in requirements.txt, incorrect Procfile format, hardcoded port instead of reading from PORT env var, or missing required environment variables. Fix the root cause and push again.
Q: Can I deploy a Flask REST API on Heroku?
Yes. Flask REST APIs deploy identically to standard Flask web apps. Use Gunicorn in your Procfile, add CORS headers with flask-cors if needed, set your DATABASE_URL and any API keys as Config Vars, and push. The deployment process is the same regardless of whether your Flask app serves HTML or JSON.
Conclusion
Deploying Flask on Heroku comes down to three files — Procfile, requirements.txt, and runtime.txt — plus Gunicorn as your production server. Get those right, bind to the PORT environment variable, and the rest of the deployment is straightforward.
For teams who want to skip the manual configuration entirely, Kuberns handles all of this automatically — stack detection, Gunicorn setup, HTTPS, CI/CD, and AWS-grade infrastructure — without a single config file.