Marc Wäckerlin
Für eine libertäre Gesellschaft

Docker Container Introduction

September 25, 2024

Views: 32

The cloud has transformed the way we develop, deploy, and manage applications. One of the key technologies driving this transformation is containerization, which provides flexibility, scalability, and portability for modern applications. In this series, we will start by introducing containers, using Docker as the primary example, and then explore how they are used in various contexts, including orchestration with Docker Swarm and Kubernetes, as well as best practices for security and deployment in cloud environments.

When I started with Docker in 2015, I wrote a docker overview, but that article is outdated.

Containers

Containers are lightweight, portable, and isolated environments that bundle an application with all its dependencies, allowing it to run consistently across different environments. The concept behind containers is to solve the it works on my machine problem by ensuring that applications behave the same, regardless of where they are deployed.

There are several container technologies available, but we will focus on Docker because it is the first and most widely adopted. Docker’s ecosystem, simplicity, and integration with cloud platforms make it an ideal choice for understanding containerization.

Comparison with Virtual Machines (VMs): Unlike traditional virtual machines, which virtualize entire operating systems, containers share the host system’s kernel, making them much lighter and faster to start. VMs require more resources, as each VM includes a full OS, while containers only package the necessary application dependencies, leading to better resource efficiency.

Basic Setup

PlantUML Syntax:</p>
<p>actor User</p>
<p>cloud {<br />
storage Registry {<br />
file image3 {<br />
database mysql<br />
}<br />
file image2 {<br />
[Backend]<br />
}<br />
file image1 {<br />
[Frontend]<br />
}<br />
}<br />
}</p>
<p>cloud Deployment {<br />
port Ingress<br />
node Node1 {<br />
file image1_copy<br />
image1_copy –> [container1] : run<br />
}<br />
node Node2 {<br />
file image2_copy<br />
image2_copy –> [container2] : run<br />
}<br />
node Node3 {<br />
file image3_copy<br />
database container3<br />
image3_copy –> container3 : run<br />
}<br />
node Node4 {<br />
storage «Network File System» as NFS<br />
}<br />
}</p>
<p>User –> Ingress</p>
<p>image1 –down–> image1_copy : pull<br />
image2 –down–> image2_copy : pull<br />
image3 –down–> image3_copy : pull</p>
<p>Ingress -> [container1]</p>
<p>container1 – container2 : network1<br />
container2 – container3 : network2</p>
<p>NFS -> container3 : volume</p>
<p>

In this example, we have three images in the registry, one containing a frontend, one a backend and the third is an image to run a MySQL database. Those images are then instantiated on different nodes in the target deployment cloud. Images are pulled from the registry, which generates a local copy of the image. Then containers are started from the images. Since container1 and container2 are connected with a different network than container 2 and container3, container1 has no access to container3. Container3 instanciates a MySQL database, so it needs a storage, which in this example is provided by a network file system on another node. If storage is not mounted from a network file system, but from the local filesystem in a node, then your deployment fails when the container is migrated to another node. The applications, here a frontend and a backend, should be developed following the Twelve-Factor App principle. Finally the Ingress provides access to an entry point. It can be a container containing a gateway such as Kong or Traefik, or it can be the master of a Docker Swarm.

Registry

A Registry is a centralized repository that stores images. It allows developers to share, distribute, and manage images across different environments. Popular public registries include Docker Hub, while private registries are commonly used within organizations to store custom images securely.

Images

Docker images are read-only templates that define the environment in which a container will run. They include the application, its dependencies, and any system configurations. Think of an image as a blueprint or template for a container, containing everything needed to instantiate a container.

Containers

Containers are the running instances of Docker images. They are analogous to virtual machines, but more lightweight. While an image is static, a container can be stopped, started, and have commands executed inside it. In essence, a container is an isolated environment that runs a specific instance of an image.

Volumes

Docker volumes allow data to persist outside the lifecycle of a container. They are essential for storing data that needs to survive container restarts or updates. Docker supports different volume types, such as local file system storage, network file systems, and object storage like S3. Volumes are commonly used to store database data or application files.

Networking

Networks are virtual networks that connect containers across nodes. By default all containers in a deployment share the same network, but separated networks can be used to segregate container.

Docker supports several networking models to allow containers to communicate with each other and the outside world. The main types include:

  • Bridge network: The default network mode where containers on the same host can communicate with each other.
  • Host network: The container shares the host’s network stack, providing better performance but reduced isolation.
  • Overlay network: Used in Docker Swarm or Kubernetes environments, allowing containers to communicate across multiple hosts.

Example Container

Install docker and docker compose on your computer, on Ubuntu you install:

sudo apt-get install docker.io docker-compose

then add yourself to the group docker, so that you can use Docker as a non root user without sudo:

sudo adduser $(whoami) docker

(If you’re on OpenSUSE, then that’s: sudo usermod -aG docker $(whoami))

Start First Container

Now you’re ready to start a docker container, e.g. a virtual Alpine Linux instance from mwaeckerlin/very-base, which is my starting point for most of my projects. It contains an Alpine Linux with some useful definitions. Alpine because that’s a very minimalist Linux distribution:

docker run -it --name first-test mwaeckerlin/very-base:latest

And you’re immediately inside of a container running an Alpine Linux with a prompt that looks like: `root@very-base[7f15391dd195]:/#` (the number is the container’s instance number). Now you can play around, e.g. install and run an Emacs (note: exit Emacs with ctrl+x ctrl+c):

apk add emacs-nox
emacs /etc/hosts

Leave the container with ctrl-d.

Once you exit, the container stops, but it still exists on your system. You can list stopped containers using: docker ps -a. If you try to start it again with docker run -it --name first-test mwaeckerlin/very-base:latest, you’ll encounter an error: docker: Error response from daemon: Conflict. The container name "/first-test" is already in use by container…. This happens because the container is stopped but still present. To resolve this, remove the stopped container using docker rm first-test, after which you can start a new container with the same name.

The -it option starts the container interactively, giving you immediate access to the container’s terminal. The :latest tag refers to the latest version of the image in the registry at the time of the pull. It’s the default if no other tag is specified. Most images have several tags for different versions or variants. Using --name first-test assigns a custom name to the container, making it easier to reference in later commands.

When you run docker run, Docker will automatically pull the image if it’s not already available locally. If a local copy exists, Docker will use that version. However, be cautious—your local image might be outdated. To ensure you’re using the most up-to-date version, you can explicitly run docker pull mwaeckerlin/very-base before starting the container.

The command docker run -it is effectively a shortcut for multiple operations: docker pull (if needed) + docker create + docker start + docker exec (or docker attach). In other words, it pulls the image (if necessary), creates a container, starts it, and attaches you to the container’s terminal.

We cleaned up the container with docker rm first-test, which deleted the stopped container. The image, however, remains on your system, which you can verify with docker image ls. Images take up considerable disk space, so it’s important to remove unused images when they are no longer needed. You can delete unused images manually with docker image rm name, or use the prune command to automatically clean up unused containers, images, networks, and volumes.

For example, to remove all non-running containers, use docker container prune. The same command exists for images, networks, and volumes. Be especially careful when pruning volumes, as they store persistent data. Deleting a volume without caution could result in permanent data loss.

If you want to free up space by removing all stopped containers, unused networks, and dangling images, use the docker system prune command. This command provides a broader cleanup of Docker resources and is particularly useful for maintaining a tidy environment.

Now you are ready to dockerize your first project.

comments title