How to connect docker compose services by name (or alias)

Docker Compose is a fantastic tool for Docker container orchestration locally, but it does not provide good examples for how to connect your containers together once they are running. Something I will explain in this article.

Below is an examples of a docker-compose.yml file which defines (1) some kind of database service, (2) some kind of backend API services, and (3) some kind of front-end client service.

# docker-compose.yml
version: "3"
services:
our-database-service:
image: some-db-image

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

Starting with our-api-service, we notice that PORT=3000 is part of its command argument. This PORT=3000 is the running port for our API code, which is the internal port that our API code is listening to internally to its container. In a similar manner with our-client-service, we also added --port 3000 to its command argument so that it too has a running port of 3000 inside its own, separate container. This means that both our API and client have a running port of 3000 . Lastly, I want to point out the additional ports argument for our-client-service.

By default Docker Compose spins up containers on our local computer but does not let us access them with our browser through localhost. For example, even though both our-client-service and our-api-service both have a running port of 3000, if we visit http://localhost:3000 we will see a blank page. This is because we need to publish our running ports to our local computer through the ports argument. In the case of our-client-service, we are publishing our running port 3000 to http://localhost:8080 on our local computer. Now, with the our distinction between a running port versus a published port, we can connect our services together.

Pivoting back to our running port of 3000 for our-client-service and our-api-services. We can connect our services together by using the below format in our code:

http://[service-name]:[running-port]/

As an example, if our-client-service wanted to fetch some sort of data from an endpoint provided by our-api-service, we would fetch that data like such:

data = fetch('http://our-api-service:3000/my-data.json')

Similarly, if we had given our-api-service a network alias in Docker Compose, we would do something like

http://[service-name]:[running-port]/

inside our code.

Since we are now familiar with running ports, published ports, and the URL structure to connect our services together, I now want to give a more holistic example.

Below is the same docker-compose.yml file we initially started with, but with a few more details filled out.

# docker-compose.yml
version: "3"
services:
our-database-service:
image: postgres:14-alpine
our-api-service:
image: some-api-image
depends_on:
- our-database-service
environment:
DATABASE_URL: http://database:5432/
command: run-my-api PORT=3000 HOST=0.0.0.0

our-client-service:
image: some-client-image
depends_on:
- our-api-service
environment:
API_URL: http://our-api-service:3000/
command: run-my-client --port 3000 --host 0.0.0.0
ports:
- 8080:3000

Browser-side pitfalls

Unfortunately, Docker Compose can only connect services together if the code being running inside of its spun up container. For backend services this should never be an issue, but for client-side rendered JavaScript applications this can be an issue. JavaScript running is a user’s browser means it is not running in side its container. For example, if we wrote a function like

async getDataFromApi() {
const response = await fetch('http://our-api-service:3000/my-data.json')
return response.data
}

If we ran this function in a Node backend Docker Compose would hook into our-api-service and return our expected data. However, if this function was run in our own browser we would get an

Unable to resolve "our-api-services:3000"

error. To work around this, we can either (1) publish our-api-service to our local computer just like our-client-service or (2) use some kind of proxy services or library that only runs inside its container. An example of publishing our-api-service to localhost would look something like this:

# docker-compose.yml
version: "3"
services:
our-database-service:
image: postgres:14-alpine
our-api-service:
image: some-api-image
depends_on:
- our-database-service
environment:
DATABASE_URL: http://database:5432/
command: run-my-api PORT=3000 HOST=0.0.0.0
ports:
- 8081:3000
our-client-service:
image: some-client-image
depends_on:
- our-api-service
environment:
API_URL: http://localhost:8081/
command: run-my-client --port 3000 --host 0.0.0.0
ports:
- 8080:3000

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Erik A. Ekberg

Software engineer with a background in human psychology and data analytics who affords both customer and engineer delight through Agile software architectures.