Dockerize Your Dash App. Get Dash Ready for Simple, Modern... | by Edward Krueger | Towards Data Science

By Edward Krueger and Erin Oefelein
This article will look at how to Dockerize a Dash app that we have already built. It's important to understand the app's structure, so please look at the repository here. If you'd like some more details and explanation, have a look at our article detailing the structure here:
towardsdatascience.com
Now that you've structured your Dash app according to our first article, you are all set up to Dockerize your app.
Docker is a platform that provides containers. Like a virtual machine (VM), a Docker container is a packaged computing environment that combines various software components in isolation from the rest of a system. The main difference between the two pertains to scale and portability. Containers are more lightweight than VMs, and container images are measured in megabytes rather than in gigabytes.
Docker containers solve a pain point seen with software for decades. Managing software across different platforms is time-consuming because each requires application dependencies, interpreters, binaries, etc. Docker provides consistency across platforms, simplifying software maintenance and deployment.
It's no wonder platforms like Google, Azure, AWS, and others use Docker to simplify app deployment for all users and all their different operating systems. Here, you'll learn how to take advantage of a technology that has revolutionized deployment!
To Dockerize your app, you'll first need to have Docker installed. Install Docker is simple for Mac users but a little more complex for Windows users. Windows machines cannot run Docker without installing Windows System for Linux (WSL2) and running Docker from within this service. We've linked a guide for those wishing to use this option here.
Once installed, you'll need to initialize Docker on your machine. Docker commands will not work unless the service is running in the background.
A Dockerfile is a set of commands that tells Docker how to build your container. It includes information about what base image your Docker container should extend, any additional installation or configuration steps needed, and instructions on running your application.
We've provided our Dockerfile below so you can easily create your Dockerfile with the correct specifications:
FROM python:3.9-slimCOPY requirements.txt ./requirements.txtRUN pip install -r requirements.txtCOPY . ./CMD gunicorn -b 0.0.0.0:80 app.app:server
One thing that may seem confusing about this Dockerfile is that it copies files in two stages. We do this because Docker builds containers in layers where, roughly speaking, each line corresponds to a layer. The first time the build runs, it caches every layer. If a layer doesn't need to be changed, we can use Docker's cached layers to make builds faster.
Let's have a closer look at the behavior. In the first COPY command, we copy only the requirements.txt file into the container.
Now for theRUN line. In a build, if requirements.txthas not been changed, then the next layer, the installation, can be skipped. If it is cached, Docker will not rerun this layer, so the Python dependencies will not need to be reinstalled.
In the second COPY line, we then copy the rest of the application's file into the container. Some of the application code will likely have changed since the last build, which means that cache for later layers is out of data. Still, since the earlier layers have already been built or retrieved from that cache, this line doesn't trigger the other dependencies to reinstall.
If we copied all of our code and dependencies at one, then changing the code would invalidate the cache, and the dependencies would be reinstalled even if the requirements didn't change.
For a deep dive into optimizing containers, check out this article by Sciforce.
medium.com
To initiate your Docker container build, run:
docker build -t tiny_home .
The -t flag "tags" our image and allows us to name the container. Tags are helpful because it allows us to refer to our container with a more human-readable name rather than machine speak. Here, we've named our container tiny_home.
This command uses our Dockerfile to build a new container image. As Docker processes our Dockerfile, it starts with the base Python container containing the Docker Python image.
There are many types of Docker base images. We are using the slim version of the Python image, which is optimal when you wish to install the minimal packages necessary to run Python. The -slim suffix denotes that the container should only contain the essentials. For many base images, there are both a slim and a full version. Less included with slim than with the full version, so the image is smaller and is ideal when dealing with space constraints. For most languages, there is also a maintained full version of the image. A full image is usually the best choice if you're unsure of which type to use with your application. Should you wish to read more on the topic, have a look at this article by Julie Perilla Garcia!
medium.com
Images usually include many layers. Layers may include system libraries, tools, dependencies and other files. In our case, the Python slim image starts by accessing the existing Python slim container from DockerHub. After the image has been downloaded locally, the most current stable release of the Debian/Linux OS distribution and Python are installed. Finally, Docker compies all application files and install the application requirements.
In our setup, we generate requirements.txt and requirements-dev.txt from our Pipfile by running pipenv-to-requirements in a pre-commit hook. Since we only reference requirements.txtDocker never sees the development. Using only the dependencies necessary to run the app in our container speeds up the build process, decreases the container's size, and potentially improves security. If you haven't done so, please refer to our first article and run these hooks. It will make this process so much easier!
Once you build your Docker container, you can view it by running:
docker image ls
And then you'll see the tiny_home image populate here:
As we've successfully built our docker container, the app will run through gunicorn on port 80. To visit the address at which our app is running, we'll need to map the port from where it's running on Docker's host at port 80 to a port on our localhost. Here, we will map our app to port 8080. To do a port mapping and run our app simultaneously, run:
docker run -p 8080:80 tiny_home
When you visit http://localhost:8080/ (or 0.0.0.0:8080 depending on your operating system), you should see your app running within your Docker container! For those unaware, localhost is an alias for 127.0.0.1, while 0.0.0.0 references all local IP addresses.
If you get an error indicating that "Bind for 0.0.0.0:8080 failed: port is already allocated", you can follow this stack overflow conversation and the provided solution to kill whatever process is using port 8080.
Your app is now Dockerized!
Stay tuned for our next article on deploying your app to GCP's Cloud Run service!
We hope you've enjoyed this article! For more content on data science, machine learning, and development, check out Edward's YouTube Channel and subscribe to my mailing list below to be the first to hear about new articles!