DEV Community

Cover image for How to Deploy a Python Flask App on Heroku Using Docker
Evan J
Evan J

Posted on • Updated on

How to Deploy a Python Flask App on Heroku Using Docker

Hello!
Today I will demonstrate how to deploy a containerized Python Flask application to Heroku using Docker.
This tutorial is separated into four parts:

  1. Prerequisites
  2. Creating the Flask application using Python
  3. Containerizing the application using Docker
  4. Deploying the container to Heroku

Prerequisites

  1. Flask
  2. A Docker installation
  3. A Heroku Account / Heroku CLI installation

Creating the Flask application using Python

We will be creating a "Hello World!" application to maintain simplicity.
The file structure by the end of this tutorial will look like:

├───FlaskApp
  └───app.py           # Our main application
  └───main.py          # Used by gunicorn to run the app
  └───requirements.txt # The packages we will be using
  └───Dockerfile       # Used to create the Docker container
  └───Procfile         # Used to deploy the container to Heroku
Enter fullscreen mode Exit fullscreen mode

To start out, create an empty directory using:

$ sudo mkdir FlaskApp
Enter fullscreen mode Exit fullscreen mode

And cd into the directory using:

$ cd FlaskApp
Enter fullscreen mode Exit fullscreen mode

Create two files called app.py and requirements.txt.
In requirements.txt, insert the line:

flask==2.0.1
gunicorn==20.1.0
Enter fullscreen mode Exit fullscreen mode

Then, to install the required packages, run:

$ pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

Then open up app.py in your favorite code editor and add the following code:

#Import the flask module
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    statement = 'Hello World!'
    return statement


#Calls the run method, runs the app on port 5000
app.run(host='0.0.0.0', port='5000')
Enter fullscreen mode Exit fullscreen mode

Next, create a file called main.py and add the following code:

from app import app

# Gets the app from app.py and runs it
app.run()
Enter fullscreen mode Exit fullscreen mode

Then run the program using:

$ gunicorn --bind 0.0.0.0:5000 main:app
Enter fullscreen mode Exit fullscreen mode

And you should get an output that looks like:

[2021-06-25 10:33:51 -0400] [1607] [INFO] Starting gunicorn 20.1.0
[2021-06-25 10:33:51 -0400] [1607] [INFO] Listening at: http://0.0.0.0:5000 (1607)
[2021-06-25 10:33:51 -0400] [1607] [INFO] Using worker: sync
[2021-06-25 10:33:51 -0400] [1609] [INFO] Booting worker with pid: 1609
Enter fullscreen mode Exit fullscreen mode

After accessing the URL that was provided upon executing at http://yourip:5000, the page should look something like:
Example Output

Now that your Python Application is up and running, let's go over how to containerize it using Docker.

Containerizing the application using Docker

In your FlaskApp directory, create a file with no file extension called Dockerfile and add the following:

#Create a ubuntu base image with python 3 installed.
FROM python:3.8

#Set the working directory
WORKDIR /

#copy all the files
COPY . .

#Install the dependencies
RUN apt-get -y update
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install -r requirements.txt

#Expose the required port
EXPOSE 5000

#Run the command
CMD gunicorn main:app
Enter fullscreen mode Exit fullscreen mode

After this, build the Docker image by running:

$ docker build -t flask-app .
Enter fullscreen mode Exit fullscreen mode

And then run it using:

$ docker run flask-app
Enter fullscreen mode Exit fullscreen mode

And you should get the same output as if you were running it normally:

2021-06-25T15:08:47.958205+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [5] [INFO] Starting gunicorn 20.1.0
2021-06-25T15:08:47.958983+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [5] [INFO] Listening at: http://0.0.0.0:5000 (5)
2021-06-25T15:08:47.958983+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [5] [INFO] Using worker: sync
2021-06-25T15:08:47.962897+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [7] [INFO] Booting worker with pid: 7
Enter fullscreen mode Exit fullscreen mode

And you should get the expected output at the given IP address at http://yourip:5000:
Example Output
Congratulations! You have successfully created a Flask application and containerized it using Docker!
Now we will go over how to deploy this container to Heroku using their container registry.

Deploying the container to Heroku

Given that the Heroku CLI is installed as stated in the prerequisites, run:

$ heroku login
Enter fullscreen mode Exit fullscreen mode

And you will be prompted to enter your Heroku credentials.
Once logged in, create an application by running:

$ heroku create <your_app_name>
Enter fullscreen mode Exit fullscreen mode

Next, login to the Heroku container registry by running:

$ heroku container:login
Enter fullscreen mode Exit fullscreen mode

Which should produce:

Login Succeeded
Enter fullscreen mode Exit fullscreen mode

In your FlaskApp directory, create a file with no extension called Procfile and add:

web: gunicorn main:app 0.0.0.0:$PORT
Enter fullscreen mode Exit fullscreen mode

After that, in our app.py file, we need to add/modify the following lines marked with arrows:

# Import OS to get the port environment variable from the Procfile
import os # <-----
# Import the flask module
from flask import Flask

# Create a Flask constructor. It takes name of the current module as the argument
app = Flask(__name__)

@app.route('/')
def hello_world():
    statement = 'Hello World!'
    return statement


# Create the main driver function
port = int(os.environ.get("PORT", 5000)) # <-----
app.run(host='0.0.0.0', port=port)       # <-----
Enter fullscreen mode Exit fullscreen mode

After editing the file, we must push the container to Heroku using:

$ heroku container:push web --app <your_app_name>
Enter fullscreen mode Exit fullscreen mode

After your app's container builds, release it to Heroku using:

$ heroku container:release web --app <your_app_name>
Enter fullscreen mode Exit fullscreen mode

Which will produce the following:

Releasing images web to <your_app_name>... done
Enter fullscreen mode Exit fullscreen mode

Once it is released, you can access the logs of your new container using:

$ heroku logs --tail --app <your_app_name>
Enter fullscreen mode Exit fullscreen mode

And you should see something along the lines of:

2021-06-25T15:08:45.662062+00:00 heroku[web.1]: Starting process with command `/bin/sh -c gunicorn\ main:app`
2021-06-25T15:08:47.958205+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [5] [INFO] Starting gunicorn 20.1.0
2021-06-25T15:08:47.958983+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [5] [INFO] Listening at: http://0.0.0.0:34683 (5)
2021-06-25T15:08:47.958983+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [5] [INFO] Using worker: sync
2021-06-25T15:08:47.962897+00:00 app[web.1]: [2021-06-25 15:08:47 +0000] [7] [INFO] Booting worker with pid: 7
2021-06-25T15:08:48.409823+00:00 heroku[web.1]: State changed from starting to up
Enter fullscreen mode Exit fullscreen mode

And you should now be able to access your application through your new app's URL with the expected output of:
Heroku Output

Congratulations! This tutorial has taught you how to create a Python Flask application, containerize it, and then deploy it to Heroku.

Discussion (4)

Collapse
krzysztofwelc profile image
Krzysztof Welc

hello, what;s the point of having

if __name__ == '__main__'

in app.py if Procfile runs main.py?

Collapse
ejach profile image
Evan J Author

That is an excellent point.
I have become so accustomed to writing that line in every Python file I create that I include that no matter what. I will update the article with this change to reduce redundancy, thank you for the insight!

Collapse
devarjunan profile image
dev-arjunan

Great. Thanks for sharing this article.

Collapse
ejach profile image
Evan J Author

Of course!