Dockerize your Python app

Sep 14, 2023

Intro

What is a container?

A container is a sandboxed process running on a host machine that is isolated from all other processes running on that host machine. That isolation leverages kernel namespaces and cgroups, features that have been in Linux for a long time. Docker makes these capabilities approachable and easy to use.

  • Is a runnable instance of an image. You can create, start, stop, move, or delete a container using the Docker API or CLI.
  • Can be run on local machines, virtual machines, or deployed to the cloud.
  • Is portable (and can be run on any OS).
  • Is isolated from other containers and runs its own software, binaries, configurations, etc.

If you’re familiar with chroot, then think of a container as an extended version of chroot. The filesystem comes from the image. However, a container adds additional isolation not available when using chroot.

What is an image?

A running container uses an isolated filesystem. This isolated filesystem is provided by an image, and the image must contain everything needed to run an application - all dependencies, configurations, scripts, binaries, etc. The image also contains other configurations for the container, such as environment variables, a default command to run, and other metadata.

Installation

  • kvm

    Docker Desktop runs a VM that requires KVM support

    modprobe kvm
    lsmod | grep kvm
    

    Set up KVM device user permissions link

    # To check ownership of /dev/kvm
    ls -al /dev/kvm
    # Add your user to the kvm group in order to access the kvm device
    sudo usermod -aG kvm $USER
    

    Sign out and sign back in so that your group membership is re-evaluated.

  • deb
    wget
    
    sudo apt remove docker-desktop
    
    rm -r $HOME/.docker/desktop
    sudo rm /usr/local/bin/com.docker.cli
    sudo apt purge docker-desktop
    
    sudo apt-get update
    sudo apt-get install ./docker-desktop-<version>-<arch>.deb
    

    To start Docker Desktop

    systemctl --user start docker-desktop
    

    Check the versions of the binaries

    docker-credential-gcloud compose version
    # Docker Compose version v2.17.3
    
    docker --version
    # Docker version 23.0.5, build bc4487a
    
    docker version
    # Client: Docker Engine - Community
    #  Cloud integration: v1.0.31
    #  Version:           23.0.5
    #  API version:       1.42
    # <...>
    
    

    To enable Docker Desktop to start on login

    systemctl --user enable docker-desktop
    
  • ob-docker
    (use-package docker
      :ensure t
      :bind ("C-c d" . docker))
    

Containerize an application

Get the app

Let’s say you have the following python repository

β”œβ”€β”€ my_app/
β”‚ β”œβ”€β”€ main.py
β”‚ β”œβ”€β”€ README.md
β”‚ β”œβ”€β”€ test/
β”‚ β”œβ”€β”€ src/
β”‚ └── requirements.txt

Build an image

  • Create a Dockerfile

    To build the image, you’ll need to use a Dockerfile. A Dockerfile is simply a text-based file with no file extension that contains a script of instructions. Docker uses this script to build a container image.

    cd /path/to/my_app
    
    touch Dockerfile
    
    FROM python:3.8-alpine
    # Or any preferred Python version.
    ADD main.py .
    RUN pip install requests beautifulsoup4 python-dotenv
    CMD [β€œpython”, β€œ./main.py”]
    # Or enter the name of your unique directory and parameter set.
    

    This Dockerfileis fairly basic, which is perfect for this application. Your Dockerfile will change depending on your code and desired app functionality. There are also other arguments available, like WORKDIR, ENV, COPY, EXPOSE, ENTRYPOINT and HEALTHCHECK. Each allows you to build more operative complexity into your Python applications, or control which resources are pulled in.

  • build

    The docker build command uses the Dockerfile to build a new image.

    docker build -t my_app .
    

    The docker build command uses the Dockerfile to build a new image. You might have noticed that Docker downloaded a lot of “layers”. This is because you instructed the builder that you wanted to start from the python:3.8-alpine image. But, since you didn’t have that on your machine, Docker needed to download the image.

    After Docker downloaded the image, the instructions from the Dockerfile copied in your application and used pip to install your application’s dependencies. The CMD directive specifies the default command to run when starting a container from this image.

    Finally, the -t flag tags your image. Think of this as a human-readable name for the final image. Since you named the image getting-started, you can refer to that image when you run a container.

    The . at the end of the docker build command tells Docker that it should look for the Dockerfile in the current directory.

run an image

Now that you have an image, you can run the application in a container using the docker run command.

docker run -dp 127.0.0.1:3000:3000 my_app

The -d flag (short for --detach) runs the container in the background. The -p flag (short for --publish) creates a port mapping between the host and the container. The -p flag takes a string value in the format of HOST:CONTAINER, where HOST is the address on the host, and CONTAINER is the port on the container. The command publishes the container’s port 3000 to 127.0.0.1:3000 (localhost:3000) on the host. Without the port mapping, you wouldn’t be able to access the application from the host.

After a few seconds, open your web browser to http://localhost:3000. You should see your app.

If you take a quick look at your containers, you should see at least one container running that’s using the my_app image and on port 3000. To see your containers, you can use the CLI or Docker Desktop’s graphical interface.

docker ps
CONTAINER ID   IMAGE    COMMAND                  CREATED             STATUS        PORTS                     NAMES
df784548666d   my_app   "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes  127.0.0.1:3000->3000/tcp  kiriclope

References