How To Customize Docker Images With Dockerfiles

This article shows how to customize Docker images using a description file named Dockerfile. You’ll see how to extend existing images, customizing them to your needs, and also how to publish the resulting image to Docker Hub.

In this tutorial you will learn:

  • How to customize an image with a Dockerfile.
  • How to publish the resulting image in Docker Hub.

HTTPS is enabled

HTTPS is enabled.

Software Requirements and Conventions Used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Ubuntu 18.04 Bionic Beaver
Software Docker
Other Privileged access to your Linux system as root or via the sudo command.
Conventions # – requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ – requires given linux commands to be executed as a regular non-privileged user


The previous articles presented Docker concepts and some basic Docker commands. In this article, you’ll see how to customize and extend an existing Docker image, describing the modifications in a Dockerfile, and publishing the image to a registry.

The Dockerfile

In the previous article, you’ve made modifications to a running container and committed the changes to the local image cache. Although it’s a helpful resource for specific situations, it’s recommended that customizations are made in a more documented way, so that the image can be deployed to other hosts. The recommended way is to write a Dockerfile.

The Dockerfile is a YAML file, which is a text file with some syntax: relations are expressed using indentation (spaces) and each line is comprised of key and value pairs.

Let’s start with a simple Dockerfile that installs package props (contains commands htop and ps) to a Debian image.

Create a new directory, get into it, and save the file below with the name Dockerfile (capital D):

FROM debian
RUN apt-get update &&\
    apt-get -y install procps

This Dockerfile states that the base image is named Debian (FROM clause). If it doesn’t exist locally, it will be downloaded from the Docker Hub. The RUN command executes apt-get twice. Notice the use of a backslash (\) to break a line and the use of -y to skip the confirmation prompt of apt-get install.

Next step is to build the image with docker build.

$ docker build -t mydebian .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM debian
 ---> be2868bebaba
Step 2/2 : RUN apt-get update &&    apt-get -y install procps
 ---> Running in 52a16b346afc
Removing intermediate container 52a16b346afc
 ---> f21a05a59966
Successfully built f21a05a59966
Successfully tagged mydebian:latest

The flag -t mydebian is naming the new image. The dot (.) tells docker to use the current directory to look for a Dockerfile. Notice that new layers are created and removed as the lines of the Dockerfile are interpreted.

There must be a new image in the local cache.

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mydebian            latest              f21a05a59966        8 minutes ago       119MB
debian              latest              be2868bebaba        7 weeks ago         101MB

A container from this image can be created.

$ docker run -it --name mydebian_container  mydebian
root@ef9eb174874a:/# ps -ef
root         1     0  0 02:43 pts/0    00:00:00 bash
root         9     1  0 02:43 pts/0    00:00:00 ps -ef

From now on you can create containers running Debian with the procps package, and the commands htop and ps will be already installed.

Now let’s create a Dockerfile to have Apache and PHP installed at image build time, to achieve the same objectives of the previous article, when the commands were executed inside the container.

FROM debian
RUN apt-get update &&\
    apt-get -y install procps libapache2-mod-php
CMD service apache2 start

We’ve added libapache2-mod-php in Line 3 and a CMD command in Line 4 to start Apache. When the container is started, the CMD command is executed. There can exist only one CMD command per Dockerfile. When the CMD command is specified, it replaces the CMD command of the image you are extending. If the CMD command is omitted, the one of the base image will be executed (if any). As you may have guessed, the Dockerfile of the Debian base image has a CMD command to execute bash. You may check this in the Docker Hub.

$ docker run -d --name mydebian_container2 -d -p 8000:80 -v "$PWD":/var/www/html mydebian
roger@slash:~/LinuxConfig/04 Dockerfile$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
ad325685b738        mydebian            "/bin/sh -c 'service…"   11 seconds ago      Up 5 seconds>80/tcp   mydebian_container2

This time we started the container using the -d switch because we want it to be detached from the terminal.

Important Dockerfile Commands

The Dockerfile has other commands beyond FROM, RUN, and CMD.

Command ENV is used to set environment variables in the image, like http_proxy, for example. Many images use environment variables to pass parameters to the new container. For examples, check the images of databases like MySQL and PostgreSQL in docker hub.

Command COPY copies files and directories from the host to the image at build time. The source path (first argument) is relative to the current directory.

Command ADD is similar to COPY, with the difference that, if the source is a compressed tar file, it will be automatically decompressed in the destination directory inside the image. Except for that use, Docker recommends the use of the COPY command whenever possible.

Command EXPOSE indicates which ports of the image can be exposed by Docker. During container creation, those ports can be mapped to host ports, if desired.

Command WORKDIR sets the directory that Docker will use when commands are executed inside the container with docker exec.

Creating an Image With HTTPS Enabled

Now we’ll extend the official PHP Apache image to activate SSL with an auto-generated certificate to exemplyfy how to use the mentioned commands. In a new directory create the following Dockerfile.

FROM php:7-apache

RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/ssl-cert-snakeoil.key -out /etc/ssl/certs/ssl-cert-snakeoil.pem -subj "/C=BR/ST=Rio Grande do Sul/L=Porto Alegre/O=Security/OU=Development/"

RUN a2enmod rewrite
RUN a2ensite default-ssl
RUN a2enmod ssl


COPY ./html /var/www/html

WORKDIR /var/www/html

In Line 3 we create a certificate. Lines 5 – 7 enable mod_rewrite and SSL. Line 9 exposes port 443 (port 80 is already exposed by the upstream image). Line 11 adds the application directory to the container. Finally, Line 13 sets the working directory as the Apache working directory. All commands executed by docker exec will use this directory as the base by default.

Now, create a directory named html and a file named phpinfo.php with this content.


Let’s now build and run the container.

docker build -t app_image .
docker run -d --rm -p 80:80 -p 443:443 --name app_container app_image

Now, you can access phpinfo.php script through both, HTTP and HTTPS.

HTTPS is enabled

HTTPS is enabled.

In HTTPS the browser will complain about the certificate’s security since this is self-signed, but the warning can be ignored.

Publishing Images To The Docker Hub

The images created exist only locally, in Docker’s local cache. You may want to share them with other Docker hosts, or with teammates, or even make them public to the world. In any case, you want to publish your images to a Docker registry. They can be published to a cloud-based registry, like the Docker Hub which, by the way, is the default if you don’t explicitly specify the registry. First create a free Docker ID, then login:

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to to create one.
Username: infroger
Login Succeeded

Next, tag the image with the repository name (infroger), image name and tag (image version).

$ docker tag app_image infroger/app_image:1
$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
infroger/app_image   1                   c093151fc68f        14 hours ago        381MB
app3_image            latest              c093151fc68f        14 hours ago        381MB

Then push the image to the repository.

$ docker push infroger/app_image:1
The push refers to repository []
27f7f2b01c49: Pushed 
81b08cd5fe07: Pushed 
d1c23d198f84: Pushed 
e66392ad9b85: Pushed 
a71f63e3a00f: Pushed 
9c58778f21dd: Pushed 
973719bed9b7: Pushed 
8f5090ef2ac0: Pushed 
fbdafdbe3319: Pushed 
a5c4801ecf39: Pushed 
e9ba112d38b9: Pushed 
25ba5230dadf: Pushed 
f2907ce42b47: Pushed 
e31bf34cfab9: Pushed 
9066d03e98e0: Pushed 
96db4ce698ad: Pushed 
abae6a338e5c: Pushed 
4572a80a7a5e: Pushed 
ef68f6734aa4: Pushed 
1: digest: sha256:2e7e53fcdf800ad0c4837cd70014170cc869d36de5c301f2e2ced318803bf963 size: 4279

Now go to Docker Hub and check the image is there:

In Docker Hub with free registration, you can have one private repository, with unlimited public repositories. Otherwise, you may want to run your own Docker registry, which can be done with one command:

docker run -d -p 5000:5000 --restart=always --name registry registry:2

The advantage of having a private registry is privacy. But you have the burden to manage security, high availability, storage requirements, access control, etc.


In this article, we’ve covered how to extend existing images and customize them to your needs using a Dockerfile. We’ve also seen how to publish the images to a Docker registry. You can do a lot so far, but we are just scratching the Docker world. In the next article, we’ll see a very simple form of local container orchestration with Docker Compose.

More in this Docker article series

Comments and Discussions
Linux Forum