Wednesday, 1 May 2024

Dockerizing Python Apps on Windows

Docker is a popular tool in software development and deployment workflows released in 2013. It is a platform that uses OS-level virtualization* to deliver software in packages called containers.

*(a form of virtualization where the operating system kernel allows the existence of multiple isolated user space instances, called containers, to run concurrently on a single host operating system. It shares the host operating system's kernel among the containers, conversely to traditional virtualization where each virtual machine (VM) runs its own separate operating system. For this reason, containers are typically faster to start up and use less memory!).

Docker is used for developing, shipping, and running applications. It utilizes containerization technology to package applications and their dependencies into standardized units called containers which run the application in isolation, away from other processes on the host machine. These containers encapsulate everything needed to run the application, including the code, runtime, libraries, and system tools, ensuring that the application behaves consistently across different environments.

Let’s imagine being part of a development team working on a Node.js application with very specific version requirements. This application needs to be shared with another developer on the team who must run it on their computer. To ensure it runs correctly, they need to set up their development environment to match mine. This involves installing the same version of Node.js, all project dependencies, and configurations such as environment variables. The setup is significant and should be applied to any machine running the same application. Docker and containers were developed to solve this problem.
 
-------------------------------------------------------------------
OBJECTIVE

In this post, I will illustrate how to (1) install Docker on Windows, (2) create an image for a simple Python application, and (3) run the image. 


THE APPLICATION

When given a name, this Python application outputs a tailored greeting message.

def greeting (name):
    phrase = 'Hello, ' + name + '!'
    return phrase    

if __name__ == '__main__':
    name = input('What is your name? ')
    print(greeting(name))

This Python file, called greeting.py, is saved in a folder that I specifically created for this exercise.


INSTALL DOCKER DESKTOP ON WINDOWS

To do this, Docker Desktop should be installed. I am using Windows, so its installation is trickier than the installation on a computer which already has a Linux distribution.

The reason for this is ignored in this post but it might be the subject for another post.

This page contains the download URL, information about system requirements, and instructions on how to install Docker Desktop for Windows. Basically, to run Docker, I have to run a full Linux environment on Windows without a virtual machine. For this, WSL, which is a Windows subsystem for Linux, can be used. To install WSL, instructions can be found here
 
 
DOCKER IMAGES

Docker images are blueprints (read-only) for containers. They are essentially a snapshot of a filesystem that includes everything needed to run an application: the application code, runtime environment, libraries, dependencies, and any additional configurations or commands required. 
 
Once a Docker image is created, it cannot be modified; instead, it must be recreated. When you run a Docker image, a container is created based on that image. Images can typically be shared without significant worries regarding compatibility.

Docker images are organised in layers. Generally, the first layer is the parent image which includes a lightweight OS and a runtime environment. Parent images for Docker can be found in a public repository called Docker Hub. Here we have a list of parent images to choose from and "pull" (download).


DOCKERFILE

A Dockerfile is a text file that contains a set of instructions used to build a Docker image. These instructions specify the steps needed to create a Docker image, including setting up the environment, installing dependencies, copying files into the image, and configuring the container's behaviour.

Each instruction in a Dockerfile roughly translates to an image layer. The order of Dockerfile instructions matters.

How a Dockerfile translates into a stack of layers in a container image (docker.docs).

In the folder where my Python application is, I create a file called "Dockerfile", with the capital "D" and no extension (I use Visual Studio Code with the Docker package installed). A list of commands that can be used in a Dockerfile can be found here.

The Dockerfile for my Python application contains:

# Define the structure of the Docker image. 
# Start from the top.
  
# Use a Python runtime as the base image.
# Found on Docker Hub.
FROM python:3.12-alpine

# Set working directory in the container (app).
# Commands are now executed relative to this dir.  
WORKDIR /app

# Copy the current dir contents into /app.
# First . ->Files in the dir where the Dockerfile is.
    # Source directory (host machine). 
# Second . ->Install files in /app.
    # Destination directory (container).  
COPY . .

# Run the application when the container starts.
CMD ["python", "greeting.py"]

 

DOCKER BUILD

The docker build command compiles the instructions from the Dockerfile to create the Docker image. 

I make sure that Docker Desktop is running on my computer. 

In the same directory where the Dockerfile is, in the terminal, I type docker build -t name_of_the_app .

-t is a flag used to give a name and a tag to the image (tag is "latest" if not specified).

. is the relative path to the Dockerfile (I am already in that directory).

This creates the Docker image which can be found in the Docker Desktop under "Images". From there, I can select the image and run a new container. Then I can select that container and start it.

However, starting the container does not launch the Python application. In fact, under "Logs" for that specific container in the Docker Desktop, I found an EOFError: EOF related to the part of the code which asks for an input (the user's name). In the context of running a Docker container, this error may occur if there is no interactive terminal available for the container to accept user input. When running a Docker container, the default behaviour is non-interactive, meaning it does not allow for interactive input from the user. The container for this image must be recreated. Read down on how to solve this.


DOCKER RUN VIA CLI

docker images -> list all available images.

docker run -it --name container_1 name_of_the_app -> to run a container named "container1" from the "name_of_the_app" image.

-it is used to

  • allocate a pseudo-TTY which enables terminal-like features such as displaying output and accepting input (t).
  • enable interactivity (i). Indeed, the "-i" flag instructs Docker to attach STDIN for the container, allowing me to interact with it. I required the user to insert a name as the input for the Python application so interactivity is needed. This solves  the EOFError related to the input.

With this, the Python application runs on the terminal no problem as soon as I create the container. If I want to run it again, I need to re-start the container.

docker ps -> list all running containers.

docker ps -a -> list all containers.

docker stop container_1 -> to stop "container_1".

docker start container_1 -> to start a "container_1" which was stopped.

In my case, when I start the container again, I need to make it interactive, so I have to type:  

docker start -i container_1

No need to enable terminal-like features with the "-t" flag again, since these features have been embedded in the container when I first created it through the CLI.    

However, stopping and restarting a container to run an application which is on it is not functional because it would reset the container's state, potentially losing any changes made during the container's previous execution.

Alternatively, I can use

docker exec -it container_1 python greeting.py 

to execute a command inside a running container without restarting it. Now, when my container is running, I can execute the Python application every time I want!

In the writing of this post, I ignored .dockerignore and Volumes, which might become the subject for another post.

No comments:

Post a Comment

Integration of Cloud Technologies with the Metaverse

The potential impact and timeline for the development of the Metaverse remain uncertain, with ongoing debate over whether it represents a me...