Recently I had faced an apparently easy to solve problem that however
became little bit trickier in the end. Imagine an application that connects
to the database. Nothing super special fancy here. The docker-compose.yml
file could for instance look something like this:
version: '3.2'
services:
db:
image: postgresql:12
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_NON_ROOT_USER: ${POSTGRES_USER}
POSTGRES_NON_ROOT_USER_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ${STORAGE_PATH}/${INSTANCE_NAME}/db:/var/lib/postgresql/data
app:
image: myapp:1
depends_on:
- db
environment:
INSTANCE_NAME: ${INSTANCE_NAME}
DB_HOST: db
DB_NAME: ${POSTGRES_DB}
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
links:
- db:db
volumes:
- ${STORAGE_PATH}/${INSTANCE_NAME}/data:/srv/web/data
ports:
- "${INSTANCE_PORT}:8080"
We see that the app
container is being run alongside the db
one. After
starting the service with docker-compose up -d
we would like to verify
that both containers are running and the ports they expose (or in case of
database hide). One of the way this could be achieved is to utilize the
--format
argument of the docker ps
command as follows:
docker ps --format "table {{.Names}}\t{{.Ports}}"
The result of this command could look like this:
NAMES PORTS
app_1 0.0.0.0:8080->8080/tcp
db_1 5432/tcp
Adding more daemons
One of the advantages of the Docker ecosystem is that you can scale the
services horizontally. So if we wanted to run another instance of the
service on the same host, we could just copy the service directory and
tweak the variables in the .env
file. Two variables that must be changed
are INSTANCE_NAME
and specially INSTANCE_PORT
. Changing port is needed
because otherwise the containers would like to bind to the same port, which
is obviously not what we want.
This daemon could be started the same way as the previous one. To observe
the running containers we also tweak our docker ps
command a little bit
to differentiate between the containers belonging to the different
services:
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"
Output could be similar to this:
CONTAINER ID NAMES PORTS
0cd27fa1c363 app_1 0.0.0.0:8080->8080/tcp
bd2832c8f6fc app_1 0.0.0.0:8081->8080/tcp
057a4002ce66 db_1 5432/tcp
347a51256c16 db_1 5432/tcp
Note that the apps are bound to the ports 8080 an 8081 on the host
system.afsdsfdThere is one big problem in this approach. Apart from the
container ID however, depending on other aspects of the container
configuration, there could be nothing easily accessible in the whole
docker ps
output that would help us really differentiate in a human
readable form what are those containers all about.
Labels to the rescue!
One way around this problem is to use so called labels. Labels allow to set metadata to most Docker objects, including:
- Images
- Containers
- Local daemons
- Volumes
- Networks
- Swarm nodes
- Swarm services
Citing the documentation, labels can be used to to organize your images, record licensing information, annotate relationships between containers, volumes, and networks, or in any way that makes sense for your business or application.
You can easily specify labelafsdsfdduring runtime, but to make it more
persistent inside Dockerfile via LABEL
instruction:
LABEL instance=red # or instance=blue
After assigning the labels and altering our format parameter to include the labels (which are hidden by default):
docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}\t{{.Labels}}"
The output can look like this:
CONTAINER ID NAMES PORTS LABELS
0cd27fa1c363 app_1 0.0.0.0:8080->8080/tcp instance=red
bd2832c8f6fc app_1 0.0.0.0:8081->8080/tcp instance=blue
057a4002ce66 db_1 5432/tcp
347a51256c16 db_1 5432/tcp
Now this is a nice approach when the Dockerfile we use is under our control, but this is not always the case. I would argue that this is more of an exception than a norm.
Dockerfile is not my own
This issue was solved in Compose file version 3.3. ou can look up the details in its documentation. Adding labels into Compose file is more convenient when the service is distributed to you through it.
For the completes, the edited Compose file could look like this:
version: '3.3' # version bumped to 3.3 or higher
services:
db:
image: postgresql:12
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_NON_ROOT_USER: ${POSTGRES_USER}
POSTGRES_NON_ROOT_USER_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ${STORAGE_PATH}/${INSTANCE_NAME}/db:/var/lib/postgresql/data
app:
image: myapp:1
depends_on:
- db
environment:
INSTANCE_NAME: ${INSTANCE_NAME}
DB_HOST: db
DB_NAME: ${POSTGRES_DB}
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
links:
- db:db
volumes:
- ${STORAGE_PATH}/${INSTANCE_NAME}/data:/srv/web/data
ports:
- "${INSTANCE_PORT}:8080"
labels: # labels in Compose file instead of Dockerfile
- "instance-name": ${INSTANCE_NAME}
This way, when displaying labels via the docker ps
command used last
time, the instance name is also visible.