Create a Docker Image
Learn to create a Docker image from Dockerfiles.
We may have seen many Docker images on Docker Hub and wondered who created them. Well, they were created by developers like us. In this lesson, we learn everything we need to know about creating Docker images.
Creating Docker images with docker build
Let’s take the example above further to a real-world scenario. In the example, we package a real-life application into a container. We use an existing source code of TodoMVC.
TodoMVC is a popular to-do application built in many frameworks.
First, we clone the course repository (https://github.com/abiodunjames/docker-lessons.git). We can do this with the git clone command:
git clone https://github.com/abiodunjames/docker-lessons.git
$ cd path-to-docker-lessons/todomvc/exercise
If we’re able to clone successfully, we should have the docker-lessons
directory created with all the source code for this course in our local environment. We’re interested in the VueJs implementation, so we navigate to the todomvc/exercise
directory.
To preview the application, we can open the index.html
in our browser to see how it works.
To begin the containerization process, we create a file named Dockerfile
in the exercise directory:
touch Dockerfile
The base image
The first instruction to define in a Dockerfile is where we wish to start from. Docker images usually start from a base image, but it’s also possible to start from scratch.
However, a starting point can’t be absent. When working with Dockerfiles, we must specify where we want to start—either from scratch or from a base image.
As stated earlier, a base image or parent image is where our image is based. It’s our starting point. It could be an Ubuntu OS, Redhat, MySQL, Redis, Node Server, and so on.
We’ll use node:12
as the base image here, so we add the code below to the Dockerfile
we created:
FROM node:12
Set the work directory and install dependencies
To set a working directory and install a dependency the application needs, we add the code below to the Dockerfile
:
WORKDIR /app
RUN yarn global add http-server
We use WORKDIR
to set the working directory of the container. WORKDIR
is equivalent to creating a directory and navigating to that directory at the same time. When we use WORKDIR /some/directory
, we use mkdir
/some/directory
and cd /some/directory
. Every instruction we execute afterward will be executed in that directory.
Secondly, we need http-server
to be able to serve static content, so we use the RUN
command to install it globally.
The RUN
command executes a command in a shell. Adding a RUN
instruction creates a new layer and executes the command on the base image.
Copy the source code
One of the fundamentals of containers is that a container packages everything required to run an application, including the source code. This is what makes the COPY
command important. It allows us to copy files and dependencies an application needs to run into a container.
The Docker COPY
command is written as COPY <source>... <destination>
:
COPY package*.json ./
RUN yarn
We use the COPY
command to copy package.json
files from the host to a path in the container and leverage the RUN
command to install the application dependencies in the container.
Now, we have all the dependencies required by the application in the container. We now copy the source code:
COPY . .
Expose a port
EXPOSE 8080
The EXPOSE
instruction informs Docker that a container is listening on a specified port. The EXPOSE
instruction is completely optional.
It doesn’t affect our Docker image’s ability to build or run. However, it’s always good to include it since it serves as a way of documenting and of informing the container’s user about which ports are to be published.
In this example, exposing port 8080
indicates that the application will be accessible on port 8080
when run.
Docker CMD
The CMD
command instructs Docker on how to execute the application we packaged in the image. The CMD
instruction follows the CMD [“command”, “argument1”, “argument2”,...] format
:
CMD [ "http-server", "/app" ]
We can see a CMD
instruction as a way of telling Docker which instruction to run when the container starts. Usually, this is where we should specify how to start the application. CMD
will always be run.
Putting everything together
At the end, our Dockerfile should look like this:
FROM node:12## make the 'app' folder the current working directoryWORKDIR /app## install simple HTTP server for serving static contentRUN yarn global add http-server## copy both 'package.json' and 'package-lock.json' (if available)COPY package*.json ./## install project dependenciesRUN yarn## copy project files and folders to the current working directory (i.e. 'app' folder)COPY . .EXPOSE 8080CMD [ "http-server", "/app" ]
Ignoring files
There’s an important concept we have to internalize while working with Docker images. We should always keep our Docker image as lean as possible. This means that we should only package what our applications require to run.
In reality, the source code typically includes additional files and directories such as .git
,.idea
,.vscode
, travis.yml
, logs
, and so on. These are necessary for our development workflow, but they won’t prevent our application from running. They don’t belong to the Docker container.
It’s best not to include them in our image. That’s what .dockerignore
is for. It prevents such files and directories from entering a Docker build.
We create a file called .dockerignore
in the root folder with the following content:
.git.gitignorenode_modulesnpm-debug.logDockerfile*README.mdLICENSE.vscode
Build a Docker image
With a Dockerfile written, we can build the image using the following command:
docker build .
Try it out:
Ensure we’re in the
docker-lessons/todomvc/solution
directory before executing thedocker build
command.
With the image built successfully, we check it by running the command below:
docker image ls
If we run the command above, the output should look similar to this:
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 9eb760c667c0 3 minutes ago 884MB
In the output above, the repository and latest column are represented as <none>
. When we have numerous images, it can be difficult to distinguish between them. Docker allows us to tag our images with friendly names of our choice. This is called tagging.
Tag the images
Tagging Docker images is important for many reasons:
-
It’ll help us version our Docker images.
-
When we tag a Docker image, we’re able to push it to a Docker registry.
-
Tagging our Docker images properly will drive clarity and is important in CI/CD automation.
We use the following code to tag an image at build time:
$ docker build -t yourusername/repository:image_tag .
Let’s rebuild and tag the Todo application:
$ docker build -t abiodunjames/todomvc .
We should get an output similar to this:
[+] Building 3.4s (11/11) FINISHED
=> [internal] load build definition from Dockerfile 0.3s
=> => transferring dockerfile: 37B
=> => writing image sha256:9eb760c667c0fce2ff680a88b27621e1d42162d4bfbc308b24de575 0.0s
=> => naming to docker.io/abiodunjames/todomvc
Congratulations! We just created a Docker image that can run on any Docker host.
Run a Docker image
Let’s run the image by executing the following command:
$ docker run -p8080:8080 abiodunjames/todomvc
Starting up http-server, serving /app
Available on:
http://127.0.0.1:8080
http://172.17.0.2:8080
Hit CTRL-C to stop the server
The command is pretty simple. We supply the -p flag
to specify the port on the host machine to which the container’s port should be mapped. The container port is the port the application listens to in the container.
To run the container in a detached mode, we can supply the argument -d
:
$ docker run -p8080:8080 abiodunjames/todomvc
Try it out
FROM node:12 # make the 'app' folder the current working directory WORKDIR /app # install simple http server for serving static content RUN yarn global add http-server # copy both 'package.json' and 'package-lock.json' (if available) COPY package*.json ./ # install project dependencies RUN yarn # copy project files and folders to the current working directory (i.e. 'app' folder) COPY . . EXPOSE 8080 CMD [ "http-server", "/app" ]