Wednesday, March 11, 2026

How to Deploy Flask App on Heroku

by Marcus Thorne
How To Deploy Flask App On Heroku

How to Deploy Flask App on Heroku

Deploying a Flask app on Heroku involves packaging your application with a Gunicorn web server, specifying dependencies in requirements.txt, and defining the start command in a Procfile. After initializing a Git repository and creating a Heroku app, simply push your codebase to Heroku’s Git remote, allowing their buildpacks to containerize and deploy your application automatically onto dynos.

Metric Details
Complexity (Initial Setup) Low to Moderate (Requires CLI familiarity, ~15-30 min)
Complexity (Maintenance) Low (Automated deployments, easy scaling)
Platform Support Heroku-20, Heroku-22 stacks (Ubuntu-based); Official Python Buildpack supporting Python 3.7+ (currently 3.9, 3.10, 3.11, 3.12 recommended).
Memory Footprint (Free Dyno) 512 MB RAM (shared, prone to cold starts/sleep after 30 mins inactivity)
Memory Footprint (Hobby Dyno) 512 MB RAM (dedicated, no cold starts, always on)
Typical Flask App Size ~50-150 MB (Python interpreter, Flask, Gunicorn, dependencies, app code)
Deployment Time 1-5 minutes (initial buildpack download & slug compilation), subsequent pushes ~30-90 seconds.

The “Senior Dev” Hook

In my experience, one of the most common pitfalls junior developers encounter when deploying a Flask application to Heroku is underestimating the importance of a correct Procfile and ensuring all dependencies are pinned in requirements.txt. I’ve personally wasted hours troubleshooting “H10 App Crashed” errors only to find out it was a missing Gunicorn entry or an unpinned dependency causing a version mismatch in Heroku’s build environment. Get those two right, and half your battle is won.

Under the Hood Logic: How Heroku Deploys Flask

When you push your code to Heroku, it doesn’t just run your Python script directly. Heroku operates on a “slug compiler” model. Here’s the sequence:

  1. Git Push Trigger: Your git push heroku main command initiates the deployment. Heroku’s Git remote receives your code.
  2. Buildpack Detection: Heroku inspects your repository for specific files. For Python, it looks for requirements.txt (or Pipfile/pyproject.toml with Poetry/Pipenv). Upon detection, the Heroku Python Buildpack is activated.
  3. Dependency Installation: The buildpack sets up a compatible Python runtime and then uses Pip to install all dependencies listed in your requirements.txt into a isolated virtual environment within the build slug.
  4. Slug Compilation: Your application code, along with its dependencies and the Python runtime, are bundled into a “slug” – a compressed, optimized package ready for execution.
  5. Dyno Formation: Once the slug is compiled, Heroku provisions one or more “dynos” – lightweight Linux containers. Each dyno is an isolated environment where your slug is unpacked.
  6. Procfile Execution: Heroku then looks for a Procfile in the root of your project. This file defines the commands Heroku should run to start your application’s processes. For a web application, it typically specifies how to start a web server like Gunicorn, pointing it to your Flask app.
  7. Application Startup: The command from your Procfile is executed, starting Gunicorn, which in turn serves your Flask application. Heroku handles routing incoming HTTP requests to your running web dynos.

This entire process ensures a consistent and reproducible deployment environment, abstracting away the underlying infrastructure details.

Step-by-Step Implementation: Deploying Your Flask Application

1. Set Up Your Local Flask Application

First, ensure you have Python installed locally. I recommend using a virtual environment for dependency isolation.


# Create a new project directory
mkdir my-flask-app
cd my-flask-app

# Create and activate a virtual environment
python3 -m venv venv
source venv/bin/activate

# Install Flask and Gunicorn
pip install Flask gunicorn

Next, create your main Flask application file. For this example, we’ll name it app.py.


# app.py
from flask import Flask, render_template_string
import os

app = Flask(__name__)

# Basic route
@app.route('/')
def hello():
    # Example to show environment variables working on Heroku
    # Heroku sets PORT, DATABASE_URL (if addon present), etc.
    message = os.environ.get('WELCOME_MESSAGE', 'Hello from Heroku Flask!')
    return render_template_string(f'''
        
        
        Flask on Heroku

        
            

{message}

This app is running on port: {os.environ.get('PORT', 'Not set')}

Python Version: {os.environ.get('PYTHON_VERSION', 'Unknown')}

''') if __name__ == '__main__': # This block is for local development only. # Heroku will use Gunicorn to run the app. app.run(debug=True, port=os.environ.get('PORT', 5000))

Now, generate your requirements.txt file to list all dependencies for Heroku’s buildpack.


pip freeze > requirements.txt

This command creates a file like this:


Flask==2.3.3
gunicorn==21.2.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.3
Werkzeug==2.3.7

Finally, create a Procfile in the root of your project. This tells Heroku how to start your web process.


# Procfile
web: gunicorn --bind 0.0.0.0:$PORT wsgi:app

Explanation:

  • web: signifies a web process, which Heroku will route HTTP traffic to.
  • gunicorn is the production-ready WSGI HTTP server.
  • --bind 0.0.0.0:$PORT tells Gunicorn to listen on all network interfaces and use the port provided by Heroku via the PORT environment variable.
  • wsgi:app instructs Gunicorn to look for a module named wsgi and within it, an application object named app. For simple setups, if your app object is named app in app.py, you can often use app:app directly. However, using a separate wsgi.py is a common best practice for clarity and complex setups.

To adhere to the wsgi:app pattern in the Procfile, create a new file named wsgi.py:


# wsgi.py
from app import app

if __name__ == "__main__":
    app.run()

This wsgi.py file simply imports your Flask application instance, making it available for Gunicorn.

2. Heroku Setup and Deployment

If you don’t have the Heroku CLI installed, install it now. Then log in:


heroku login
# Follow the browser prompt to log in.

Now, initialize a Git repository and commit your files:


git init
git add .
git commit -m "Initial Flask app for Heroku deployment"

Create a new Heroku application. You can specify a name or let Heroku generate one.


# Option 1: Let Heroku generate a name
heroku create

# Option 2: Specify a name (must be unique)
# heroku create my-unique-flask-app-name

Heroku will add a new Git remote named heroku to your repository.

Finally, deploy your application by pushing your code to the Heroku remote:


git push heroku main

Heroku will detect the Python buildpack, install dependencies, compile the slug, and start your dyno. Once complete, open your application in the browser:


heroku open

You should see your “Hello from Heroku Flask!” message.

3. (Optional) Adding a Database

Most Flask apps need a database. Heroku makes it easy with add-ons. Heroku Postgres is a popular choice.


heroku addons:create heroku-postgresql:hobby-dev

This adds a PostgreSQL database and sets the DATABASE_URL environment variable, which your Flask app can access via os.environ.get('DATABASE_URL'). You’d typically use an ORM like SQLAlchemy with Flask-SQLAlchemy to connect to it.

What Can Go Wrong (Troubleshooting)

  • H10 App Crashed: This is the most common and generic error.

    • Check Heroku Logs: Always run heroku logs --tail immediately after a failed deploy or when your app isn’t responding. This provides crucial information.
    • Procfile Issues: Ensure your Procfile is correctly named (case-sensitive: Procfile, no extension) and the command is accurate (e.g., web: gunicorn wsgi:app). A typo here means Heroku can’t start your web process.
    • Missing Dependencies: If requirements.txt is incomplete, your app might crash due to ModuleNotFoundError. Ensure you used pip freeze > requirements.txt in your activated virtual environment.
    • Port Binding: Ensure your Gunicorn (or other server) is binding to 0.0.0.0:$PORT. Heroku dynamically assigns the port.
    • Syntax Errors: Python syntax errors in your application code will prevent Gunicorn from starting correctly. Check your logs for Python tracebacks.
  • Cannot push because you have uncommitted changes: Git requires a clean working directory for pushing.

    • Solution: Either commit your changes (git add . && git commit -m "message") or stash them (git stash) before pushing.
  • Slug size too large: Heroku has a 500 MB slug size limit.

    • Solution: Exclude unnecessary files (e.g., local database files, large data files not needed for deployment) using a .slugignore file, similar to .gitignore.
  • Local vs. Heroku Environment Variables: If your app works locally but fails on Heroku, check environment variables.

    • Solution: Use heroku config:set KEY=VALUE to set environment variables on Heroku. Use heroku config to view them.

Performance & Best Practices

While Heroku provides an excellent developer experience, it’s essential to understand its nuances for optimal performance and cost-effectiveness.

When NOT to use this approach (or at least, the free/hobby tier):

  • High-traffic, mission-critical applications: Free and Hobby dynos are not designed for high availability or consistent low latency under heavy load. They lack autoscaling features present in professional dynos or dedicated platforms.
  • Applications requiring significant custom infrastructure: If you need specific networking configurations, direct access to underlying VMs, or complex microservices architectures, alternatives like AWS ECS, Google Cloud Run, or self-managed Kubernetes might be more suitable.
  • Strict data residency requirements: While Heroku is compliant with many standards, specific enterprise-level data residency needs might require infrastructure in particular regions that Heroku may not directly support at all tiers.
  • Very large applications: Heroku’s slug size limit (500 MB) can be a constraint for apps with many static assets or large embedded data files.

Alternative Methods & Comparison (Legacy vs. Modern):

  • AWS Elastic Beanstalk / App Runner: Offers a similar PaaS experience but within the AWS ecosystem, providing more integration with other AWS services and often more granular control and cost optimization for larger deployments. App Runner, in particular, is a serverless container service that is very “Heroku-like”.
  • Google Cloud Run: A fully managed serverless platform for containerized applications. It scales automatically from zero to thousands and is priced per request, making it extremely cost-effective for fluctuating workloads. This is a very modern and competitive alternative to Heroku for many use cases.
  • DigitalOcean App Platform / Render: Other PaaS providers that offer similar experiences to Heroku, often with competitive pricing and slightly more control.
  • Self-hosted Kubernetes: For maximum control, scalability, and resource optimization, deploying Flask apps within a Kubernetes cluster (on any cloud provider or bare metal) is the most powerful but also the most complex. This requires significant DevOps expertise.

Best Practices for Heroku Flask Deployments:

  • Pin Dependencies: Always use pip freeze > requirements.txt to ensure specific versions are used, preventing unexpected breaks from dependency updates.
  • Gunicorn Worker Count: The default Gunicorn configuration (`gunicorn wsgi:app`) typically starts 1 worker. For better concurrency, especially on larger dynos, consider adding more workers. A common heuristic is --workers=(2 * CPU_CORES) + 1. For Heroku’s standard dynos, starting with 2-4 workers is a good baseline (e.g., web: gunicorn --workers 3 --bind 0.0.0.0:$PORT wsgi:app).
  • Environment Variables: Store sensitive information (API keys, database credentials) and configuration specifics as Heroku Config Vars, not directly in your code. Access them using os.environ.get('YOUR_VAR').
  • Logging: Heroku aggregates logs from all dynos. Use Python’s standard logging module. Heroku automatically captures stdout/stderr. Avoid writing logs to the filesystem, as dyno filesystems are ephemeral.
  • Static Files: Heroku’s filesystem is ephemeral and not suitable for serving static files in production. Use a dedicated static file server (like AWS S3 or Cloudinary) or a CDN for production. For simple cases, Flask’s static file serving is fine for development but not production scale.
  • Scale Appropriately: Start with Hobby dynos for non-trivial applications to avoid cold starts and ensure your app is always responsive. Scale up to Professional dynos as traffic demands.

For more on this, Check out more Web Development Tutorials.

Author’s Final Verdict

In my opinion, for small to medium-sized Flask applications, prototypes, or educational projects, Heroku remains an unparalleled deployment platform due to its simplicity and robust developer experience. The “Git push to deploy” model significantly reduces friction. However, as your application grows in complexity, traffic, or requires specific resource guarantees, you’ll need to critically evaluate if the cost-benefit analysis favors scaling up on Heroku or migrating to a more specialized cloud platform like Google Cloud Run or AWS App Runner. For many developers, starting with Heroku is still the smartest, most efficient path to getting a Flask application live.

Have any thoughts?

Share your reaction or leave a quick response — we’d love to hear what you think!

Related Posts

Leave a Comment