Hello! Welcome to the next lesson in our Docker module.
In our previous sessions, you learned how to pull pre-built images like nginx and redis from Docker Hub and manage their lifecycle. This is a great start, but the true power of Docker for a DevOps engineer comes from packaging and distributing your own applications.
Today, you'll learn how to do just that. We will move from being a consumer of Docker images to a creator. The key to this is a simple but powerful file: the Dockerfile.
Today's Learning Outcome: By the end of this lesson, you will be able to create a basic Dockerfile to build a custom container image for a simple application.
We will cover:
- What a Dockerfile is and how it works.
- The essential instructions used to define an image.
- A hands-on exercise to write a Dockerfile, build a custom image for a Python web application, and run it as a container.
Let's begin.
1. What is a Dockerfile?
A Dockerfile is a plain text file that contains a series of instructions. These instructions tell Docker exactly how to assemble a custom image. Think of it as a recipe for your application's environment.

Each instruction in a Dockerfile creates a new "layer" in the image. Docker cleverly caches these layers. When you rebuild an image after making a change, Docker only rebuilds the layers that have changed, making the process fast and efficient.
To get a better sense of what a Dockerfile is and why it's so fundamental to DevOps, let's start with a quick reading.
How to Build a Docker Image With Dockerfile From Scratch
This article from KodeKloud provides a great introduction to the concept of a Dockerfile.
Please read the section titled 'What Is a Dockerfile?'. It explains the basic format and purpose of this essential file.
As the article mentions, a Dockerfile consists of instructions in the format DIRECTIVE argument. Let's look at the most common directives you'll use.
2. Core Dockerfile Instructions
While there are many possible instructions, a handful form the backbone of almost every Dockerfile you'll write.
To get a quick overview of the most common instructions, please review the list in the following resource.
Build & Optimize Docker Images Step-By-Step
This article provides a concise reference list of the most common Dockerfile instructions. It's a good way to quickly familiarize yourself with the available tools.
Read section '2. Dockerfile Instructions Explained (The Complete Guide)'. Don't worry about memorizing everything; just get a sense of what each instruction does. We'll focus on the most important ones next.
Now, let's focus on the five instructions that are the workhorses of a basic Dockerfile:
FROM: Every Dockerfile must start with this. It specifies the base image you are building upon. This could be an operating system likeubuntu:22.04, a language runtime likepython:3.9-slim, or another application image.WORKDIR: Sets the working directory for any subsequentRUN,CMD,COPY, andADDinstructions. If the directory doesn't exist, it will be created. This helps keep your container's filesystem organized.COPY: Copies files or directories from your local machine (the "build context") into the filesystem of the image. Its basic syntax isCOPY <src> <dest>.RUN: Executes a command in a new layer during the image build process. This is used for tasks like installing software packages (e.g.,RUN apt-get update && apt-get install -y vim) or application dependencies (e.g.,RUN pip install -r requirements.txt).CMD: Provides the default command to execute when a container is started from the image. A Dockerfile can only have oneCMD. If you provide a command when you run the container (e.g.,docker run my-image ls -l), it will override theCMDinstruction.
The distinction between RUN and CMD is crucial. RUN executes at build time; CMD executes at run time.
Test your understanding!
You are writing a Dockerfile for a Python application. You need to install the requests library and then start the web server defined in app.py. Which instruction would you use for each task?
Show answer
You would use RUN to install the library, as this is a build-time setup step:RUN pip install requests
You would use CMD to start the web server, as this is the command that should be executed when the container starts:CMD ["python", "app.py"]
3. Hands-On: Building Your First Custom Image
Now, let's put theory into practice by building an image for a simple Python Flask web application. We'll use the excellent step-by-step guide from KodeKloud that you looked at earlier.
Step 1: Set Up Your Project
First, create a new directory on your local machine for this project. Let's call it flask-app.
mkdir flask-app
cd flask-app
Inside this directory, create two files: app.py and a blank Dockerfile.
The KodeKloud article provides the simple "Hello, World!" Flask application code.
How to Build a Docker Image With Dockerfile From Scratch
Let's grab the application code and the basic Dockerfile structure from the KodeKloud article.
Follow the sections from 'Building a Dockerfile for a Sample Python/Flask Application' up to '#6 Define the default command'. First, create the app.py file with the provided Python code. Then, create your Dockerfile and add the instructions from steps #1 through #6 one by one, reading the explanation for each.
After following those steps, your Dockerfile should look like this:
FROM python:3.11-slim
WORKDIR /app
RUN pip install flask==2.3
COPY . /app
ENV FLASK_APP=app.py
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]
Step 2: Create a .dockerignore file
When you run docker build, the entire current directory is sent to the Docker daemon as the "build context". The COPY . /app instruction copies this context into the image. However, we don't need to copy the Dockerfile itself into our image.
To exclude files and directories from the build context, we use a .dockerignore file, which works just like the .gitignore file you use with Git.
Let's create it now.
How to Build a Docker Image With Dockerfile From Scratch
This section of the KodeKloud article explains how and why to create a .dockerignore file.
Read section '#7 Create a .dockerignore file' and create the file in your flask-app directory with the content specified.
Step 3: Build and Run Your Custom Image
Now for the exciting part! With your app.py, Dockerfile, and .dockerignore files in place, you are ready to build the image and run your application.
The following resource will walk you through the final commands.
How to Build a Docker Image With Dockerfile From Scratch
Let's use the final part of the KodeKloud guide to build our image, run it as a container, and test it.
Read and execute the commands in the section 'Building and Running the Docker Image'. Pay close attention to the syntax of the docker build and docker container run commands.
To summarize the commands you just ran:
- Build the image:
# The -t flag tags the image with a name (sample-flask-app:v1) # The . specifies that the current directory is the build context docker build . -t sample-flask-app:v1 - Verify the image was created:
docker image ls - Run the container from your new image:
# -d runs it in detached mode # -p maps port 5000 on your host to port 5000 in the container docker container run -d -p 5000:5000 sample-flask-app:v1 - Test the application: Open your web browser and navigate to
http://localhost:5000. You should see "Hello, World!".
Congratulations! You have successfully packaged an application into a custom Docker image and run it.
Conclusion
Today you've taken a massive step forward in your Docker journey. You are no longer just using images that others have built; you are now able to create your own. This skill is at the heart of container-based DevOps workflows, enabling you to create consistent, portable, and reproducible environments for any application.
Key Takeaways:
- A Dockerfile is a text-based script of instructions for creating a custom Docker image.
- Each instruction in a Dockerfile creates a cached layer, making builds efficient.
- Key instructions include
FROM(base image),WORKDIR(set directory),COPY(add files),RUN(execute build-time commands), andCMD(specify run-time command). - The
docker build -t <name:tag> .command builds an image from the Dockerfile in the current directory. - A
.dockerignorefile is a best practice to keep your build context small and your final image clean.
Next Up: Optimizing Your Images
You now know how to build a basic image. But is it as good as it can be? Production-grade images need to be small, secure, and efficient. In our next lesson, you will learn how to optimize Docker images using multi-stage builds to reduce image size and improve security.