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.
Software Requirements and Conventions Used
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 |
Introduction
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
UID PID PPID C STIME TTY TIME CMD
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
ad325685b738464c49bff40b65c6824160105ab5c285282efefbc4ddeec20ba2
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 0.0.0.0:8000->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/CN=example.com"
RUN a2enmod rewrite
RUN a2ensite default-ssl
RUN a2enmod ssl
EXPOSE 443
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.
<?php
phpinfo();
?>
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.
http://localhost/phpinfo.php
https://localhost/phpinfo.php
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 https://hub.docker.com to create one.
Username: infroger
Password:
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 [docker.io/infroger/app_image]
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:
https://hub.docker.com/r/infroger/app_image
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.
Conclusion
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.