How to Connect Docker Compose Services To Each Other
One Docker Compose service can connect to another service through a URL like
http://{{service-name}}:{{listening-port}}/
where {{service-name}}
is the name of the service in the docker-compose.yml
file and the {{listening-port}}
is the port the application is listening to inside the container.
Using the below sketch of a docker-comopse.yml
file which defines three services: our-database-service
, our-api-service
, and our-client-service
# docker-compose.yml
version: "3"
services:
our-database-service:
image: some-db-image
environment:
PORT: 5432
our-api-service:
image: some-api-image
command: run-my-api PORT=3000 HOST=0.0.0.0
our-client-service:
image: some-client-image
command: run-my-client --port 3000 --host 0.0.0.0
ports:
- 8080:3000
our-api-service
can fetch data from our-database-service
through http://our-database-service:5432/
.
What is a Listening Port?
A listening port is the port that an application is accepting requests on.
Using our sketch of a docker-compose.yml
file from above, we see that our-database-service
is listening on port 5432
.
Alternatively, our-api-service
and our-client-service
are both listening on port 3000
as part of their command
arguments.
Even though our-api-service
and our-client-service
are listening to the same port, they are doing so in their own isolated containers. So there is no conflict.
However, a listening port is different than an exposed port.
What is an Exposed Port?
An exposed port is how Docker compose makes our-client-service
accessible at localhost:8080
, denoted by 8080:3000
in our docker-compose.yml
file.
Using the ports
argument with something like 8080:3000
tells Docker Compose to map the listening port 3000
to localhost:8080
.
Reviewing our docker-compose.yml
we see that our-client-service
is exposed on localhost:8080
but none of our other services are. So we can’t access them through localhost
.
What are Client-Side Pitfalls?
Docker Compose can only connect to other services if the request is being made from inside the container.
For backend services like our-database-service
and our-api-service
this isn’t an issue. But for our-client-service
connecting to other services may be a problem.
For example, if we wrote a function like
async getDataFromOurApiService = () => {
const res = await fetch('http://our-api-service:3000/my-data.json')
return res.data
}
and ran it server side, Docker Compose would correctly route the request to our-api-service
.
However, if we ran this function in a browser, at localhost:8080
for example, we would get an error like Unable to resolve "our-api-services:3000"
.
The reason this error occurs is because the browser has no awareness of Docker Compose and how it is handling these magic URLs: so it just throws an error.
To work around this, we either need to expose our-api-service
to localhost
just like we did our-client-service
or use some kind of proxy library that only ever runs inside the container.
If we exposed our-api-service
our docker-compose.yml
file might look something like this:
# docker-compose.yml
version: "3"
services:
our-database-service:
image: some-db-image
environment:
PORT: 5432
our-api-service:
image: some-api-image
command: run-my-api PORT=3000 HOST=0.0.0.0
ports:
- 5000:3000 # Expose service to locahost:5000
our-client-service:
image: some-client-image
command: run-my-client --port 3000 --host 0.0.0.0
ports:
- 8080:3000