Build your first image using Dockerfile

Images from the Dockerfile

In my previous post, I have used existing images that come from Docker registries. Here, we’re going to cover how to create our own image, using a Dockerfile.

The Dockerfile is text file with commands that you can use to create your own image.

This allows you to specify the starting place for your image, and then you can specify the changes you’d like to have made to that image.

We’re going to create a new image, here’s what I want for this demo, I want a lightweight container, that runs a binary of my own creation, and by lightweight, what I mean is, I’m looking for a container that only has as much as it needs to run my binary. I don’t want a bunch of additional, files, such as what you’d see in an Ubuntu image.

Here’s the code for the binary, this is a very basic, hello world app, written in Go. I have already compiled the app, and I have created the binary hello.

package main
import "fmt"

func main() {
	fmt.Printf("hello, world\n")

Compile this using env GOARCH=386 GOOS=linux go build hello.go. The env variables are necessary, otherwise you will run into architecture issues when running inside the docker container. Specifically, you will get this famous error: cannot execute binary file: Exec format error. All it is saying is if you run go build hello.go then it is going to use mac’s OS (darwin) as its architecture, and this causes issues because scratch image does not use the same architecture. Anyway, building this binary is not the focus of this post, so let’s get back.

(base) shravan-docker# env GOARCH=386 GOOS=linux go build hello.go

So let’s look at how to create an image with Dockerfile that can run this binary. Here’s the Dockerfile that we’ll use for this demo.

FROM scratch
COPY hello /
CMD ["/hello"]

It’s starts out with a FROM instruction, which is used to specify the starting image. This allows you to set the image that you want to build on top of. This makes Docker rather flexible, because you can use any of the community images, an image from the store, or you own base image, as the start. This demo uses a special image called scratch, the reason it’s special is that it’s not something that you can just run, the same way the we ran the hello world or Ubuntu images. This is meant to be used as a minimalist base. So, FROM scratch, tells Docker, to start our image using the scratch image.

The next instruction is the COPY instruction, which allows you to copy files from your host, into a layer of the image. In this example, I’m telling Docker, to copy the file named hello, into the root directory of the image.

After the command is processed, a new layer will be created, containing this hello binary.

Any commands that we run afterward, will be able to interact with that hello binary.

The copy instruction can copy files, and directories, and it also supports wild cards.

Finally, we have the command CMD instruction, which is the default command to run, when the container starts up, unless one is specified on the command line. So there should be one command instruction per Dockerfile. If you have multiple, then it’s always going to use the bottom most.

This syntax here, has the command to run as an array, where the first element is the binary to execute. And then additional elements in the array, are arguments for the executable. So this is telling Docker to run the hello world binary when the container starts.

Okay, in theory, this Dockerfile should meet the requirements for the demo is set. I said, it should be lightweight, and allow me to run a binary. Because we’re using the default scratch image, it’s going to be lightweight. And using the copy and command instructions, allow us to run the hello binary. Let’s turn this Dockerfile into an image now.

I’m here at the bash prompt, and I’m in a directory containing the Dockerfile, and hello binary. If I run this binary here, it’s going to show us what the results will look like when we run it inside of a Docker container. So there you go.

(base) shravan-docker# ls
Dockerfile	hello		hello.go

base) shravan-docker# ./hello
hello, world

Let me start off by listing the existing images.

docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
ubuntu                            latest              4e5021d210f6        4 weeks ago         64.2MB
hello-world                       latest              fce289e99eb9        15 months ago       1.84kB

Currently there are no docker containers, as shown here. This is because I ran docker container prune to remove all old containers.

(base) shravan-docker# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
(base) shravan-docker#

So, the command to build an image from Dockerfile is docker build <dir-where-dockerfile-is-located>. Since the Dockerfile is in my current directory, I need to just run this:

(base) shravan-docker# docker build .
Sending build context to Docker daemon  2.186MB
Step 1/3 : FROM scratch
Step 2/3 : COPY hello /
 ---> 1755a369820b
Step 3/3 : CMD ["/hello"]
 ---> Running in d5c7fafe47b3
Removing intermediate container d5c7fafe47b3
 ---> c614233f6b53
Successfully built c614233f6b53
(base) shravan-docker#

And notice it goes through our instructions from the Dockerfile. And it ends with a success message. So, now if we list the images again, it’s going to show our new image.

(base) shravan-docker# docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
<none>                            <none>              c614233f6b53        37 seconds ago      2.18MB
ubuntu                            latest              4e5021d210f6        4 weeks ago         64.2MB
hello-world                       latest              fce289e99eb9        15 months ago       1.84kB

And there it is. So what I want you notice is that, it doesn’t actually have a repository or a tag, it does have an ID, though, using an ID to reference an image is, one of the most unintuitive ways you can interact with an image. So I built it this way intentionally, to show you what happens when you build this, without providing a repo name.

Let’s remove this image. I want to remove this, and build it again with a repo name.

To remove an image, use docker rmi <imageid>

base) shravan-docker# docker rmi c614233f6b53
Deleted: sha256:c614233f6b5376a432e3fb7cb4450cb3d969f3f1a243803826992fefd8b4ec85
Deleted: sha256:1755a369820b79caa392f85758e9a69b9f25a40ef4337e4949d0bfa7579f2a58
Deleted: sha256:0f3b8ce35e4f53ee45b14a1e1b4fd23bc2a4678fa3c3ad5c90a87061dbd7eb8d
(base) shravan-docker#

Okay, so let’s Build an image from a Dockerfile again but this time with -t option specifying a repo and tag name.

base) shravan-docker# docker build -t greeting .
Sending build context to Docker daemon  2.186MB
Step 1/3 : FROM scratch
Step 2/3 : COPY hello /
 ---> 5e31aaab56b5
Step 3/3 : CMD ["/hello"]
 ---> Running in fd7a7299416f
Removing intermediate container fd7a7299416f
 ---> 65bd78e55f92
Successfully built 65bd78e55f92
Successfully tagged greeting:latest
(base) shravan-docker#

And there we have it. Notice here at the bottom, it says, it was successfully tagged with greeting, and a colon, and then the word latest. Listing the images again, notice the repository is named greeting, and then there’s a tag that says latest. Tags in Docker have their own structure, and they allow you to supply a repo name, and a tag, at the same time. Because I only provided the repository portion of the Docker tag, it’s going to automatically use a tag of latest.

(base) shravan-docker# docker images
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
greeting                          latest              65bd78e55f92        2 minutes ago       2.18MB

Now that we have an image, we can actually create a container based on it. So let’s give that a try. And we’ll do that with the Docker run command, and passing in the repository name of the image. And there it is, there’s the output that we expected from the hello binary. So we can use the Docker ps command, with a minus a flag, to show that the container ran, and then it exited successfully, based on this status code of zero here.

Execute: docker run greeting

(base) shravan-docker# docker run greeting
hello, world
(base) shravan-docker#
(base) shravan-docker# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
92015b8c5b95        greeting            "/hello"            About a minute ago   Exited (0) About a minute ago                       naughty_shirley


The takeaway is that, you can create your own images using the Dockerfile. It allows you to build your image, based on an existing image, and include any files you may need, and then set the default command to execute, when the container starts up.