Episode 2: Building and Managing Containers

ยท

5 min read

In the last episode: Episode 1: Unveiling docker core concepts, we look at what docker is and how it is an isolated and lightweight process in the host machine. Now it's time to roll up your sleeves and start building containers.

Let's install Docker

  1. Visit the official docker website: https://docs.docker.com/get-docker/

  2. Follow the commands from there for your respective machine

  3. Verify installation by running docker in the terminal

    ๐Ÿ’ก
    Congratulations! You successfully installed docker on your system ๐ŸŽ‰

Docker architecture

  • Docker follows a client-server architecture i.e. Client( Docker CLI ) sends a request to Server( Docker Daemon ).
docker version

Now, let's try to understand containerd and runc, 2 core components of any container application

Runc and Containerd are both open-source tools for managing containers, but they serve slightly different purposes.

Runc
Runc is a container runtime that provides a low-level interface for creating and running containers according to the Open Container Initiative (OCI) specification. It is designed to be a lightweight and secure runtime that can be easily integrated with higher-level container orchestration systems like Kubernetes.
containerd
Containerd, on the other hand, is a higher-level container runtime that provides a complete environment for managing containers, including image management, container execution, and container lifecycle management. It is designed to be a more robust and extensible runtime that can be used in a variety of container orchestration systems.
  • The Docker client and daemon communicate using a REST API, over UNIX sockets or a network interface.

Docker Architecture diagram

Docker Daemon

The Docker daemon (dockerd) listens for Docker API requests and manages Docker objects such as images, containers, networks, and volumes. A daemon can also communicate with other daemons to manage Docker services.

Docker Client

The Docker client (docker) is the primary way that many Docker users interact with Docker. When you use commands such as, the client sends these commands to dockerd, which carries them out. The docker command uses the Docker API. The Docker client can communicate with more than one daemon.

Docker Registries

A Docker registry stores Docker images. Docker Hub is a public registry that anyone can use, and Docker looks for images on Docker Hub by default. You can even run your own private registry.

When you use the docker pull or docker run commands, Docker pulls the required images from your configured registry. When you use the docker push command, Docker pushes your image to your configured registry.

Dockerfile

A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image

Let's clone the project: https://github.com/docker/getting-started-app and create a Dockerfile in the root of the application and replace with the below content

# Dockerize Node.js application
FROM node:18-alpine

WORKDIR /app

COPY . .

RUN yarn install --production

CMD ["node", "src/index.js"]

EXPOSE 3000

Let's break each of the commands, # are used to write comments

FROM:

FROM [--platform=<platform>] <image> [AS <name>]

Will set the base image for the container, it will pull images from public repositories

WORKDIR:

WORKDIR /path/to/workdir

create a working directory inside your base image, think it as creating a folder for your code inside a new environment.

COPY:

COPY <src> <dest>

copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>.

CMD:

CMD ["executable","param1","param2"]

there can only be one CMD in the Dockerfile If more than one then only the last CMD will take effect.

The main purpose of a CMD is to provide defaults for an executing container.

EXPOSE:

EXPOSE <port> [<port>/<protocol>...]

The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified.

Now, let's run our first container

docker build -t getting-started .

By running build command you are instructing the docker daemon to bundle up your application as an image of the current directory( . signify that) and -t add a tag to it (think like the name of the image) getting-started

You can check all the images present in your system by running docker images

Now, it is time to run the container against your getting-started image

docker run getting-started

run will create a container against getting-started the image that you just created.

You will notice that your cursor is stuck after running this. Why is this so?

๐Ÿ’ก
Stop your container by opening another terminal window and searching for the running container by docker ps and use docker stop <container-id> stop the container

When you run your container docker defaults to foreground mode which attaches the console to the process inside your container's standard input, output and standard error, which makes it appear as if your terminal is "hanging". To prevent this you should start a container in detached mode by specifying -d=true or just -d the option.

The command will return the complete docker ID which can be used to stop or kill the container in future. Or you can use docker ps to see the complete list of running container

If you hit the http://localhost:3000 you will not receive any output because the you expose port of docker and as you learned earlier Docker containers are isolated environments from the host machine, so your network call won't work here.

For this, we have a special tag in the docker run command i.e. --port or -p which is used for port binding of the Docker container with the host

The -p flag takes a string value in the format of HOST:CONTAINER, where HOST is the address of the host, and CONTAINER is the port on the container.

After a few seconds, open your web browser to http://localhost:3000. You should see your app.

Sharing your Image

For sharing our image with the rest of the world, we can make use of Docker Hub

  1. Create an account

  2. Select the Create Repository button.

  3. For the repository name, use getting-started. Make sure the Visibility is Public.

  4. Select Create.

docker push <your-username>/getting-started
ย