TLDR @ bottom
Overview
Docker is a powerful tool for creating, deploying, and managing applications in containers. It allows you to package your application and its dependencies into a single container, making it easy to run consistently across different environments. It basically functions like a VM (virtual machine) if you're familiar with that. However, there are differences which I won't get into but here's a great video that provides more information.
Docker
"Docker's container-based platform allows for highly portable workloads." - docs.docker.com
In this read, I hope to give you the basic knowledge to help set up Docker in your project. Without further ado let's get started.
Prerequisites
I assume that you already have Python installed but if not click here, you're going to need it. I also assume that you already have some basic knowledge of Python so this will not cover any Python fundamentals.
First we need to install Docker. We can visit the site here to install what we need. For Mac user's it's really easy; just click and install. However for Windows it's a bit more complicated because we need to use WSL. Here's a great video that gives an awesome tutorial on how to install Docker on WSL. The Docker site here also provides a tutorial as well. Feel free to use whichever one you like. Once you have Docker installed make sure you have it running before you type any docker commands otherwise they won't work.
Step 1: Python Project Structure
You can either create a new project or I'm assuming you're probably looking to add this to an existing project. Either way the process is the same. A very basic Python project might look something like this:
my_python_project/
└── app.py
Very basic. Let's assume however you plan on adding dependencies to your project. You might have a Pipfile
and Pipfile.lock
, you also might also be using pipenv
. To make our lives easier we're going to add a requirements.txt
somewhere in our working directory. So our file structure will look like this now:
my_python_project/
└── app.py
└── requirements.txt
In our requirements.txt
we're going to make a list of the dependencies which we need for our project. This will come in handy later. The requirements.txt
will look something like this:
# /requirements.txt
Flask==2.1.0
SQLAlchemy==1.4.30
requests>=2.25.0
numpy~=1.21.2
Step 2: Dockerfile / .dockerignore
1. Dockerfile
In order to actually use Docker we need to include a Dockerfile
in the root directory. To do this you simply create a new file and name it Dockerfile
.
my_python_project/
└── app.py
└── requirements.txt
└── Dockerfile
Let's talk about what goes into our Dockerfile
.
FROM
FROM
determines what application platform our parent image will use in docker. In this case we will use Python 3.8.
# /Dockerfile
FROM python:3.8
WORKDIR
WORKDIR
determines what the working directory is in the parent image. In this case our working directory will be /app
however for you it may be different.
# /Dockerfile
FROM python:3.8
WORKDIR /app
COPY
COPY
will literally copy all of the contents in a
specified location. In our case we want to copy /app
and requirements.txt
.
# /Dockerfile
FROM python:3.8
WORKDIR /app
COPY requirements.txt .
COPY app .
RUN
RUN
will run any commands you specify. This is where we want to include our dependencies and any other commands we want to run prior to running the application. Because we created a requirements.txt
we don't need to pip install
each of our dependencies, only the requirements.txt
.
Note: It's important to install our dependencies first before we copy our app
to speed up subsequent builds. For more information follow the link here.
# /Dockerfile
FROM python:3.8
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app .
EXPOSE
EXPOSE
will make any ports available that are needed for your project. This is helpful if you're using python for web development. In this case we will expose the port 5555
.
# /Dockerfile
FROM python:3.8
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app .
EXPOSE 5555
CMD
CMD
will be the final command that Docker will run in order to start your application. This will be written as a list and not include spaces. For example the code to run our application would be python app.py
. In the Dockerfile
that would like this ["python", "app.py"]
.
# /Dockerfile
FROM python:3.8
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY app .
EXPOSE 5555
CMD ["python", "app.py"]
Here is the Docker documentation for more information on various other commands.
2. .dockerignore
.dockerignore
will tell Docker what files to ignore when building an image. In our case we have nothing to ignore but in case you do here is a list of some items you can include (link to the repo):
# /.dockerignore
# Git
.git
.gitignore
.gitattributes
# CI
.codeclimate.yml
.travis.yml
.taskcluster.yml
# Docker
docker-compose.yml
Dockerfile
.docker
.dockerignore
# Byte-compiled / optimized / DLL files
**/__pycache__/
**/*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Virtual environment
.env
.venv/
venv/
# PyCharm
.idea
# Python mode for VIM
.ropeproject
**/.ropeproject
# Vim swap files
**/*.swp
# VS Code
.vscode/
my_python_project/
└── app.py
└── requirements.txt
└── Dockerfile
└── .dockerignore
Step 3: Building The Docker Image
Once your Dockerfile
is completed and you have Docker running in the background you can begin to build the image. To do so you want to open your terminal and navigate to your project directory where the Dockerfile is located. Now we want to run the following command:
docker build -t my-python-app .
The -t
flag lets Docker know what we want to call the image. So whatever follows -t
will be the image name. The .
indicates the directory so don't forget it or the build won't run.
Once the build runs you should see the following in your terminal:
What's Next?
View summary of image vulnerabilities and recommendations → docker scout quickview
Step 4: Run your Docker Container
Once you run your build you can start your container with the following command:
docker run -p 5555:5555 my-python-app
-p 5555:5555
maps port 5555 on your local machine to port 5555 in the Docker container. You can change these ports to whatever your needs are.
Conclusion
Docker is a powerful tool that you can use to your advantage. It simplifies the process of packaging and deploying your Python applications, making them more portable and consistent across different environments. You can further customize your Docker setup to suit the needs of your project and deploy it to various platforms. I hope that this guide was helpful to you and good luck to any projects you work on. Happy coding!
TLDR
File Stucture
my_python_project/
└── app.py
└── requirements.txt
└── Dockerfile
└── .dockerignore
Dockerfile
# /Dockerfile
# Use an official Python runtime as a parent image
FROM python:3.8
COPY app .
# Set the working directory in the container
WORKDIR /app
# copy the dependencies file to the working directory
COPY requirements.txt .
# Install any needed packages specified in requirements.txt or run any commands prior to starting your app
RUN pip install -r requirements.txt
# Copy the current directory contents into the container at /app
COPY app .
# Make port 5555 available to the world outside this container
EXPOSE 5555
# Define the command to run your application
CMD ["python", "app.py"]
.dockerignore
# /.dockerignore
# list of files/folder that can be ignored
# Git
.git
.gitignore
.gitattributes
# CI
.codeclimate.yml
.travis.yml
.taskcluster.yml
# Docker
docker-compose.yml
Dockerfile
.docker
.dockerignore
# Byte-compiled / optimized / DLL files
**/__pycache__/
**/*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Virtual environment
.env
.venv/
venv/
# PyCharm
.idea
# Python mode for VIM
.ropeproject
**/.ropeproject
# Vim swap files
**/*.swp
# VS Code
.vscode/
Build Image Command:
docker build -t my-python-app .
note: -t my-python-app
tags the image with the name "my-python-app".
Run Image Command:
docker run -p 5555:5555 my-python-app
note: -p 5555:5555
maps port 5555 on your local machine to port 5555 in the Docker container.
Sources
- https://docs.docker.com/get-started/overview/
- https://www.python.org/about/gettingstarted/
- https://www.youtube.com/watch?v=31ieHmcTUOk&list=PL4cUxeGkcC9hxjeEtdHFNYMtCpjNBm3h7
- https://docs.docker.com/get-docker/ -https://docs.docker.com/engine/reference/builder/#dockerignore-file
- https://docs.docker.com/engine/reference/builder/
- https://gist.github.com/KernelA/04b4d7691f28e264f72e76cfd724d448
- https://www.docker.com/blog/containerized-python-development-part-1/
Top comments (2)
Hey 👋
There is some potential improvements for you Dockerfile, the main one is to install your dependencies before copy your source to use layers caching efficiently.
You can take a look to this blog post series which could help you to make a efficient containerization of your Python app for prod and development 😉
docker.com/blog/containerized-pyth...
Thanks for the pointer! I made some changes according to your suggestion including the link you provided. Appreciate the help!