Deploy a Flask app on Heroku using Docker

Written while containerising a Detectron2-backed deep learning project of mine. Every tutorial I found went either far too deep or stopped halfway, so this is the whole path in one place.

Dockerising a Flask app before it goes to Heroku buys you one thing worth having: you stop being limited to what Heroku offers. The image runs anywhere. Moving to another cloud provider becomes a deploy, not a rewrite.

If you’ve never touched Docker, that’s fine — everything you need is below.

Why Docker

Containerising is a good way to put a machine learning model into production. Docker uses OS-level virtualisation to ship software as containers: the model, its Python version, its system libraries and its weights all travel together, so the thing you tested is the thing that runs.

Image versus container

An image is the software package you build from your project. A container is a running instance of that image, and one image can have many containers. The distinction is subtle enough that plenty of people use the words interchangeably — it’s worth getting right before you start reading error messages.

The Dockerfile

A Dockerfile is an extensionless text file telling the Docker daemon how to build your image.

An example Dockerfile with FROM, RUN, EXPOSE and ENTRYPOINT instructions
A minimal Dockerfile for a Flask app.
  • FROM — the base image. Here, the official Python 3.7 image. You can build on top of anything.
  • RUN — runs a command at build time, baking the result into the image.
  • EXPOSE — tells the daemon to make port 5000 available for external mapping. If your app serves on 5000, this is the line.
  • ENTRYPOINT and CMD — what actually runs. In this case, python app.py.

Installing Docker

Register an account, then install Docker Desktop from the official docs. On Windows you’ll need 10 version 1903 (May 2019) or Enterprise. Start the app once before you go any further.

Building the image

Your project root needs three files:

  • Dockerfile
  • requirements.txt
  • app.py

In app.py, guard the entry point with if __name__ == "__main__" and — this is the part people miss — set the host explicitly:

app.run(host="0.0.0.0", port=5000)

Without the host, the server binds to localhost inside the container and nothing outside it can connect. The app will look broken; it isn’t.

From the project root:

docker build --tag flask-demo-app .

Then run it, mapping your port 5000 to the container’s:

docker run -d -p 5000:5000 flask-demo-app

Open localhost:5000 and the app should be up. docker ps lists what’s running — you’ll need the container ID from there to stop it.

Deploying to Heroku

  • Install the Heroku CLI via your package manager or the binary, and check it with heroku --version.
  • heroku login, then heroku container:login to register your container.

Create the app, push the image, release it:

heroku create demo-app
heroku container:push web --app demo-app
heroku container:release web --app demo-app

That’s it — the app is in production. Worth reading next: dynos, the abstraction that keeps you out of infrastructure management, and the CLI’s log and repository commands, which are where you’ll live the first time something misbehaves.

Found this useful, or found a gap in it? Tell me.


All writing