Building a Custom CentOS Image with Dockerfile: Commands, Best Practices, and Troubleshooting

Dockerfile is a declarative text file that defines the steps required to assemble a Docker image. It enables reproducible, version-controlled, and automated image builds—essential for modern containerized application delivery.

Core Build Workflow

  1. Author a Dockerfile with instructions describing environment setup, dependencies, and runtime configuration.
  2. Execute docker build to process the file and generate an immutable image layer by layer.
  3. Launch containers from the resulting image using docker run.
  4. Push the finalized image to a registry (e.g., Docker Hub or Alibaba Cloud Container Registry) via docker push.

Anatomy of a Dockerfile

  • Instructions are case-sensitive and must be uppercase (e.g., FROM, RUN).
  • Execution proceeds top-to-bottom; each instruction creates a new filesystem layer.
  • Lines starting with # are comments (unless they begin # syntax=).
  • Each RUN, COPY, or ADD command produces a cached intermediate layer—critical for efficient rebuilds.

Key Instructions Explained

Instruction Purpose
FROM Specifies the base image (e.g., centos:7, alpine:latest). Required as the first non-comment line.
LABEL Adds metadata (e.g., maintainer, version, license) in key-value format. Replaces deprecated MAINTAINER.
ENV Sets persistent environment variables accessible during build and at runtime.
WORKDIR Defines the default working directory for subsequent RUN, CMD, ENTRYPOINT, and COPY instructions.
RUN Executes commands in a new shell layer (e.g., installing packages, configuring services).
COPY Copies local files or directories into the image filesystem (more secure and explicit than ADD).
EXPOSE Documents which ports the container listens on (does not publish them; use -p at runtime).
CMD Provides default executable and arguments invoked when the container starts. Overridden by CLI arguments to docker run. Only the last CMD takes effect.
ENTRYPOINT Configures a container to run as an executable. Arguments passed to docker run append to the ENTRYPOINT array, enabling flexible command composition.
ONBUILD Triggers instructions only when the current image serves as a base for another FROM instruction.

Practical Example: Extending CentOS 7

Official CentOS images are minimal—often lacking common utilities like vim or net-tools. To create a more usable variant:

  1. Initialize project structure:

    mkdir -p ~/docker-builds/centos-enhanced && cd ~/docker-builds/centos-enhanced
    
  2. Create Dockerfile:

    FROM centos:7
    LABEL maintainer="dev@example.com"
    
    ENV APP_HOME=/opt/app
    WORKDIR $APP_HOME
    
    # Fix yum repos for EOL CentOS 7
    RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo && \
        sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*.repo && \
        yum makecache && \
        yum -y install vim-enhanced net-tools && \
        yum clean all
    
    EXPOSE 80
    CMD ["/bin/bash"]
    
  3. Build the image:

    docker build -t enhanced-centos:7.9 .
    

⚠️ Note on Repository Mirrors: As CentOS Linux 7 reached end-of-life, its official mirrors no longer serve updated packages. The sed substitutions above redirect yum to vault.centos.org, which hosts historical snapshots.

Testing the Resulting Image

# Launch interactively
$ docker run -it enhanced-centos:7.9

# Verify tools exist and work
[root@5a2b1c4d8e9f app]# vim --version
VIM - Vi IMproved 7.4...

[root@5a2b1c4d8e9f app]# ifconfig eth0 | grep "inet "
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255

# Confirm WORKDIR is active
[root@5a2b1c4d8e9f app]# pwd
/opt/app

Understanding CMD vs ENTRYPOINT

Scenario A: Using CMD

Dockerfile-cmd:

FROM alpine:3.19
CMD ["echo", "hello"]

Build and test:

docker build -f Dockerfile-cmd -t cmd-demo .
docker run cmd-demo               # → "hello"
docker run cmd-demo world         # → Error: "world" not found as executable

The CMD is fully replaced by CLI arguments — world attempts to launch a binary named world, failing.

Scenario B: Using ENTRYPOINT

Dockerfile-entrypoint:

FROM alpine:3.19
ENTRYPOINT ["echo", "hello"]

Build and test:

docker build -f Dockerfile-entrypoint -t entry-demo .
docker run entry-demo             # → "hello"
docker run entry-demo world       # → "hello world"

Here, world appends to the ENTRYPOINT array, forming echo hello world.

When to Choose Which?

  • Use CMD when you want users to easily override the default command entirely (e.g., debugging shells, ad-hoc commands).
  • Use ENTRYPOINT when the container should behave like a binary—accepting flags or subcommands while preserving core functionality (e.g., database clients, CLI tools, or wrapper scripts).

Multiple ENTRYPOINT instructions are invalid; only the final one applies. You may combine both: ENTRYPOINT sets the executable, and CMD provides default arguments that can be overridden.

Inspecting Build History

To audit how layers were created:

docker history enhanced-centos:7.9

This reveals each instruction, layer ID, size, and creation timestamp—valuable for optimization and security analysis.

Final Notes

  • Prefer COPY over ADD unless tar extraction or remote URL fetching is needed.
  • Chain related RUN commands with && to minimize layers and reduce image size.
  • Clean package caches (yum clean all, apt-get clean) in the same RUN step where packages are installed.
  • Use .dockerignore to exclude unnecessary files (e.g., .git, node_modules) from the build context.

Tags: docker dockerfile centos containerization devops

Posted on Thu, 07 May 2026 07:06:50 +0000 by billkom