Multi-Staged Hello Docker!
Probably I’m not the first one who writes about Docker Multi-Stage, and not the first example that prints out Hello Docker!
Recently I had to run a small workshop about Docker and the concepts behind it. Before the session, I prepared a small example to demonstrate what I wanted to share. I guess here is a nice place to put everything together.
The Audiences of this article are mainly beginners and newbies to Docker World 🤓
As we know Docker is a tool designed to make it easier to create, deploy, and run applications by using containers. But wait, what are containers?
Containers allow developers to package up an application with all of the parts it needs, such as libraries and other dependencies, and ship it all out as one package.
Ah okay… then docker is the same as virtual machines? No!
Virtual machines have a full OS with their memory management installed with the associated overhead of virtual device drivers. It’s so awesome that Docker Containers can share a single kernel and share application libraries, that’s why they are super lightweight and make them perform better. The outcome is a reduced size application.
Simplest Docker file (Which is multi-stage)
Let’s try to make a Dockerfile. We need to know some instructions first:
From: It’s a must to start the Dockerfile withFROM
. It specifies the Base Image from which you are building. In this example we are building from Golang Base image [ Since we only need to run/compile a Golang application ]
Workdir: It’s a self-explanatory command. It specifies a new default directory within the images file system and if the directory doesn’t exist, it creates automatically.
Copy: With this instruction from your local machine (host) we copy a file to container’s file system (These are all modifications to the base image).
You might see another command called Add.
Both commands can copy to the container’s file system. Differences are:
- Add accepts 🔗s as source files
- Add unpacks/unzips the *.tar files 😱
For further reads, there is a Stack Overflow question and a nice blog on medium.
Run: Obviously runs a command… BUT first creates a writeable container (An additional layer is added to your container) over the specified image, and then starts it using the specified command.
If you download the repo you will see there is a main.go function. Which is a simple web application that prints out Hello Docker!
What I’m trying to do is copying the main.go
file, installing all dependencies and compiling it. The result is an executable called app
.
However, the question is, do we need all these steps/layer in our “production” environment? The answer is No 😁
That’s why we can add a builder
tag, to show this container will be only used as a builder to compile and generate the executable file.
So what’s the next step? We can have a smaller base image, only containing the executable without having the dependencies installed in the container.
We start the next stage with alpine
as a light base, with copying the executable from the builder stage (Yay!🎉 we made our first docker-multi-stage). Since our application requires to have an open port, we EXPOSE 60234
as it was defined in the body of our program. With CMD command, you provide defaults when executing the container. In this case, we execute our hello docker app!🖐🏽
Okay, but how to run now?
docker build . -t hellodocker
docker run -p 60235:60234 hellodocker
First we need to build from the Dockerfile. By -t
we tagg/name our container hellodocker
. As we saw in the web app implementation it’s listening on 60234
port. To just make things more complex, try to assume on our system this port is already allocated, then we have to map it to another free port!
So in our docker run command, 60235
shows the port number on the host and 60234
is the port on container.
Finally, we can see the Hello message!
🥶 until here… but running and taking care of all these steps are hard. Is there a way to put all these steps into one file and spin up with one line? YES 🤘🏽
Docker is more beautiful with Docker Compose!
I guess the main documentation of docker is more than enough to find out about all the details of compose files.
But let’s shortly recap what’s going on here. Imagine a big project, you want to build and run a couple of containers together. That’s why we start with defining “services
”. In this example the first (and only) service that we have is hellodocker
- build: defines from which Dockerfile it should build and create a container.
- ports: is the same as port mapping that was covered before.
- volumes: super important!! Read a lot about it. Basically it maps a directory from host to the container’s file system. In this demo
.
to/go/src/hellodocker
[ Basically, any changes in this directory on your local host will be reflected there…. Think about how this will useful for databases 🤯 ] - container_name: gives a name to your container.
Last step is running/building:
docker-compose up --build
It will take care of everything 😌
Hopefully, you can use these small hints in your projects and take the advantage of Dockerizing stuff!