The Power in a Name

My full name is Mark Allen Miller. You can find my profile on LinkedIn under my full name https://www.linkedin.com/in/markallenmiller/. I went to college with two other Mark Millers. One of them also had the same middle initial as me so my name is not the most unique name in the world. My dad’s name is Siegfried Miller. At the age of 18, because he could “change the world”, he changed his last name from Mueller to Miller and yep, he doesn’t have a middle name. My grandfather’s name is Karl Mueller. His Austrian surname, prior to immigrating to the US in 1950, was Müller with an umlaut which is a mark ( ¨ ) used over a vowel to indicate a different vowel quality. Interesting trivia you might say, but what does this have to do with Docker?

Well, Docker originally had the name dotCloud. According to wikipedia “Docker represents an evolution of dotCloud’s proprietary technology, which is itself built on earlier open-source projects such as Cloudlets.” I had never even heard of Cloudlets until I wrote this blog.

Docker containers have names also. These names give us humans something a little more interesting to work with instead of the typical container id such as 648f7f486b24. The name of a container can be used to identify a running instance of an image, but it can also be used in most commands in place of the container id.

Container Names

To start a container with a specific name you can use the --name parameter to the docker run command which will assign a name to the container. You can then stop the container by its name. Let’s use tommy for our example. Once a container has been named, whether it is running or not, you cannot start another container with the same name. You will have to remove the original tommy container first before launching it again. When a container is not running you can start it up again using its name. In the following examples I will be utilizing a current version of the Apache Tomcat image running in JRE 11.

$ docker run -d --name tommy tomcat:9-jre11
01ade0d133f70b2458087097409d8423387ea300b3316f69ad809ff6a997be7f

$ docker stop Tommy
tommy

$ docker run -d --name tommy tomcat:9-jre11
docker: Error response from daemon: Conflict. The container name "/tommy" is already in use by container "52650ae6e461a55871654792c480f71df0731f84008b38b86b89a9cd01e0e832". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.

$ docker start tommy
tommy

Well, it is running but I cannot do anything useful to it since I forgot to map the ports to my local host. So let’s remove the running container and start it up again. Finally, let’s prove its actually serving up static content.

$ docker rm -f tommy
tommy

$ docker run -d --name tommy --publish 8080:8080 tomcat:9-jre11
37d67ee4ba4cf5d59de25a27b915ce138289f40665aa1455886c307e249876fb

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
f5ab0e5dc91f        tomcat:9-jre11      "catalina.sh run"   4 seconds ago       Up 3 seconds        0.0.0.0:8080->8080/tcp   tommy

$ curl http://localhost:8080

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/9.0.19</title>
...

Container Hostname

Another type of name that is important to many applications is the hostname. The docker run command supports a --hostname parameter. The --hostname flag only changes the hostname inside your container. This may be needed if your application expects a specific value for the hostname. It does not change DNS outside of docker, nor does it change the networking isolation, so it will not allow others to connect to the container with that name. This means that the users outside of the container will not be able to resolve to the hostname just by setting that value via the --hostname on the container.

The default hostname of the container is its container id. But we want to change that to something more meaningful such as tomhost.

$ docker exec -it tommy hostname
f5ab0e5dc91f

$ docker rm -f tommy
tommy

$ docker run -d --name tommy --hostname tomhost --publish 8080:8080 tomcat:9-jre11
57913b4ca9e22e12ecc3125db7436c4a99515fe66c47e25e5f8a5f17751b2207

$ docker exec -it tommy hostname
tomhost

Container Network Name

You can use the container name (or the container’s id) to connect from one container to another container using docker’s embedded DNS as long as both containers share the same network and that network is not the default bridge network.

Let’s create a network tomnet and attach it to my already running tomcat container. Then let’s launch another container and curl back to the tomcat server using its name tommy.

$ docker network create tomnet
dab5175522b28b94baa6949ab1c9545be042f62a6734f5062817b643865d07c4

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
b27b0c0a82d6        bridge              bridge              local
39d4efbc37bc        docker_gwbridge     bridge              local
0990ed079c58        host                host                local
v6s1e7qyo419        ingress             overlay             swarm
5456492e4c0f        none                null                local
dab5175522b2        tomnet              bridge              local

$ docker network connect tomnet tommy

$ docker run -it --network tomnet nicolaka/netshoot curl http://tommy:8080

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/9.0.19</title>
...

Docker Service Names

The docker run command simply starts a single container. A docker service create command works with the Swarm orchestrator to provision a service. A service is a declaration of the state of your particular application. Services are really just “containers in production.” A service references a single image, but it contains the declared state of the way that image runs. That declarative model of a service defines things like what ports it should use, how many container instances should run, where the services should run, any resource reservations and limitations, and so on. And, of course, services have names.

If I create a service without a name, one will be assigned by docker. In the example below you can see the service name of blissful_meninsky.

$ docker service create redis:3.0.6
5qxktvdvweir2ncb3sllpl9h7
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

$ docker service ls
ID                  NAME                   MODE                REPLICAS            IMAGE               PORTS
5qxktvdvweir        blissful_meninsky      replicated          1/1                 redis:3.0.6         

By specifying the --name parameter I can name my service. Here I am creating a service with 3 container instances. You can see the service named myredis.

$ docker service create --name myredis --replicas 3 redis:3.0.6
p34u8bzc0l85eg9mvxjsd20ty
overall progress: 3 out of 3 tasks 
1/3: running   [==================================================>] 
2/3: running   [==================================================>] 
3/3: running   [==================================================>] 
verify: Service converged 

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
5qxktvdvweir        blissful_meninsky   replicated          1/1                 redis:3.0.6         
p34u8bzc0l85        myredis             replicated          3/3                 redis:3.0.6         

A service is composed of tasks. Each task represents the state that a container should run under. I can show the tasks of a service with the following.

$ docker service ps myredis
ID                  NAME                IMAGE               NODE                    DESIRED STATE       CURRENT STATE            ERROR               PORTS
k58qnaq64ch8        myredis.1           redis:3.0.6         linuxkit-025000000001   Running             Running 30 seconds ago                       
shsdutogi38q        myredis.2           redis:3.0.6         linuxkit-025000000001   Running             Running 31 seconds ago                       
iw5zmiteavc6        myredis.3           redis:3.0.6         linuxkit-025000000001   Running             Running 30 seconds ago                       

Notice that the tasks have names too. Under the NAME column you can see the three instance names in the form of <service-name>.<slot-number>.

The containers can be viewed with the docker ps command. Notice how the containers have names in the form of <service-name>.<slot-number>.<task-id>.

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
cf67de83161a        redis:3.0.6         "/entrypoint.sh redi…"   5 minutes ago       Up 5 minutes        6379/tcp            myredis.1.k58qnaq64ch8bey5epoubxcls
7da623fb36f5        redis:3.0.6         "/entrypoint.sh redi…"   5 minutes ago       Up 5 minutes        6379/tcp            myredis.3.iw5zmiteavc67t8fr9ag4je12
8da70efc4a37        redis:3.0.6         "/entrypoint.sh redi…"   5 minutes ago       Up 5 minutes        6379/tcp            myredis.2.shsdutogi38qh1cbghlqpyudp
8f0e46e42f47        redis:3.0.6         "/entrypoint.sh redi…"   9 minutes ago       Up 9 minutes        6379/tcp            blissful_meninsky.1.hxqqsmi4qcuvq30xuqhmzslg0

Within a service you can communicate to another service using its (you guessed it) service name. First, we will create new network. Second, update the myredis service to use that network. Third, we will exec into a container under the myclient service and test that we can access the redis service by the name myredis.

$ docker network create --driver overlay redisnet
krz71c4bvcjwn5wd14yaf7p6g

$ docker service update --network-add redisnet myredis
myredis
overall progress: 3 out of 3 tasks 
1/3: running   [==================================================>] 
2/3: running   [==================================================>] 
3/3: running   [==================================================>] 
verify: Service converged 

$ docker service create --name myclient --network redisnet nicolaka/netshoot tail -f /dev/null
ew4tcu3j9wb1opb8y1islvbq3
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

$ docker ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS               NAMES
a9540e066eb3        nicolaka/netshoot:latest   "tail -f /dev/null"      5 minutes ago       Up 5 minutes                            myclient.1.la5gq78ljem9yuhqpad19kn7x
8abc7c5309ad        redis:3.0.6                "/entrypoint.sh redi…"   9 minutes ago       Up 9 minutes        6379/tcp            myredis.2.wu8v29hiww95sua71ynnrloh4
108222757e5a        redis:3.0.6                "/entrypoint.sh redi…"   9 minutes ago       Up 9 minutes        6379/tcp            myredis.1.h41a2fvznncfjf27ncxompziu
da600a22e9ea        redis:3.0.6                "/entrypoint.sh redi…"   10 minutes ago      Up 9 minutes        6379/tcp            myredis.3.6rcpzblkc353scjpqo1ejwk21
8f0e46e42f47        redis:3.0.6                "/entrypoint.sh redi…"   30 minutes ago      Up 30 minutes       6379/tcp            blissful_meninsky.1.hxqqsmi4qcuvq30xuqhmzslg0

$ docker exec -it a9540e066eb3 sh
/ # echo 'PING' | nc myredis 6379
+PONG
^C

Great! We sent PING and receive a response of PONG from the myredis service.

If we stay logged into the myclient container, we can use the dig command to lookup some interesting things about the service.

/ # dig myredis

; <<>> DiG 9.12.3-P1 <<>> myredis
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59798
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;myredis.            IN  A

;; ANSWER SECTION:
myredis.        600 IN  A   10.0.0.2

;; Query time: 1 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Wed Apr 24 03:34:44 UTC 2019
;; MSG SIZE  rcvd: 48


/ # dig tasks.myredis

; <<>> DiG 9.12.3-P1 <<>> tasks.myredis
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 19633
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;tasks.myredis.            IN  A

;; ANSWER SECTION:
tasks.myredis.        600 IN  A   10.0.0.6
tasks.myredis.        600 IN  A   10.0.0.5
tasks.myredis.        600 IN  A   10.0.0.3

;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Wed Apr 24 03:34:53 UTC 2019
;; MSG SIZE  rcvd: 118

The first dig command results in the virtual IP of the myredis service which is 10.0.0.2. That IP address is on the redisnet network we created earlier.

The second dig command against the name tasks.myredis results in three IP addresses which represent the IP address of each unique container that sits behind the VIP.

So, the interesting thing here is that docker maintains the VIP and round-robins requests amongst the live containers. So, if one container goes down then the other two containers are routed any inbound requests.

Docker Stack Names

A stack is a group of interrelated services that share common networks, volumes, configurations, and secrets. A single stack is capable of defining and coordinating the functionality of an entire application of numerous micro-services. Although, multiple stack deployments may suite a complex micro-services application.

And a docker stack has a name. The name is mandatory for the stack deployment. The services get deployed with the stack name prepended to the service name.

The following stack file named docker-stack.yml will mimic the previous service and network deployments.

$ cat redis-stack.yml 
version: "3.7"

services:
  myredis: 
    image: redis
    networks:
      rednet:
    deploy:
      replicas: 3

  myclient:
    image: nicolaka/netshoot
    command: tail -f /dev/null
    networks:
      rednet:

networks:
  rednet:

Now to deploy the stack.

$ docker stack deploy -c redis-stack.yml marks
Creating network marks_rednet
Creating service marks_myredis
Creating service marks_myclient

$ docker stack ls
NAME                SERVICES            ORCHESTRATOR
marks               2                   Swarm

$ docker stack ps marks
ID                  NAME                IMAGE                      NODE                    DESIRED STATE       CURRENT STATE                ERROR               PORTS
xebv9ws2kbo5        marks_myclient.1    nicolaka/netshoot:latest   linuxkit-025000000001   Running             Running about a minute ago                       
epldn8afn1iq        marks_myredis.1     redis:latest               linuxkit-025000000001   Running             Running about a minute ago                       
jlzpn5f34g4d        marks_myredis.2     redis:latest               linuxkit-025000000001   Running             Running about a minute ago                       
is7gaa8ra6rm        marks_myredis.3     redis:latest               linuxkit-025000000001   Running             Running about a minute ago                       

$ docker service ls
ID                  NAME                MODE                REPLICAS            IMAGE                      PORTS
v5njxpd1iq9q        marks_myclient      replicated          1/1                 nicolaka/netshoot:latest   
yfoe0o30ogzt        marks_myredis       replicated          3/3                 redis:latest               

$ docker ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED              STATUS              PORTS               NAMES
bb2cf90a6379        redis:latest               "docker-entrypoint.s…"   About a minute ago   Up About a minute   6379/tcp            marks_myredis.2.jlzpn5f34g4dci4239emypnlt
d64d7af977c1        redis:latest               "docker-entrypoint.s…"   About a minute ago   Up About a minute   6379/tcp            marks_myredis.1.epldn8afn1iqwj01bp4ispp6k
7944e3315025        redis:latest               "docker-entrypoint.s…"   About a minute ago   Up About a minute   6379/tcp            marks_myredis.3.is7gaa8ra6rmef9u6k3r9jv5e
968e7f88fa05        nicolaka/netshoot:latest   "tail -f /dev/null"      About a minute ago   Up About a minute                       marks_myclient.1.xebv9ws2kbo5x9de1kvq4rk7q

Notice how the service names and container names all have the stack name prepended. They are the same thing as if you had created the service using the docker service create command. So everything else I have told you regarding names still applies.

Even the part about being able to set your containers hostname can be done using a docker stack file.

version: "3.4"

services:
  nginx:
    image: nginx
    hostname: '{{.Node.Hostname}}'

This stack file uses a filter to set the hostname. You can combine any static text along with the dynamic filter to specify a name. The .Node has several other values you can specify. Reference docker documentation for Service Constraints and Services Templates

Now let’s deploy the stack.

$ docker stack deploy -c nginx-stack.yml mynginx
Creating network mynginx_default
Creating service mynginx_nginx

$ docker stack ps mynginx
ID                  NAME                IMAGE               NODE                    DESIRED STATE       CURRENT STATE              ERROR               PORTS
jzwvgzofh3xc        mynginx_nginx.1     nginx:latest        linuxkit-025000000001   Running             Preparing 11 seconds ago                       

$ docker ps
CONTAINER ID        IMAGE                      COMMAND                  CREATED             STATUS              PORTS               NAMES
ecb4b62b0fa2        nginx:latest               "nginx -g 'daemon of…"   3 seconds ago       Up 2 seconds        80/tcp              mynginx_nginx.1.jzwvgzofh3xcca7ahuc0brhb8
bb2cf90a6379        redis:latest               "docker-entrypoint.s…"   10 minutes ago      Up 10 minutes       6379/tcp            marks_myredis.2.jlzpn5f34g4dci4239emypnlt
d64d7af977c1        redis:latest               "docker-entrypoint.s…"   10 minutes ago      Up 10 minutes       6379/tcp            marks_myredis.1.epldn8afn1iqwj01bp4ispp6k
7944e3315025        redis:latest               "docker-entrypoint.s…"   10 minutes ago      Up 10 minutes       6379/tcp            marks_myredis.3.is7gaa8ra6rmef9u6k3r9jv5e
968e7f88fa05        nicolaka/netshoot:latest   "tail -f /dev/null"      11 minutes ago      Up 11 minutes                           marks_myclient.1.xebv9ws2kbo5x9de1kvq4rk7q

$ docker exec -it ecb4b62b0fa2 sh
# hostname
linuxkit-025000000001

And the output of this container’s hostname command is linuxkit-025000000001 which is the name assigned on my MacBook in the Docker for Mac installation.

Summary

Hopefully we have learned a little about containers, services, and stacks. And specifically about how names play an important part in the Docker platform by giving us the power to manipulate containers, services, and stacks.  The advantage of using names is that you don’t have to remember long object IDs.

I have also shared some history of my family’s names and of Dockers former names.

The company I work for has a new name as well. We are Capstone IT. Formerly Capstone Consulting, we changed our name to capture more of the spirit of what we do. We specialize in IT Solutions and Staffing. To find out more about us, visit us at https://capstonec.com.

Mark Miller
Solutions Architect
Docker Accredited Consultant