Learn how you can become a Python programmer in just 12 weeks.

    We respect your privacy. Unsubscribe at anytime.

    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

    Want to learn more?

    Get my book that will teach you everything a modern Python cloud developer needs to master.

    Learn how to create REST API microservices that generate metrics that allow you to monitor the health of the service.

    What does all that mean? 

    Don’t wait, get my book and master it and become one of the sought-after Python developers and get your dream job.

    Python Circle

    Do you know what the 5 key success factors every programmer must have?

    How is it possible that some people become programmer so fast?

    While others struggle for years and still fail.

    Not only do they learn python 10 times faster they solve complex problems with ease.

    What separates them from the rest?

    I identified these 5 success factors that every programmer must have to succeed:

    1. Collaboration: sharing your work with others and receiving help with any questions or challenges you may have.
    2. Networking: the ability to connect with the right people and leverage their knowledge, experience, and resources.
    3. Support: receive feedback on your work and ask questions without feeling intimidated or judged.
    4. Accountability: stay motivated and accountable to your learning goals by surrounding yourself with others who are also committed to learning Python.
    5. Feedback from the instructor: receiving feedback and support from an instructor with years of experience in the field.

    I know how important these success factors are for growth and progress in mastering Python.

    That is why I want to make them available to anyone struggling to learn or who just wants to improve faster.

    With the Python Circle community, you can take advantage of 5 key success factors every programmer must have.

    Python Circle
    Python Circle

    Be part of something bigger and join the Python Circle community.

    Leave a Comment