Docker with Python for Dummies
Tags: docker, python, beginners, tutorial
Published: 2023-10-14 00:36:41

**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](https://www.youtube.com/watch?v=31ieHmcTUOk&list=PL4cUxeGkcC9hxjeEtdHFNYMtCpjNBm3h7) a great video that provides more information. 
 
> **Docker**
> "Docker's container-based platform allows for highly portable workloads." - [docs.docker.com](https://docs.docker.com/get-started/overview/)

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](https://www.python.org/about/gettingstarted/), 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](https://docs.docker.com/get-docker/) 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](https://www.youtube.com/watch?v=8Ev1aXl7TGY&list=PL4cUxeGkcC9hxjeEtdHFNYMtCpjNBm3h7&index=2)'s a great video that gives an awesome tutorial on how to install Docker on WSL. The Docker site [here](https://docs.docker.com/desktop/install/windows-install/) 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: 

```python
# /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.

```python
# /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. 

```python
# /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`. 

```python
# /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](https://www.docker.com/blog/containerized-python-development-part-1/).

```python
# /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`. 

```python
# /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"]`.

```python
# /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](https://docs.docker.com/engine/reference/builder/) 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](https://gist.github.com/KernelA/04b4d7691f28e264f72e76cfd724d448)): 

```python
# /.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: 
```bash
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: 

```bash
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: 

```bash
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**

```python 
# /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**

```python
# /.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:**

```bash
docker build -t my-python-app .
```

**note**: `-t my-python-app` tags the image with the name "my-python-app".

**Run Image Command:**

```bash
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/