A Deep Dive into Container Identification and Dependency Management
Tags: docker, beginners
Published: 2024-03-29 13:16:38

_edit: Thanks to @mitz215 for finding the error in my post_

Welcome to an in-depth exploration of Docker’s container identification and dependency management. As we navigate the world of Docker, we often encounter scenarios where we need to create multiple instances of a container. While this might seem straightforward, it can quickly become complex due to potential naming conflicts and intricate dependency chains. 

This post aims to unravel these complexities, providing you with a comprehensive understanding of Docker’s unique identification system and the nuances of managing container dependencies. 

We’ll walk you through the process of creating, renaming, and managing containers, delve into the significance of unique identifiers, and explore how Docker handles dependencies among containers.

## Flexible Container Identification

Imagine that you want to create multiple copies of an `nginx` container. Our first thought would be to simply start more than one web container.

Let's try to do that.

```shell
# Create a container named "webserver"
docker run -d --name webserver nginx

# Create another container named "webserver"
docker run -d --name webserver nginx
```

The second 'docker run' command will trigger a conflict error:

```

docker: Error response from daemon: Conflict. The container name "/webserver" is already in use by container "c1add141a7729d0995e516549ad6981f15c3a6af0af026cfb17d1e6d65dbf131". You have to remove (or rename) that container to be able to reuse that name.

```

It is essential to have unique container names to prevent conflicts, particularly in environments with multiple containers. Fixed names like 'webserver' are useful for experimentation and documentation purposes.

Docker generates a unique and easily understandable name for each container it creates. The `—name` flag simply overrides the process with a predetermined value. To change the name of a container, you can use the Docker rename command.

```shell
# Rename the first web container to webserver-old
docker rename webserver webserver-old

# Create a new container named webserver.
docker run -d --name webserver nginx
```

While renaming containers can resolve one-time naming conflicts, assigning unique names from the beginning can prevent these issues entirely. Apart from the name, Docker also assigns a distinct identity to each container. These identities are represented as hex-encoded 1024-bit numbers, such as:

```
c1add141a7729d0995e516549ad6981f15c3a6af0af026cfb17d1e6d65dbf131
```

When in detached mode, Docker displays the unique identifiers of containers on the terminal for easy reference purposes. You can use these identifiers instead of the container name in any command that needs to identify a specific container. For example, you may utilize the prior ID with a `stop` or `exec` command:

```shell
docker exec \
  c1add141a7729d0995e516549ad6981f15c3a6af0af026cfb17d1e6d65dbf131 \
  ps

docker stop \
  c1add141a7729d0995e516549ad6981f15c3a6af0af026cfb17d1e6d65dbf131
```

The vast number of potential combinations for a 1024-bit alphanumeric ID significantly reduces the likelihood of collisions. Let's consider assigning unique IDs to a billion entities—a vast number. Even in this scenario, the probability of two entities having the same ID is miniscule because the total number of possible IDs is astronomically larger. Imagine it like having a unique identifier for every grain of sand on all the beaches on Earth—there's very little chance of two grains sharing the same ID.

While the first 12 characters of a 1024-bit alphanumeric ID don't guarantee the same level of uniqueness as the full ID, they still offer a very high probability of being unique. Even with just 12 characters, the vast number of possible combinations ensures a high level of uniqueness. Considering there are 16 possible characters (6 letters + 10 digits), there are a total of 16^12 combinations at your disposal. In most practical scenarios, this vast number of combinations is more than enough to ensure that the first 12 characters provide unique identifiers, minimizing
the risk of collisions.

So, In most Docker interfaces, container IDs are typically displayed using the first 12 characters for easier identification and reference. This makes the IDs slightly more user-friendly by simplifying their usage. We can use them anywhere that a container identifier is necessary. So, the previous two commands may be written as:

```shell
docker exec c1add141a772 ps
docker stop c1add141a772
```

Both of these IDs are considered difficult for users to work with because of their complexity and limited informative value. However, they function exceptionally well with scripts and automation approaches. In certain situations, a full or truncated numeric ID will be utilized.

One way to get a container's ID is by starting or creating a new container and saving the output in a shell variable. The container ID is written to the terminal (`stdout`) when a new container is started in detached mode. If this were the only way to get the container ID when it was made, you couldn't use it with interactive containers. You can make a container without starting it, though, by using a different command. The `docker create` command is a lot like `docker run`. The main difference is that when you use `docker create`, the container is already stopped.

In a Linux shell such as sh or bash, you can easily save the output to a shell variable for future use:

```shell
container_id=$(docker create nginx:latest)
echo $container_id
```

This method enables you to obtain the container ID without initiating the container. Once you have the container ID saved in a variable, you can use it for various operations, such as starting, stopping, or removing the container.

Although shell variables can lead to conflicts, these issues are confined to the terminal session or the specific working environment in which the script runs. Conflicts should be easily avoidable as they are confined to the terminal session or the specific working environment controlled by a single user or program. However, this method is limited in scenarios where data sharing among multiple users or automated systems is required, potentially leading to conflicts. This is where a container ID (CID) file comes in handy to manage container IDs efficiently.

Both `docker run` and `docker create` commands provide the `--cidfile` flag, which allows you to save the ID of a new container in a designated file.

```shell
# Create a new stopped container
docker create --cidfile /tmp/webserver.cid nginx

# Inspect the file
cat /tmp/webserver.cid
```

This approach, like using shell variables, makes it easier for things to go wrong. You must know or have a good idea of the arrangement of the CID file name that you give after `--cidfile`.

This method uses known names in a global (Docker-wide) namespace, the same way that manually naming containers does. Docker won't make a new container with the given CID file if that file already exists, which is good news. It will fail the same way it fails when you make two buckets with the same name.

One reason to use CID files instead of names is that they are easy to share between containers and can be given a new name for each one. Volumes, a feature offered by Docker, help efficiently manage container IDs by simplifying their organization. We will talk more about volumes another time.

Alternatively, you can utilize tools like docker ps to fetch the ID of a container at any point in time. Say you want to get the short ID of the last container that was made, you can use this:

```shell
CID=$(docker ps --latest --quiet)
echo $CID

# or with short-form flags
CID=$(docker ps -l -q)
echo $CID
```

But even though truncation helps, these container IDs are rarely easy to read or remember. For this reason, Docker also generates human-readable names for each container.

The standard way to name a container is to use a personal adjective, an underscore, and the last name of a famous scientist, engineer, creator, or other thought leader.

Generated names are `compassionate_swartz`, `hungry_goodall`, and `eloquent_turing`, to name a few. These seem to be just right for reading and remembering.

In the docker tool itself, you can always use `docker ps` to get the names that people can understand. Identifying containers can be difficult, but Docker provides tools like ID generation and name assignment features to assist you in managing and resolving this challenge.

## Container States and Dependencies

Given what we discussed in the previous section, we can setup a script that launches a new `nginx` and some imaginary `mailmonkey` and `agent_smith` images. The mailmonkey image is responsible for sending emails, while agent_smith ensures that everything is running correctly.

```shell
MAILMONKEY_CID = $(docker run -d mailmonkey)
NGINX_CID = $(docker create nginx)
AGENTSMITH_CID = $(docker create --link $NGINX:webserver \
	--link $MAILMONKEY_CID:mailman agent_smith)
```

We can use `docker ps` to see what images are created, but, neither `agent_smith` nor `nginx` are listed. These containers are in a stopped state, and `docker ps` only displays running containers by default.

Considering the status column reported by `docker ps`, you can construct the following state transition diagram:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/be2jty3ncphgiqw0k3bk.png)

To confirm that the containers are created, you can use the 'docker ps -a' command. To start the images, we need first to start the `nginx` container, and then the `agent_smith` container, since `agent_smith` has a dependency on `nginx`.

```shell
docker start $NGINX_CID
docker start $AGENTSMITH_CID
```

Understanding the underlying mechanics makes this concept clearer. The link mechanism injects IP addresses into dependent containers, and containers that aren’t running don’t have IP addresses. If you tried to start a container that has a dependency on a container that isn’t running, Docker wouldn’t have an IP address to inject.

Whether you use `docker run` or `docker create`, it is crucial to start the containers in the reverse order of their dependency chain. This highlights that it is impossible to create circular dependencies using Docker container relationships.

## Conclusion
In conclusion, Docker offers a robust and flexible system for managing container identification and dependencies. By understanding and leveraging Docker’s unique identifiers and dependency management, we can efficiently navigate potential conflicts and streamline our container operations. 

Docker’s tools and features are designed to help you manage and resolve challenges effectively. 

Remember, the key to mastering Docker lies in understanding its underlying mechanics and using them to your advantage. As we continue to explore Docker, we’ll delve into more advanced topics like volumes and further explore the world of container states and dependencies. 

Stay tuned for more insights and happy Dockering!