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!