Docker Level 2, Task 5: Building Custom Images with a Dockerfile¶
Today's task was a major step up. I moved from using and managing existing Docker images to creating my own from scratch. This is the absolute core of using Docker for application development. I learned how to write a Dockerfile, which is a recipe for building a custom, reproducible image.
This process taught me how to codify an application's environment—from the base operating system to package installation and configuration—into a single, version-controlled text file. This is the foundation of "Infrastructure as Code" in the container world.
Table of Contents¶
- The Task
- My Step-by-Step Solution
- Why Did I Do This? (The "What & Why")
- Deep Dive: The
DockerfileInstructions and Image Layers - Common Pitfalls
- Exploring the Commands Used
The Task¶
My objective was to create a custom Docker image on App Server 1 by writing a Dockerfile. The specific requirements for the image were:
1. The Dockerfile must be located at /opt/docker/Dockerfile.
2. The base image must be ubuntu:24.04.
3. The final image must have apache2 installed.
4. The Apache server must be configured to listen on port 8083.
My Step-by-Step Solution¶
The process involved creating the Dockerfile and then, for verification, building the image and running a container from it.
Step 1: Prepare the Environment¶
First, I connected to App Server 1 (ssh tony@stapp01). The task required the Dockerfile to be in a specific location, so I created the directory first. Since /opt is owned by root, I needed sudo.
sudo mkdir -p /opt/docker
Step 2: Create the Dockerfile¶
I created and edited the Dockerfile using vi.
sudo vi /opt/docker/Dockerfile
# Use ubuntu:24.04 as the base image
FROM ubuntu:24.04
# Update package lists and install apache2 non-interactively
RUN apt-get update && apt-get install -y apache2
# Use sed to find the default Listen port (80) and replace it with 8083
RUN sed -i 's/Listen 80/Listen 8083/g' /etc/apache2/ports.conf
# Expose the new port for documentation and networking purposes
EXPOSE 8083
# The command to start Apache in the foreground when the container runs
CMD ["apache2ctl", "-D", "FOREGROUND"]
Step 3: Verification (The Professional Way)¶
The task was technically complete, but I wanted to prove my Dockerfile actually worked.
1. Build the Image: I navigated to the directory and used the docker build command. The -t flag gives the image a name (my-apache-app:latest), and the . tells Docker to use the Dockerfile in the current directory.
cd /opt/docker
sudo docker build -t my-apache-app:latest .
sudo docker run -d -p 8083:8083 --name test-apache my-apache-app:latest
curl to test if my new Apache server was responding on the correct port.
curl http://localhost:8083
I received the HTML for the "Apache2 Ubuntu Default Page," which was the definitive proof that my Dockerfile was perfect.
Why Did I Do This? (The "What & Why")¶
-
Dockerfile: This is a simple text file that contains a script of instructions for building a Docker image. It's the standard for creating reproducible and automated container builds. By putting my environment setup in aDockerfile, I ensure that anyone on my team can create the exact same image. -
Reproducibility: This is the core benefit. Instead of manually modifying a container (like I did in a previous task), a Dockerfile provides a version-controlled, documented, and automated way to create an image. This is essential for CI/CD pipelines.
Deep Dive: The Dockerfile Instructions and Image Layers¶
This task helped me understand the most common Dockerfile instructions and the concept of layering.
-
FROM ubuntu:24.04: This is the foundation. EveryDockerfilemust start with aFROMinstruction, specifying the base image to build upon. -
RUN apt-get update && ...: TheRUNinstruction executes a command. Each RUN command creates a new, read-only layer on top of the previous one. This is a key feature. If I were to change myCMDinstruction later and rebuild, Docker would use the cached layers from theFROMandRUNsteps, making the rebuild almost instant. -
EXPOSE 8083: This is a form of documentation. It tells Docker (and other developers) that a container from this image is intended to listen on this port. It doesn't actually publish the port; you still have to do that with the -p flag in docker run. -
CMD ["apache2ctl", "-D", "FOREGROUND"]: This sets the default command to be executed when a container is started. The most critical part here is-D FOREGROUND. Docker containers only stay alive as long as their main process is running in the foreground. If I had used a command that started Apache in the background (like service apache2 start), the command would finish instantly, and the container would think its job was done and immediately exit.
Common Pitfalls¶
-
Using a Background Command in
CMD: This is the most common beginner mistake. It causes the container to exit immediately after starting. The main process must run in the foreground. -
Forgetting apt-get update: Trying to install packages without first updating the package list will often fail with
"package not found"errors. Chaining it with&&in the sameRUNcommand is a common and efficient pattern. -
Case Sensitivity: The file must be named
Dockerfilewith a capital'D'. Naming it dockerfile would cause the docker build command to fail unless you explicitly pointed to the file with the -f flag. -
Permissions: I needed
sudoto create the directory in/optand to build the image, as Docker daemon operations require root privileges.
Exploring the Commands Used¶
-
sudo mkdir -p /opt/docker: Creates the required directory. The-pflag creates parent directories if they don't exist. -
sudo vi /opt/docker/Dockerfile: Creates and edits the Dockerfile with root privileges. -
sudo docker build -t [name:tag] .: The command to build an image.-
-t: Tags the image with a human-readable name and version. -
.: Specifies the build context (the current directory), where Docker will look for the Dockerfile.
-
-
sudo docker run -d -p [host_port]:[container_port] [image_name]: Runs a container from the newly built image.