Docker-Compose projects with identical service names
Use the Docker-Compose service name only if the service is only on the Docker-Compose project internal network.
If we have identical Docker-Compose projects with identical service names, connected by a Docker network, we must make sure that we access the proper service. Over a Docker network there are two ways we can access a service:
- By service name
- By container name
I have multiple Docker-Compose projects that are almost identical, each project is in its own directory and has its own environment.
I assumed that a service inside a Docker-Compose project would always use another service inside the same Docker-Compose project when using the service name (not the container name). And that it would produce an error, if it was not available. I was wrong. What actually will happen is that you may access the service with the same name in another Docker-Compose project.
As always, I am running this on Ubuntu 22.04.
Project setup
We have two directories with our (almost) identical projects.
├── my_app1
│ ├── docker-compose.yml
│ └── .env
├── my_app2
│ ├── docker-compose.yml
│ └── .env
The two 'docker-compose.yml' files are identical. The '.env' files are different, they contain only one environment variable, the 'COMPOSE_PROJECT_NAME'. This variable not only is used to create the container names, we also use it inside our containers to identify the Docker-Compose project.
We are using the 'nicolaka/netshoot' Docker image here. It has a lot of useful utilities, including a proper working 'netcat', 'nc', which we use to create listener daemons.
Here are the files:
# my_app1/.env
COMPOSE_PROJECT_NAME=my_app1
and
# my_app2/.env
COMPOSE_PROJECT_NAME=my_app2
and
# docker-compose.yml
version: '3.7'
x-service_defaults: &service_defaults
env_file:
- ./.env
restart: always
services:
app:
<< : *service_defaults
image: nicolaka/netshoot
command: bash -c "echo \"From ${COMPOSE_PROJECT_NAME} - app:\" | /usr/bin/nc -l 80"
networks:
- internal_network
- app_network
web:
<< : *service_defaults
image: nicolaka/netshoot
command: bash -c "echo \"From ${COMPOSE_PROJECT_NAME} - web:\" | /usr/bin/nc -l 81"
networks:
- internal_network
- app_network
networks:
internal_network:
app_network:
external:
name: my_app_network
Before starting, we create the external Docker network:
docker network create my_app_network
Running some checks
Open some terminals and start both projects by running this command in each directory:
docker-compose up
Check that the following containers have been created and are running:
my_app1_app_1
my_app1_web_1
my_app2_app_1
my_app2_web_1
Now in another terminal run:
docker run -it --network=my_app_network --rm busybox
Access a service in our containers using the container name:
telnet my_app_1_app_1 80
The response will be:
Connected to my_app1_app_1
From my_app1 - app:
This is working as expected. Type some text. This will be echo-ed. You can also check this in the terminal of the 'my_app1' Docker-Compose project.
Generating the error
First we enter the 'app' service of the 'my_app1' project:
docker exec -it my_app1_app_1 bash
Now let's access the 'web' service in the same Docker-Compose project, using the service name:
telnet web 81
The response is:
Connected to web
From my_app1 - web:
Perfect, this is what we expected.
Now let's make the 'web' service in the 'my_app1' project (temporarily) disappear. We do this by taking it down.
In another terminal, we go to the 'my_app1' directory and type:
docker-compose stop web
Now we access the 'web' service in the same project again:
telnet web 81
The response is:
Connected to web
From my_app2 - web:
Uh-oh. Now we are accessing 'web' service in 'my_app2' while we wanted to access the 'web' service in 'my_app1'. Note that there are (much) better, but also much more complex, ways to simulate this.
Solutions
There are two possible solutions here, depending on your situation:
Solution 1. If the 'web' service does not need to be external, we remove it from the external network
We do this for both projects (remember that the 'docker-compose.yml' files are identical):
web:
<< : *service_defaults
image: nicolaka/netshoot
command: bash -c "echo \"From ${COMPOSE_PROJECT_NAME} - web:\" | /usr/bin/nc -l 81"
networks:
- internal_network
#- app_network
Take the containers down and then up again, and stop the 'web' service of the 'my_app1' project again.
Now, if you try to access the 'web' service, see above:
telnet web 81
the response will be, after some time:
telnet: bad address 'web'
Good. We get an error message when the service is not available.
Solution 2. If the 'web' service also must be external, we always use the container name of the 'web' service when referring to it, even if we are in the same Docker-Compose project
This is not really a nice solution but there is no other way. If the 'app' service wants to access the 'web' service in the same Docker-Compose project, we can construct the container name using the 'COMPOSE_PROJECT_NAME' variable:
container_name = <COMPOSE_PROJECT_NAME>_<service name>_1
For the 'web' service in 'my_app1':
web_container_name = my_app1_web_1
Summary
I bounced into this, when going from one container to two containers. Strange things started happening, and data got mixed up between the containers. After locating the problem, I simulated it with the code above. Then I fixed it for my project. Lesson learned: Never assume.
Links / credits
Docker - Change pre-defined environment variables in Docker Compose
https://docs.docker.com/compose/environment-variables/envvars
nicolaka / netshoot
https://github.com/nicolaka/netshoot
Read more
Docker Docker-compose
Most viewed
- Using Python's pyOpenSSL to verify SSL certificates downloaded from a host
- Using PyInstaller and Cython to create a Python executable
- Reducing page response times of a Flask SQLAlchemy website
- Connect to a service on a Docker host from a Docker container
- SQLAlchemy: Using Cascade Deletes to delete related objects
- Using UUIDs instead of Integer Autoincrement Primary Keys with SQLAlchemy and MariaDb