Creating Your First Dockerfile

In this lab, you will build a Dockerfile for a Flask-based web application.

To get started, create a dedicated directory specifically for this lab and navigate into it by executing the following command in Cloud Shell:

cd ~

mkdir create-dockerfile && cd create-dockerfile

Create Your Flask Application #

Before we proceed with creating the Dockerfile, we first need to build our application. It will be a simple Python application that utilizes the Flask framework to display random facts.

Create a dedicated directory named src to store your application source code. Within this directory, copy and save the provided Python code into a file named app.py:

# import Flask module
from flask import Flask, render_template
import requests

app = Flask(__name__)
 
# Define root endpoint
@app.route('/', methods=['GET'])
def index():
    # Make a request to the open API
    response = requests.get('http://numbersapi.com/random/trivia?json')
    fact_data = response.json()
    
    # Extract the fact from the response
    fact = fact_data['text']
    
    # Render the home.html template and pass the fact to it
    return render_template('home.html', fact=fact)

# Define health endpoint
@app.route('/healthz', methods=['GET'])
def health():
    return 'All good.', 200
 
# Main driver function
if __name__ == "__main__":
    app.run()

The Python code provided above makes a reference to an HTML file that will be displayed when users visit our application. To set this up, create an additional directory within your src directory and name it templates. Inside the templates directory, copy and save the following HTML code into a file named home.html:

<!DOCTYPE html>
<html>
<head>
    <title>Workshop App</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f9f9f9;
            padding: 20px;
            text-align: center;
        }
        
        h1 {
            color: #333;
            margin-bottom: 30px;
        }
        
        .fact-container {
            background-color: #fff;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
            padding: 20px;
            max-width: 500px;
            margin: 0 auto;
        }
        
        .fact-text {
            font-size: 24px;
            margin-bottom: 20px;
        }
        
        .new-fact-btn {
            background-color: #007bff;
            border: none;
            border-radius: 3px;
            color: #fff;
            font-size: 16px;
            padding: 10px 20px;
            cursor: pointer;
        }
        
        .new-fact-btn:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <div class="fact-container">
        <h1>Random Fact Generator</h1>
        <p class="fact-text">{{ fact }}</p>
        <button class="new-fact-btn" onclick="window.location.reload();">Get New Fact</button>
    </div>
</body>
</html>

For reference, your directory structure should look like the following:

├── create-dockerfile
│   └── src
│       ├── app.py
│       └── templates
│           └── home.html

To proceed with creating the Dockerfile, navigate back to your create-dockerfile directory. This is where we will define and build our Dockerfile.

Build Your Dockerfile #

A Dockerfile is a text document that contains a set of directives or instructions defining how a container image should run. Common directives include:

  • FROM
  • RUN
  • COPY
  • WORKDIR
  • CMD

Let’s go through each line one by one to thoroughly explain the reasoning behind it. Begin by creating and opening a new file called Dockerfile.

A Dockerfile starts with the FROM directive. The FROM instruction initializes a new build stage and sets the Base Image for subsequent instructions. In our app, we will use python:3.12.0b1-alpine. Add the following line to your file Dockerfile:

FROM python:3.12.0b1-alpine

Now that we have a starting point, let’s add some requirements that our application needs to run. Docker provides the RUN instruction, allowing us to run commands during the image build process. Add a RUN instruction to install the Flask and Requests Python package:

RUN pip install Flask requests

The next step is to define a non-root user to run our application. Running the container as a non-root user adds an extra layer of security by limiting processes and execution privileges. We can create a user using the adduser command with the RUN directive:

RUN adduser -D -h /app python-user

In a Dockerfile, you can set a working directory with the WORKDIR directive, specifying the directory where subsequent commands will run:

WORKDIR /app

Next, we need to add our application code to the image. The COPY directive allows us to copy local files or directories and add them to the image’s filesystem. Use the following COPY instruction to copy the code from your local src directory to the container image at the path /app:

COPY src/ .

With our contents in place, we can now switch to the non-root user we created earlier using the USER directive:

USER python-user

Since our app listens on a specific port, we can use the EXPOSE directive to inform Docker that the container listens on the specified network port:

EXPOSE 5000

We’ve set the stage to run our application, and now the final piece is to execute it. The CMD directive provides defaults for the executing container. If there are multiple CMD directives in the file, only the last one takes effect. The preferred form of CMD is:

["<executable>", "<param1>", "<param2>"]

For our application, add the following line to start the application and listen on port 5000:

CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]

That completes your Dockerfile. Save and close the file.

Locally Build Your Image #

With your Dockerfile complete, it’s time to build your image. This can be achieved using Docker’s build command, which requires specifying a tag and the Dockerfile. A Docker tag provides a unique identity for a Docker image. Execute the following command to build your Docker image, using the -t flag to specify the tag for the current build:

docker build --tag random-facts-app:latest --tag random-facts-app:1.0 .

You will see an output similar to the following:

[+] Building 9.5s (11/11) FINISHED
...
=> => naming to docker.io/library/random-facts-app:latest
 => => naming to docker.io/library/random-facts-app:1.0 

Note: If you encounter a permission issue while running the above command, you can resolve it by executing the following usermod command, which adds your user to the docker Linux group. Remember to restart your Cloud Shell afterward.

usermod -a -G docker <your-linux-username>

Once you have successfully built the image, you can use the docker images command to view your image. If you require assistance, use the -h flag.

You will see an output similar to the following:

REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
random-facts-app   1.0       5697444860e2   7 seconds ago   71.1MB
random-facts-app   latest    5697444860e2   7 seconds ago   71.1MB

Once you have viewed your image you can move onto the next lab.