The trap

localhost inside a container refers to that container’s own loopback, not the host and not any other container. Calling http://localhost:8086 from inside my-app to reach openfga will be refused.

How it actually works

Containers on the same Docker network get Docker’s internal DNS. Service names resolve to container IPs automatically.

services:
  openfga:
    ports:
      - "8086:8080"   # host:container
 
  my-app:
    # reaches openfga at http://openfga:8080, not localhost:8086

Port mappings (8086:8080) only apply to traffic from outside Docker — the host, a browser, curl on the host. Container-to-container traffic bypasses them entirely.

Rule of thumb

CallerURL
Host machinehttp://localhost:<host-port>
Another containerhttp://<service-name>:<container-port>