Python

Docker Survival Guide for Python Developers

How to run a Python code in Docker

Do you need to create your first Docker image to run your Python module in a Docker container? In this tutorial, you will learn how to do that.

Most get a bit scared the first time they need to create their first docker image – it seems so complex. But don’t worry, in this guide, you will learn how to build your python docker image which you can use to run your Python module in a Docker container.

Docker for Python

Step 1: What is Docker?

Docker enables developers to easily deploy their applications in containers to run in a specific environment decided by you.

That might be a bit difficult to understand at first.

Take our Python API – we already know, that it does not run with Python 3.8. But we want to be able to deploy it in different places without the trouble of having correct Python, libraries, environment variables, etc. This is what Docker can do for us.

You can package Python 3.10 (in our case) with the correct libraries in a Linux OS.

What about this Linux, you might wonder. I don’t run Linux.

Neither do I – but I can run it in my Docker Daemon.

This is the beauty of it, you can run it on any machine or server with a Docker Daemon.

This actually solves a big problem. Imagine you also had another API running, but it only worked with Python 3.8. Your new one does not work with 3.8.

But each of these APIs is running in each their package with each their environment.

Therefore, you can have it running isolated in their own containers (as they are called) – also, they can call each other endpoints (APIs) and communicate with each other.

You suddenly understand the power of Docker.

Step 2: Docker terminology

There can seem to be a lot of terminology with Docker.

The good news is, you only need to understand the high-level concepts of how it works – the precise concepts can ease communication, but most people understand you if you call a Dockerfile the Docker image.

The terminology used is as follows.

  • Dockerfile. The commands to create a Docker image.
  • Docker images. The blueprints of the containers.
  • Docker container. Created from Docker images and run the actual application – this is the running code.
  • Docker Daemon. The background service runs on the host that manages the building, running, and distributing Docker container.
  • Docker Client (CLI). The command line tool allows users to interact with the daemon.
  • Docker Hub. A registry of Docker images.
  • Docker Desktop. An easy-to-install UI including Docker Daemon and Docker client.

I think we will learn by doing, but in short, you need the following.

  • Install Docker Desktop.
  • Create Dockerfile.
  • Compose a Docker image.
  • Deploy the image to run as a container in Docker Daemon.
  • They know how to shut them down and restart them.

Don’t worry too much about understanding all this now. You will learn it in this tutorial.

Step 3: Install Docker Desktop

You can install Docker Desktop and it will automatically install all you need to manage all Docker tools need.

Go to Docker official download and get Docker Desktop for your OS (here).

Follow the installation instructions and you are ready to go.

  • Linux Notice. Please notice that if you use Linux there is no Docker Desktop you will need to use command lines when we use the Desktop. For the most part, we only use it to see the containers and see what is running. This can be done from a command line as well.

You can launch Docker Desktop, which will start the Docker Daemon in the background.

Step 4: Explore a Python project with Docker

The best way to learn is to try it on a real project. To do that, I suggest you clone the following project (here)

If you want to understand the code in the project, I suggest you read this tutorial. Then you can continue to run the Python module in Docker.

If you want to understand the logging read this one.

And if you want to understand the tox testing framework you should read this one.

Dockerfile explained

Here we will explore the Dockerfile in the project.

FROM python:3.10-bullseye

COPY requirements.txt .
RUN pip install -r requirements.txt

RUN mkdir /src
COPY . /src

WORKDIR /src

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

On the first line, we take the base image, which is the most recent Python 3.10 version, called python:3.10-bullseye.

  • If you at a later stage want to use other Python versions in a Docker image, you can find them here: https://hub.docker.com/_/python
  • Notice, that some are early alpha releases and it is not recommended to use them unless you know what you are doing.

 The base image is like the foundation of what you are running it on. This is a prebuilt image and contains all the standard libraries for the Python 3.10 release.

  • It should be clear that whatever you will run from a container created by this image, cannot access anything except what is provided by this image and what we also add to it.

This makes the next line understandable.

COPY requirements.txt .

This will copy the requirements.txt file to the Docker image.

Notice that we need the requirement.txt to be in the same folder as the Dockerfile where we will build the image.

RUN pip install -r requirements.txt

The next line will run a command: pip install -r requirements.txt.

Remember what that did?

Yes, you are right, it installs all the requirements.

Notice, that the RUN will execute this command inside the Docker image, and hence, the installation is inside the Docker image.

RUN mkdir /src

This will create a folder /src inside the Docker image.

COPY . /src

This will copy all the files to the Docker image.

Now you might wonder about one thing.

  • Why first copy requirements.txt and install it and then copy everything thing afterward?

This has something to do with how Docker images are created. And, yes, you could do it, but I will tell you why the approach in the Dockerfile might be good.

Dockerfile layers explained

Docker images are built in layers. And when you re-build an image, it will only build from the layer there have been changes.

Each line in the Dockerfile represents a layer.

Now consider this.

  • Imagine you already have a built image of the Dockerfiler
  • Then you make a change to app/main.py file.
  • What happens?

This change does not affect the requirements.txt file. Hence the installation of all the required libraries will not be executed again. It will start from the COPY . /src command.

You see, it will save time the next time you build your image.

WORKDIR /src

This will set the working folder (or directory). This means, when you execute a command, it will run from this folder.

EXPOSE 8000

This will inform Docker that the container will listen to that specified network port.

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Finally, the CMD is the command.

This can seem a bit confusing. But it takes the list of arguments and runs the command uvicorn with all the arguments.

uvicorn app.main:app –host 0.0.0.0 –port 8000

You might wonder.

  • Why don’t we run python server.py?
    • We could, but we will use the uvicorn instead here.

Now we have some basic understanding of the Dockerfile, we are ready to build the Docker image.

Step 5: Build and run the Docker image

Before you build your first Docker image, you need to ensure that the Docker Desktop is running.

This is needed to have the Docker daemon running.

Now run the command: docker build -t fruit-api .

Notice the space and dot at the end of the command.

Then it will start making a lot of stuff. You need to wait until it finishes.

[+] Building 16.3s (12/12) FINISHED                                                                                                                                                                                     
 => [internal] load build definition from Dockerfile                                                                                                                                                               0.0s
 => => transferring dockerfile: 257B                                                                                                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                                                                                    0.0s
 => [internal] load metadata for docker.io/library/python:3.10-bullseye                                                                                                                                            2.5s
 => [auth] library/python:pull token for registry-1.docker.io                                                                                                                                                      0.0s
 => [1/6] FROM docker.io/library/python:3.10-bullseye@sha256:8846e26238945dc748151dc3e0f4e9a00bfd09ad2884edcc6e739f085ad9db3b                                                                                      0.8s
 => => resolve docker.io/library/python:3.10-bullseye@sha256:8846e26238945dc748151dc3e0f4e9a00bfd09ad2884edcc6e739f085ad9db3b                                                                                      0.0s
 => => sha256:cee26ee24819941bed15229baeb8e437866a7864432a6b15d1bfbd5da70cee09 2.22kB / 2.22kB                                                                                                                     0.0s
 => => sha256:ace6d68f6bea726cb6f2aa5a02cc799df1902deef9a94b362fc6a23ff8a69efc 3.04MB / 3.04MB                                                                                                                     0.4s
 => => sha256:8846e26238945dc748151dc3e0f4e9a00bfd09ad2884edcc6e739f085ad9db3b 1.86kB / 1.86kB                                                                                                                     0.0s
 => => sha256:b62e4294564cd2bdf0693cbd786338d15734a6fbee280faa976c23f3ae60f636 8.53kB / 8.53kB                                                                                                                     0.0s
 => => extracting sha256:ace6d68f6bea726cb6f2aa5a02cc799df1902deef9a94b362fc6a23ff8a69efc                                                                                                                          0.3s
 => [internal] load build context                                                                                                                                                                                  6.4s
 => => transferring context: 46.05MB                                                                                                                                                                               5.3s
 => [2/6] COPY requirements.txt .                                                                                                                                                                                  0.2s
 => [3/6] RUN pip install -r requirements.txt                                                                                                                                                                      6.3s
 => [4/6] RUN mkdir /src                                                                                                                                                                                           0.2s 
 => [5/6] COPY . /src                                                                                                                                                                                              0.3s 
 => [6/6] WORKDIR /src                                                                                                                                                                                             0.0s 
 => exporting to image                                                                                                                                                                                             0.4s 
 => => exporting layers                                                                                                                                                                                            0.3s 
 => => writing image sha256:8842602312874f7a1c289a2f6a47e5b341033ee4a447ed73381c06df1d44421e                                                                                                                       0.0s 
 => => naming to docker.io/library/fruit-api                                                                                                                                                                       0.0s

It tells you to use docker scan. This is beyond the scope of what we will cover and requires you to create a login.

The image is created from the official Python image and the files in your project.

Now, where is the image?

Look in Docker Dashboard (Docker Desktop) and click images and search for the fruit-api.

Now there is your image.

Can you access it?

It is not running. Remember, you need to launch it in a Docker container.

  • Run the command: docker run -dp 8000:8000 fruit-api
    • The -d will detach it from the command line, this is convenient as it will occupy the terminal until you finish the container.
    • The p part (of -dp) will publish the port 8000:8000 on the host from the container.

The name (beautiful_diffie) will most likely be different in your case. This is simply a random name.

If you click on it you will see the output.


To call it, you can run make_order.py file.

Then you should see it in the log-in Docker Desktop.

Step 6: Stopping your Docker container

When you want to shut down your Docker container – that is, you don’t want to have the service running on port 8000, what do you do?

If you hover the mouse over you will see the following options.

It has a stop button (and some more).

If you click the stop button, it will stop.

  • Don’t believe me? Try to stop it and run make_order.py and it will fail.

Then you can start it again with the RUN button (the STOP will change to a RUN button).

  • Try it and run make_order.py and see if it works again.

Step 7: How to make changes to your API running in Docker

Right now, you have built your Docker image and you can run it in a Docker container.

If you make changes to the code in PyCharm, those changes will not be reflected if you run the container we have available.

To make changes you need to build a new image and run that image as a container.

Let’s try that.

Make the following change in app/routers/order.py

import logging
from http import HTTPStatus
from typing import Dict

from fastapi import APIRouter

logger = logging.getLogger(__file__)
router = APIRouter(tags=['income'])


@router.post('/order', status_code=HTTPStatus.OK)
async def order_call(order: str) -> Dict[str, str]:
    logger.info(f'Incoming order received: {order}')
    return {'Order Received': order}

Let’s make this simple change to line 14.  Change it to return {‘Order received’: order}

If you start the Docker container, you will see it is the old version running.

To be clear, you can modify make order a bit to print the response.

As you see – it returns the old response we had.

What to do?

  1. Stop the container.
    1. You can do that as we saw with the STOP button in Docker Dashboard.
    2. Or run the command: docker stop eloquent_hawking
      1. Of course, you need to change the name after a stop to fit with the name it gave your instance of the container.
  2. Delete the container.
    1. This can also be done from Docker Dashboard (use the DELETE button – hiding under the symbol).
  3. Or run the command: docker container rm eloquent_hawking
    1. Again, you need to change it to your container’s name.
  4. Build the image again.
    1. Run docker build -t fruit-api .
    2. This is done in the terminal.
    3. Notice, that it will be faster, as it does not need to build all layers.
  5. Run the image
    1. Run the command: docker run -dp 8000:8000 fruit-api
    2. This could also be done from Docker Dashboard.
      1. Go to images
      2. Find the image
      3. Run it and remember to publish the port.

Now make an order (run make_order.py)

And it prints our changes

Also, be sure to learn How to Setup MySQL Server in Docker for Your Python Project or check the full roadmap for Python web app developer.

Are You a Python Developer Ready to Land Your Dream Job?

Unlock the Key to Success with Cloud, Docker, Metrics, and Monitoring!

Master cloud computing, Docker, logging, Git & GitHub, metrics, and monitoring to accelerate your path to success as a Python developer.

Get job-ready skills without wasting time figuring it out on your own.

Deploy your Python applications effortlessly to the cloud, building scalable and resilient solutions.

Streamline your development workflow with Docker, eliminating compatibility issues and enabling seamless collaboration.

Optimize performance with metrics and monitoring, delivering exceptional user experiences and standing out to employers.

Don't settle for the ordinary. Stand out, impress employers, and supercharge your Python developer career. Buy this eBook now and unlock the power of the cloud, Docker, metrics, and monitoring.

Rune

Recent Posts

Build and Deploy an AI App

Build and Deploy an AI App with Python Flask, OpenAI API, and Google Cloud: In…

5 days ago

Building Python REST APIs with gcloud Serverless

Python REST APIs with gcloud Serverless In the fast-paced world of application development, building robust…

5 days ago

Accelerate Your Web App Development Journey with Python and Docker

App Development with Python using Docker Are you an aspiring app developer looking to level…

6 days ago

Data Science Course Made Easy: Unlocking the Path to Success

Why Value-driven Data Science is the Key to Your Success In the world of data…

2 weeks ago

15 Machine Learning Projects: From Beginner to Pro

Harnessing the Power of Project-Based Learning and Python for Machine Learning Mastery In today's data-driven…

2 weeks ago

Unlock the Power of Python: 17 Project-Based Lessons from Zero to Machine Learning

Is Python the right choice for Machine Learning? Should you learn Python for Machine Learning?…

2 weeks ago