Python Like A Pro: Building Docker Containers

Ben Wilcock

Packaging your application code into Docker containers is a tricky business. Python code is no exception. There are a ton of best practices that you need to know about if you’re going to build a container that is safe, secure, and maintainable over the long term. Buildpacks codify these best practices, and they’re open-source, so they’re a great way to turn your application code into runnable containers.

And because buildpacks completely remove the need for a Dockerfile, they dramatically simplify the maintenance of your container images — particularly useful if you have multiple images to maintain. They’re no ‘flash-in-the-pan’ either. Developed by Heroku in 2011, and also used in Cloud Foundry, buildpacks have built and run millions of production workloads!

For Python developers, there are currently two implementations of the Buildpack standard which provide Python compatible buildpacks. One is from Google and the other is from Heroku. In this guide, you’ll learn how to use Heroku’s Python buildpack to create a container image for a sample Python application.

Before You Begin

There are a few things you need to do before getting started with Python Buildpacks:

  • Install Docker Desktop. The pack CLI requires the Docker daemon, so you’ll need to have that installed and running locally.

  • Check out Containers 101 on KubeAcademy, particularly if you’ve never worked with containers or Docker before.

  • Follow the documentation for installing pack in your local environment.

  • [Optional] If you are completely new to buildpacks, you might prefer to first read up on what are buildpacks?

Using Buildpacks With Python

Buildpacks work the same way no matter what language the code is written in. In this guide you’ll use a simple Python application, but remember that the process works in the same way for Node.JS, Java, Go, PHP, and more.

Follow the steps below to quickly create a container image for a Python application using the Heroku Python Buildpack.

Get The Sample Python Application

Download the sample Python application from Github and make the sample application’s folder your current working directory as follows:

> git clone https://github.com/benwilcock/buildpacks-python-demo.git
> cd buildpacks-python-demo

In the folder you will notice three text files, web.py, requirements.txt, and Procfile.

The file web.py contains a hello-world web application written in Python3 using the Flask and MarkupSafe libraries.

from flask import Flask
from markupsafe import escape
app = Flask(__name__)

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

@app.route('/hello/<username>')
def hello_user(username):
    # say hello to that user
    return 'Hello %s' % escape(username)

The file requirements.txt clarifies which libraries your application is dependent on, and their versions. Pip can generate the contents of this file for you using the command pip freeze.

click==7.1.2
Flask==1.1.2
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
Werkzeug==1.0.1

The Procfile specifies the command-line used to execute the application at runtime. In this case the Procfile declares that the web.py file contains your FLASK_APP, calls flask run, and binds the web-server to the --host with the IP address 0.0.0.0. It is expected that the --port to bind the application to will be set as an environment variable using the name $PORT.

web: FLASK_APP=web.py python3 -m flask run --host=0.0.0.0 --port=$PORT

Get The List Of Suggested Builders

There are many implementations of the Buildpacks standard. These implementations are called ‘builders’. To discover the very latest list of suggested builders, use the pack suggest-builders command as follows:

> pack suggest-builders

Suggested builders:
	Google:                gcr.io/buildpacks/builder:v1                 Ubuntu 18 base image with buildpacks for .NET, Go, Java, Node.js, and Python
	Heroku:                heroku/buildpacks:18                         heroku-18 base image with buildpacks for Ruby, Java, Node.js, Python, Golang, & PHP
	Paketo Buildpacks:     gcr.io/paketo-buildpacks/builder:full-cf     cflinuxfs3 base image with buildpacks for Java, .NET, NodeJS, Golang, PHP, HTTPD and NGINX
        ...

As you can see, both Heroku and Google Cloud Platform offer open-source Python compatible builders. For this exercise, you’ll use the Heroku builder, but you could just as easily use Google’s.

Set Heroku As Your Default Builder

Copy the name of the builder that you want to set as your default from the list above — in this case it’s heroku/buildpacks:18 — and use the pack set-default-builder command to set this buildpack as the default as shown below:

> pack set-default-builder heroku/buildpacks:18
Builder heroku/buildpacks:18 is now the default builder

Use pack To Create Your Container Image

To run the builder and create your Python application container image, use the command pack build. Be sure to also specify an image name for the container in the format “<repository>/<container-name>:<tag>” as shown in the following example:

> pack build benwilcock/python-sample:1.0.0

The process of building the image will now begin. The first time you do this, you will notice that docker is downloading a series of container image ‘layers.’ This is because buildpacks are also containers, so they must first be pulled by Docker before the buildpack can be run locally. Once these images are in your cache, the process is much quicker. The output looks something like this:

18: Pulling from heroku/buildpacks
4e20becbd46f: Pull complete
3c742a4a0f38: Already exists
ab0f59294661: Downloading [=============================>                     ]  16.05MB/26.96MB
Digest: sha256:296e4f3394e3147a61bd8b08d3c46c0dfa2bf2d4266ed598241cf2419dc96fa3
Status: Image is up to date for heroku/buildpacks:18
18: Pulling from heroku/pack
Digest: sha256:219a7621db58790ace66a87d33a200cd89aeda03192994e11a05967fbf8892f6
Status: Image is up to date for heroku/pack:18
===> DETECTING
heroku/python   0.2
heroku/procfile 0.5
===> ANALYZING
Restoring metadata for "heroku/python:shim" from cache
===> RESTORING
Restoring data for "heroku/python:shim" from cache
===> BUILDING
-----> No change in requirements detected, installing from cache
-----> Installing SQLite3
-----> Installing requirements with pip
-----> Discovering process types
       Procfile declares types     -> web
===> EXPORTING
Reusing layer 'launcher'
Reusing layer 'heroku/python:profile'
Adding 1/1 app layer(s)
Reusing layer 'config'
*** Images (a904788f7748):
      index.docker.io/benwilcock/python-sample:1.0.0
Adding cache layer 'heroku/python:shim'
Successfully built image benwilcock/python-sample:1.0.0

When you see the words “Successfully built image” the process is complete. Your new container image will now be available in your local Docker image repository. You can list the images in your local repository with the command docker images.

> docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
benwilcock/python-sample    1.0.0               59843a212207        40 years ago        651MB

Already we’re benefiting from buildpack engineering! Notice that the CREATED date says “40 years ago”. This is a best practice whereby the timestamps of layers are ‘zeroed’ to make container builds more reproducible, cache-able, and to avoid unnecessary image downloads. You can read more here.

Test The Container Image

Testing the container is no more difficult than running the image with the docker run command as follows:

> docker run -d -ePORT=8080 -p8080:8080 --name python-sample benwilcock/python-sample:1.0.0

Now the container image of your application is running in the background, simply query the http://localhost:8080 endpoint, either using a command-line tool like Httpie as shown below, or a regular web browser.

> http localhost:8080/

Your application will respond with the legend “Hello, World!” like so:

HTTP/1.0 200 OK
Content-Length: 13
Content-Type: text/html; charset=utf-8
Date: Tue, 21 Jul 2020 10:27:47 GMT
Server: Werkzeug/1.0.1 Python/3.6.11

Hello, World!

And you’re done! You built your sample Python application into an OCI compliant Docker container image without resorting to a Dockerfile. Heroku will take care of most of the underlying maintenance tasks on the operating system and the Python interpreter. All you need to do is re-run pack build and you’ll get a fresh image. If nothing changed, neither will your image.

Tidy Up

You can stop and remove the container from Docker as follows:

> docker stop python-sample
> docker rm python-sample

Keep Learning

Learn more about buildpacks right here on the Tanzu Developer Center with these great guides:

Find out more about what the pack tool can do by using the --help command and browsing the Buildpacks.io website.

If you liked this guide, you might find these others in our ‘Python Like A Pro’ series useful:

And if you’d prefer to see the buildpack in action without actually following the steps above, check out the accompanying YouTube video guide: