Skip to content

Dockerfile

Build and run Images.

What you will learn:

  • Dockerfile syntax
  • Writing a Dockerfile
  • Building an Image from a Dockerfile
  • Rebuilding new versions of an Image
  • Tagging an Image
  • Run an Image
  • Writing Multistage build in a single Dockerfile


1. Syntax

As mentioned in the last section of Chapter 2, a Dockerfile is a text document. It holds instructions for Docker to automatically build an Image as a blueprint for a container. We covered the instructions FROM and COPY. That was sufficient to start a simple static html page.

We also mentioned that Images consist of layers. Thus, you could read a Dockerfile as a list of layers. Each command will add a layer to the image. In order to build more complex and individual containers, there are plenty of instructions available.

As a first step get an overview of the most commonly used instructions on the Docker Documentation.

From the example in the Docker Docs, what’s the base image in use?

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip install flask==2.1.*
COPY hello.py /
ENV FLASK_APP=hello
EXPOSE 8000
CMD flask run --host 0.0.0.0 --port 8000


Solution

It's the one after the FROM instruction.


Alright, that was an easy one!


A Dockerfile always follows this syntax pattern:

# Comment
INSTRUCTION argument

or:

INSTRUCTION argument # Comment

and for variables:

INSTRUCTION variable="value"

# e.g.:

ENV FOO=/bar
WORKDIR ${FOO}   # == WORKDIR /bar
ADD . $FOO       # == ADD . /bar
COPY \$FOO /quux # == COPY $FOO /quux

While \ and ` are escape characters.



2. Writing a Dockerfile

With all that knowledge, let us write a Dockerfile. The Image should build a container with a Go application that requires some configuration. It is just a very limited guestbook that saves the entered messages in different backends.

Copy the go-app directory within this Lab repository (exercise/docker/go-app) to the solutions directory (solutions/docker/go-app).

Now create a Dockerfile. The following steps should be completed. But feel free to add more instructions that could make sense. For a vast documentation on Dockerfile instructions, check out this cheatsheet.

Let’s start:

Create a Dockerfile and open it with your editor of choice / Code IDE.


Solution

For example:

$ cd solutions/docker/go-app
$ cp -r ../exercise/docker/go-app .
$ cd go-app
$ touch Dockerfile
$ vi Dockerfile


Search for the golang:alpine base image on Dockerhub. Add it to the Dockerfile as Base Image.

Solution

# Dockerfile
FROM golang:alpine


Declare /app/k8s-example-app/ as working directory in the container.


Solution

WORKDIR /app/k8s-example-app/


Copy the application code from the go-app directory to the working directory on the container.


Solution

COPY . .


Add the instruction to install all dependencies on the container. The command you need for the go app is go mod download.


Solution

RUN go mod download


Add the instruction to expose the go application on container port 8080.


Solution

EXPOSE 8080


Add the default container command as final instruction in the Dockerfile. This should be go run ., as declared in the package.json.


Solution

CMD ["go", "run", "."]


Alright, now all things should be in place. Take a final look at the Dockerfile. Talk through the steps and check if it makes sense to you.


Hint

You can find a complete solution at solutions/go-app.Dockerfile. In case you are having trouble, take a look at it or copy it into your project directory. There is an extra command in the go-app.Dockerfile that we will explain later.


3. Building and Rebuilding an Image

Now that the Dockerfile is finished, let us build an image from it. It’s pretty much the same flow as in Chapter 2.

Building the image

The Image should have the following params:

name: go-test
version tag: 1.0

Make sure that you declare the correct directory in your command. Also, take note of the output in your terminal about the different layers that are being built!


HINT

Cd into the project folder and run the command from there:

Solution build

$ cd exercise/docker/go-app/
$ docker build -t go-test:1.0 .

Solution buildx

$ cd exercise/docker/go-app/
$ docker buildx build -t go-test:1.0 .


Alternatively declare the Dockerfile you want to use and the directory with the application code:
Solution build

$ docker build -f [path-to-Dockerfile] -t go-test:1.0 [path-to-directory]
e.g.:
$ docker build -f docker/go-app/Dockerfile -t go-test:1.0 .

Solution buildx

$ docker buildx build -f [path-to-Dockerfile] -t go-test:1.0 [path-to-directory]
e.g.:
$ docker buildx build -f docker/go-app/Dockerfile -t go-test:1.0 .


You could also use the Dockerfile from the solutions directory:
Solution build

$ docker build -f docker/solutions/go-app.Dockerfile -t go-test:1.0 .

Solution buildx

$ docker buildx build -f docker/solutions/go-app.Dockerfile -t go-test:1.0 .



Perfect, did you see the logs resembling the individual layer builds? Now, check your images, if the go-test:1.0 is available!


HINT

$ docker images


Rebuilding

That’s looking good. Next we want to demonstrate how cache can help us reduce build time. In this simple case we will just edit the Readme.md file. Open the file and add some text at the beginning.

Optional

Instead of editing the Readme.md file, you can also edit some of the web templates in the web folder if you like.

$ docker build -t go-test:1.1 .
$ docker buildx build -t go-test:1.1 .

Again, take note of the output in your terminal. You should see at least one line of code that is prefixed with CACHED. That means, Docker already has built that layer before and sees, that nothing has changed. Docker takes the layer from a previous build to reduce computation.

You can try running just the same command as before one more time without making any changes! Now, more data can be returned from the cache, as nothing changed in comparison to the last build. Tag the version to 1.2 this time:

$ docker build -t go-test:1.2 .
$ docker buildx build -t go-test:1.2 .

This is an important concept of a Dockerfile. Thus, it can make sense to separate (or combine) some steps in order to avoid long building times.

4. Tagging an Image

One more thing to notice here are the Image Ids. Call docker images and look at the IMAGE ID column. The images go-test:1.1 and go-test:1.2 should have the same id!

That means, if we ‘rebuild’ the same image without any changes but a new tag, we are reusing the image and simply adding a tag. You wouldn’t usually do this. That was only for demonstrational purposes to see, how layers are being cached.

If you want to simply tag an image, there is a dedicated docker command for it. Find it and add the latest tag to the go-test:1.2 image!


Solution

$ docker --help | grep tag
$ docker tag go-test:1.2 go-test:latest


Cool, now your image is up to date and can be run with the common latest tag!

You can remove the older versions of the go-test image now. See in the logs, that removing the first instance is simply untagging the image. When deleting the last instance of an image, it will be fully deleted!


Solution

$ docker rmi go-test:1.0 go-test:1.1



5. Running an Image

As this is a repetition of Chapter 2 topics, we will just briefly go over the run process again. In case these steps are unclear to you, consider to review Chapter 2.

List the images and make sure that you do have got one instance of the go-test image with the latest tag.

Solution build

$ docker images
$ docker build -f docker/solutions/go-app.Dockerfile -t go-test:latest .

Solution buildx

$ docker images
$ docker buildx build -f docker/solutions/go-app.Dockerfile -t go-test:latest .


Now, create and start a container from the go-test:latest image! Make sure to run it in detached mode and include the following specs:

name: go-app
port: 8082


HINT

You can combine the create and start commands in a single run command:

HINT

$ docker run --help


Remember the container port we declared in the Dockerfile of the go-test image. We need to map it to port 8082 on the host machine!
HINT

- container-port: 8080 - host-port: 8082

$ docker run -p 8082:8080 [Image]


Run in detached mode and name the container! The full command would be:
HINT

- container-port: 8080 - host-port: 8082

$ docker run -d -p 8082:8080 --name go-app go-test:latest


Cool, that’s it! Check if the container is running:

$ docker ps

And test the application on http://code.vm0.c<your-ID>.f<training-number>.k8s.workshop.thinkport.cloud:8082!



END