CodeGym /Kursy /Docker for Spring /localhost and service name in Docker Compose

localhost and service name in Docker Compose

Docker for Spring
Poziom 16, Lekcja 0
Dostępny

1. Introduction

When the application was the only container, almost any addressing looked “logical”: you exposed the port and went to http://localhost:8080. But as soon as a second container appears, the brain, by inertia, keeps living in a world where “everything is on my laptop.” This is exactly where a beginner most often writes jdbc:postgresql://localhost:5432/... and is sincerely surprised why the database is “not visible,” even though the PostgreSQL container seems to be running.

The main idea of this lecture is simple: localhost is not “my computer in general,” but “the current machine/container where I am standing”. In a Compose setup, you are inside the app container, while PostgreSQL lives in the neighboring postgres container. That means the address should not be a loopback localhost, but the service name.

2. Two different “worlds”: the host machine and the container

It is very convenient, and very dangerous, to think that a container is a “small VM,” and therefore localhost is somehow “localhost everywhere.” Unfortunately, no. For our practical level, it is enough to accept this: the host machine and each container have their own network “room”, and inside that room the word localhost always means “myself,” not “the entire apartment.”

So we have two different contexts, and they get mixed up all the time:

  • Host machine (your laptop / workstation). Here localhost is your real computer.
  • Container app. Here localhost is the app container.
  • Container postgres. Here localhost is the postgres container.

Compose makes containers able to communicate over the network, but that does not cancel the fact that localhost inside each container remains selfish and points to itself.

To fix the picture in your mind, you can imagine the following diagram:

flowchart LR
  %% Host -> container: access from the laptop to the published port
  %% App -> Pg: internal Compose network (localhost is almost always a mistake here)
  Host["Host machine
localhost"] -->|port published| App["container: app
localhost inside app"] Host -->|port published| Pg["container: postgres
localhost inside postgres"] App -->|internal Compose network| Pg

The arrow Host -> App is your browser/curl on the machine. The arrow App -> Pg is what the application does inside the Compose network. And in that second arrow, localhost is almost always wrong.

3. The meaning of localhost inside a container

When you write localhost in any network address, it usually resolves to 127.0.0.1 (loopback). In everyday terms, that means “connect to myself.” On the host machine, it means “connect to the process on my laptop.” In a container, it means “connect to the process inside this same container.” A container is not telepathic: it cannot guess that you meant the neighboring container.

Let us look at a typical incorrect fragment of compose.yaml (this is exactly how people do it by habit):

services:
  app:
    environment:
      # ❌ localhost inside the app container means "this same app container", not postgres
      SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/${POSTGRES_DB}

Why is this bad? Because Spring Boot inside the app container honestly tries to find PostgreSQL inside the app container on port 5432. But inside app you have a Java process, not a database. The result usually looks like “connection refused” (and sometimes even more amusingly, if something inside the container happens to be listening on that port).

If you ran PostgreSQL on your host machine (not in Compose) and launched the application locally, then jdbc:postgresql://localhost:5432/... would have been a normal solution. But in a Compose setup, PostgreSQL is already not a process on the host machine, but a separate container.

4. Service name as the host: built-in DNS in Docker Compose

To keep containers from looking for each other “by photo” (IP addresses), Docker Compose provides a very practical feature: service names become DNS names inside the Compose network. That is, if compose.yaml has a postgres service, then from the app container you can connect to the host postgres — and Docker will route the traffic to the right container automatically.

At minimum, it looks like this: we tell the application that the database host is postgres, not localhost.

services:
  app:
    environment:
      # ✅ Inside the Compose network, use the service name (postgres) and the container port (5432)
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}

  postgres:
    image: postgres

Two ideas are important here.

First, postgres is not “some magical domain,” but exactly the service name in YAML. Compose brings up a network for the project and adds DNS to it, which knows: “if someone asks for postgres, route them to the container of the postgres service.”

Second, this approach scales. Today you have postgres; tomorrow redis will appear, then rabbitmq. And you will not rewrite IPs or hardcode localhost; you will simply address services by name, like normal participants in the setup.

If you want an analogy, localhost is a shout of “Hey, me!” that only the one shouting can hear. A service name is calling by name: “Pete, come here.” In the Compose network, “Pete” is found without guessing.

5. Ports: container and host are different things

With ports, beginners usually make exactly two common mistakes: first, they think port publishing is needed so containers can “see” each other; second, they plug the host port into the inter-container URL, although inside Compose the container port is used. Both points lead to unexpected errors, because everything looks almost correct, except for one number.

Here is a typical fragment where PostgreSQL is published to the host machine:

services:
  postgres:
    ports:
      - "55432:5432" # host:container (outside 55432, inside the network it is still 5432)

This means the following: on your host machine, PostgreSQL will be available as localhost:55432. But inside the Compose network, PostgreSQL still listens on 5432. Therefore, the app container must connect to postgres:5432, not postgres:55432, and certainly not localhost:55432.

In short: ports: is the “door to the outside” on the host, not a “wire between containers.”

6. Addressing: where we connect from and where we connect to

To avoid falling into this confusion every time, it helps to build a mental table of “who goes where” once, specifically in the context of our setup. It is like in an office: one entrance for employees, another for visitors, and if you mix them up, the security guard looks suspiciously at you. Compose behaves in much the same way: it is not evil, it just literally follows instructions.

Here is a handy cheat sheet for app + postgres:

Who makes the request Where to Address (host) Port Why
You on the host machine (browser/curl) the application localhost 8080 (or yours) Check the application API
The application in the app container PostgreSQL postgres 5432 Working database connection inside Compose
You on the host machine (psql, DBeaver) PostgreSQL (if needed) localhost 55432 (if published that way) Connect to the database from the outside

The key thing here is not to remember the numbers (they can change), but to remember the principle: between containers, service name + container port work. The host port appears only when you explicitly publish it outside.

7. Quick check: look for an extra localhost

When you assemble a setup, it is very tempting to trust your eyes: the containers are “Running,” so everything must be fine. But network and configuration are tricky things: a container may be “Running,” while the application inside it has already died from an unsuccessful attempt to connect to the database, or vice versa — the application is alive, but it is running in-memory mode because you accidentally pointed it to the wrong place. So it is useful to have a short checklist for “did I mix up the addresses.”

The simplest way is to literally look for the connection string in compose.yaml and make sure there is no localhost where it should not be:

services:
  app:
    environment:
      # If you see localhost here, you are almost certainly on the wrong branch of reality
      # Correct reference: service name (postgres) + container port (5432)
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}

If you want a bit more “reality palpation” through the CLI, you can ask Compose to show the environment inside the app container:

# Execute the command inside the app container
docker compose exec app env | grep SPRING_DATASOURCE_URL
# Verify: the host should be postgres, and the port should be 5432 (container port)
# SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/catalog

This command is good because it does not require understanding Spring, JPA, or magic. It simply confirms that the value you expect really made it into the container and did not “shift” because of a typo, quotation marks, or incorrect variable substitution.

8. Typical mistakes in addressing inside Compose

Mistake #1: localhost in SPRING_DATASOURCE_URL “out of habit”.
This is the most common scenario, because in a local run without Docker, that is usually how it works. But inside a container, localhost means “me,” that is, the app container. PostgreSQL lives in a different container, so the address must be the service name, for example postgres.

Mistake #2: confusing the host port and the container port.
When you write ports: "55432:5432", it is easy to start thinking that “PostgreSQL is now on 55432.” For the host machine — yes; for other containers — no. The app container should connect to postgres:5432. If you accidentally set postgres:55432, you will get a connection error, although from the outside (from the host) everything will look alive.

Mistake #3: believing that publishing a port “fixes” inter-container communication.
Port publishing (ports) is needed so that you can reach the service from the host machine. For communication between app -> postgres, it is not required at all. Containers in the same Compose network can see each other even without publishing. So “let us open the port and everything will work” is often treating the wrong disease.

Mistake #4: trying to fix a networking problem by changing Java code.
When Spring Boot writes something scary in the log about Connection refused, the hand reaches for “well, let me change the URL in code and do if (docker) ....” In our course, that is considered an anti-pattern: the environment is changed through configuration and Compose, not through conditional branches in code. In most cases, you simply specified the wrong host or port.

Mistake #5: relying on container_name as the “true name” of the container.
Sometimes people want to fix container_name: my-postgres and use it as a host. Technically, this may work, but you lose Compose’s main advantage: the service name as part of the declarative model. It is much more robust to use the service name itself (postgres), because it is logical, readable, and does not make your setup manual and fragile.

1
Zadanie
Docker for Spring, poziom 16, lekcja 0
Niedostępne
Poprawa hosta JDBC w Compose
Poprawa hosta JDBC w Compose
1
Zadanie
Docker for Spring, poziom 16, lekcja 0
Niedostępne
Poprawa portu połączenia między kontenerami
Poprawa portu połączenia między kontenerami
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION