Hands on Dockerfile: Recipes of containers (Part 1)
docker containerization dockerfile
Docker has revolutionized the way we package, deploy, and manage software applications. Its containerization technology offers unprecedented portability, speed, and resource efficiency. If you’re in the fields of development, system administration, or DevOps, learning Docker is a valuable investment. In this article, we’ll take you on a hands-on journey with Docker.
Concepts you need to know
Before getting our hands dirty, it’s important to understand concepts about docker and containerization. Check out the following articles to learn about the concepts:
- Hands on Dockerfile: Recipes of containers
- Containerization: Revolutionizing Software Development and Delivery
Prerequisites
In order to follow this tutorial, you need to meet the following requirements:
-
Familirility with basic linux CLI (Command Line Interface): You should be comfortable with navigating your terminal and running commands. Check out this article at https://bagor.tech/what-is-cli/ to learn about the CLI.
-
Have a system with Docker installed: If you haven’t already done so, you can install Docker on your system (Windows, macOS, or Linux). Follow the instructions at https://www.docker.com/get-started/.
-
Install git in your system: Install git from here https://git-scm.com/downloads/.
Docker CLI
Now that you have Docker installed, open your terminal (Mac/Linux) or command prompt (Windows) and execute the following command:
docker --help
Usage: docker [OPTIONS] COMMAND
A self-sufficient runtime for containers
Common Commands:
run Create and run a new container from an image
exec Execute a command in a running container
ps List containers
build Build an image from a Dockerfile
pull Download an image from a registry
push Upload an image to a registry
images List images
login Log in to a registry
logout Log out from a registry
search Search Docker Hub for images
version Show the Docker version information
info Display system-wide information
...
If you get the same response, it means that you’ve installed docker succesfully.
While you are here, take some time to read docker commands documentation.
In this tutorial, we will only use docker run
, docker ps
, docker build
, docker exec
, docker image
and docker compose
.
Note: response may vary depending on the Docker version you installed.
Your first docker container
For your first container, let’s start by running a simple “Hello, World” image from Docker Hub:
docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
478afc919002: Pull complete
Digest: sha256:a26bff933ddc26d5cdf7faa98b4ae1e3ec20c4985e6f87ac0973052224d24302
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
What happened:
- You asked Docker to run the entrypoint command of the “hello-world” image (
docker run hello-world
). - Docker looked for the image on your computer and didn’t find it.
- Docker went online to Docker Hub and downloaded the “hello-world” image.
- Docker built a tiny virtual environment (a container) using the “hello-world” image.
- Docker ran a tiny program inside that container that printed the “Hello from Docker!” message.
Now, you can check whether you have downloaded the “hello-world” image on your computer by executing the following command:
docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest ee301c921b8a 11 months ago 9.14kB
Writing a container image (Dockerfile)
Prelude
-
Clone this github repository
git clone https://github.com/gstdl/rust-docker-demo-counter-api.git cd rust-docker-demo-counter-api
-
Understanding the contents of the repository
The code you just cloned contains a simple web server written in Rust. This web server’s purpose is to keep track of a counter value. It expects a configuration file (‘config.yaml’) to exist and reads a “username” from it (you can change the username if you want to). When executed, it will serve 3 endpoints:
/
: Displays the current counter value./add
: Increments the counter./reset
: Resets the counter to 0.
All 3 endpoint will also display a greeting message to the “username” provided in the configuration file.
Can I test the app without installing Rust? Yes (click for more details)
You can play around with the application by executing the command below.
docker run -p 8000:8000 gstdl94/rust-docker-demo-counter-api
This will pull and run a working container of the application from docker hub. Once you have the container up and running, you can visit http://localhost:8000/, http://localhost:8000/add, and http://localhost:8000/reset to see what the app does.
Click cmd+c (macOS) or ctrl+c (Windows/Linux) to exit the app.
Writing the Dockerfile
-
Write the Dockerfile
To begin, let’s create a new file named
Dockerfile
. The content of the file will be as follows:# Base image FROM rust:1.77.2-alpine3.18 # Set working directory WORKDIR /app # Copy application code and dependencies COPY . . # Install OS dependencies RUN apk add --no-cache musl-dev # Build the application RUN cargo install --path . # Set environment variable ROCKET_ADDRESS ENV ROCKET_ADDRESS 0.0.0.0 # Run the application CMD [ "/usr/local/cargo/bin/rust-rocket-counter-api" ]
Detailed explanation (click to expand)
- Selecting a base image
FROM rust:1.77.2-alpine3.18
Every
Dockerfile
starts with a base image and every instructions afterwards extends the base image. The format of a base image is typically<image>:<tag>
where in this tutorial the image isrust
and the tag is1.77.2-alpine3.18
indicating that we are building on top of rust version 1.77.2 on alpine version 3.18. You can search for base images in Docker Hub- Extending the base image
After selecting the base image, we continue by extending it using Docker instructions. The instructions used to extend the
rust
base image for this application starts with defining the working directory.WORKDIR /app
After that, we continue by copying files and folders from our current working directory to the container’s working directory.
COPY . .
Next, we install required the application os dependencies and build the application binary.
RUN apk add --no-cache musl-dev RUN cargo install --path .
In this step, we set environment variables required for the application.
ENV ROCKET_ADDRESS 0.0.0.0
Finally, we execute the final command that will be executed once we start the application.
CMD [ "/usr/local/cargo/bin/rust-rocket-counter-api" ]
-
Building the Image:
Build an image named
my-counter
:docker build -t my-counter:latest .
-
Running from your Image:
Run this command to run the
my-counter
app:docker run -p 8000:8000 my-counter
Using config file: config.yaml thread 'main' panicked at src/main.rs:41:56: Can't open config.yaml: Os { code: 2, kind: NotFound, message: "No such file or directory" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Running
my-counter
app will result in the error message above. The error message is telling us that the application is expecting a config file namedconfig.yaml
and it is not found.We can diagnose the file existenct by executing the command below. This will list all the files in the container’s working directory by executing the
ls
command.docker run my-counter ls
Cargo.toml Dockerfile LICENSE src target
Notice that
config.yaml
is not listed in the working directory, but it is available in the application github repository. We can map the config file from our computer to the container by using the-v
(or--volume
) flag indocker run
.docker run -v ./config.yaml:/app/config.yaml my-counter ls
Volume mapping explanation (click to expand)
./config.yaml
is where the config file is located in our computer./app/config.yaml
is where we expect the config file is located in the container.
You can map the file using
-v
flag or removeconfig.yaml
from.dockerignore
file.Cargo.toml Dockerfile LICENSE config.yaml src target
Finally, the full command to run the app is:
docker run -p 8000:8000 -v ./config.yaml:/app/config.yaml my-counter
Using config file: config.yaml Rocket has launched from http://0.0.0.0:8000
Visit http://localhost:8000 to play around with the app (this assumes your app listens on port 8000).
Final remarks
Congratulations, you have succefully created a Dockefile. If you followed along, not only have you written your first Dockerfile, but you’ve also did a little bit of debugging to create your Dockefile.
Next Steps
- Explore Docker Hub: Find and use images for databases (MySQL, PostgreSQL), web servers (Nginx, Apache), and other commonly used software.
- Understanding Docker Volumes: Persist data generated by your containers.
- Trying Docker Compose: Manage multi-container applications with ease.
- Dockerfile best practices: Figure out ways to reduce docker image size and build time.