Docker to the rescue!

Published on 2024-02-24

Flask Docker

One of the issues we often encounter during software development is that our solution works in the development environment, but when deploy to the production environment, it miraculously stops working! Docker provides a consistent environment across development, testing, and production environments

Docker is a name given to the container/package/image which contains the source code and its dependencies i.e. code, runtime, system tools, system libraries, binaries, settings and even operation system), thereby, providing the consistent environment across development, testing and production environments.

Docker runs on the Docker Engine which enables containerized applications to run consistently on any infrastructures solving “dependency hell”. When running a container, it uses an isolated file system (Dockerfile). This custom filesystem is provided by a container image. Since the image contains the container’s filesystem, it must include everything needed to run the application – all dependencies, configurations, scripts, binaries, etc. The image also contains other configuration for the container such as environment variables, a default command to run and other metadata.

We will look at how we can package our application to an image with Docker and then host it onto Docker Hub.

Ok, now, assuming we are packaging our Flask application, the first step is

  1. Create a Dockerfile listing the commands for installation of code dependencies at the root folder of the Flask Application

    # Dockerfile is a filesystem with a list of commands written in Docker syntax to install dependencies for our app to run 
    # this is a combination of Docker Langauge and linux command
    # We can either start creating an OS image from scratch (a base image) or use a parent image (From xxx). 
    FROM python:3.10-slim-buster
    # set the working directory is /app (create the directory if not exist in the image)
    WORKDIR /app 
    # copy requirements.txt file into our working directory /app in the image 
    COPY requirements.txt requirements.txt
    # install the python modules in the requirements.txt
    RUN pip3 install -r requirements.txt
    # add the source code into the image 
    COPY . .
    # which port are we using from the container?
    EXPOSE 8000
    # what happens when our image is executed inside a container
    # CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=8000"]
    # run flask with gunicorn in front
    CMD ["gunicorn", "--bind", "0.0.0.0:8000", "wsgi:app"]

  2. In the command line, create an image with Docker CLI from dockerfile (by cd-ing to the directory where dockerfile exists). That's why we have '.' in our command line.

    $ docker build -t <image-name> .
    Like me, if you are running your docker in Mac with Apple Silicon (i.e. Mac with M1/2 Chips), when we build an image with docker command above, the image built is targeted for ARM64. This poses a problem when we are deploying the image to the Operating System with Intel processor (AMD64). Since our image is built for ARM64 architecture, any operation system running on Intel processor will not be able to execute our image. We can check the architecture of our computer by opening the Terminal application, and running the following command:
    arch
    There are two solutions to this:
    (A) Explicitly define the targeted architecture like this in command line. OR
    # Build for AMD64
    docker build --platform=linux/amd64 -t <image-name>-amd64 .
    (B) Update your Dockerfile
    FROM --platform=linux/amd64 BASE_IMAGE:VERSION

  3. Run the image in a detached mode. This will create a container with the image.
    $ docker run -dp 8000:8000 <image-name>
    Above command runs a Docker container based on the specified image in detached mode (-d) and publishes ports (-p 8000:8000). It maps port 8000 in the container to port 8000 on the host. If we did not specify a port for flask application in the docker file, since the default is 5000, we will have to expose port 5000 inside the container to port 8000 outside the container. In that case, our docker CLI would be 8000:5000 to the –publish flag.

That's it! Now, flask application is run on the container port: 8000 and container will run on the host machine’s on port 8000. When we are running the docker image locally, we are hosting the docker container locally.

Note: CLI to remove the Docker container and image $ docker ps $ docker stop $ docker rm $ docker rmi $ docker images DockerRun CLI

Docker Hub So far, we have only been working in our local machine. Ultimately, we want to host our containerized application into a cloud service. The very first step is to push the image we created to Docker Hub. We can publish it privately or in public mode. Later on, our choice of Cloud Service will download the image from Docker Hub and host the web application on their cloud.

First, head to Docker Hub and register an account if you have not done it yet. We will push our image to Docker Hub from CLI.

  1. Authenticate yourself to Docker Hub
    $ docker login  
  2. Next, we need to tag our image with our Docker username/id. Why? To give a unique name.

    $ docker tag <image-name> <dockerhub-username>/<image-name>

  3. Then, we push the image to Docker Hub

    $ docker push <dockerhub-username>/<image-name>

In next post, we will look at how we can deployed our containerized image to cloud service.