While connecting MinIO locally, I ran into a very common Docker networking confusion.
The MinIO web console worked from the browser at localhost:9001. It seemed reasonable that Laravel could also reach MinIO through localhost.
But inside the application container, that address points somewhere completely different.
Every container has its own localhost.
Once I separated those network contexts, the model became simple: use an exposed port from the host machine and the service name for container-to-container communication.
Why localhost:9001 Works in the Browser
Browser | | http://localhost:9001 vComputer running Docker | | published port 9001 vMinIO containerThe browser runs on the machine where Docker is running.
For the browser, localhost therefore means that host machine.
Docker exposes a container port to the host, so a request to localhost:9001 reaches the MinIO web console.
In this case, localhost is correct because the request originates outside the containers.
Inside a Container, localhost Means That Container Itself
Laravel container | | http://localhost:9000 vLaravel container itself MinIO container X The request neverreached MinIO.A container has its own network namespace.
If PHP inside a container opens http://localhost:9000, it looks for port 9000 inside that same container.
MinIO runs in another container, so the request never reaches it.
The problem is neither Laravel nor MinIO. The address simply points to the wrong network context.
127.0.0.1 Has the Same Meaning
Replacing localhost with 127.0.0.1 does not fix this problem.
Both addresses point back to the current network namespace.
Inside a container, that still means the same container rather than another Docker service.
Containers Reach Each Other by Service Name
Laravel container | | http://minio:9000 vDocker network | | service name: minio vMinIO containerIf both services are attached to the same Docker network, they can reach each other using service names.
For MinIO, the internal address in my case became:
http://minio:9000
Docker resolves the service name minio to the corresponding container.
I do not need to know its current internal IP address.
I Do Not Configure Services by Container IP
http://172.20.0.7:9000http://minio:9000A container internal IP may change when the container is recreated.
The service name is a much more stable boundary.
I therefore configure the application with minio rather than a hardcoded 172.x.x.x address.
The Same Service May Have Different Addresses Depending on the Caller
From the browser: http://localhost:9001 for the web console From the Laravel container: http://minio:9000 for the S3-compatible APIAt first this can look contradictory: MinIO appears to have two different addresses.
In reality, the service is the same. Only the path to it differs.
The host machine enters through an exposed Docker port, while another container uses the internal Docker network.
The addresses therefore do not conflict. They belong to different network contexts.
Container Ports and Host Ports Are Not Necessarily the Same
Host localhost:19001 | vDocker port mapping | vcontainer:9001Docker may expose an internal container port under a different port number on the host.
For example, the host may use 19001 while the container still listens on 9001.
Container-to-container communication normally uses the service internal port rather than the host-facing port.
I Test Connectivity from the Application Container
curl -i \ http://minio:9000/minio/health/liveIf Laravel needs to reach MinIO from the PHP container, I test exactly that route.
A working web console proves only that the path from the host to an exposed port is available.
It says nothing about communication between two containers.
A health check from the application environment is therefore a much more useful verification.
The Service Name Can Be Verified Separately
getent hosts minioIf minio cannot be resolved, the problem may exist before HTTP is involved.
The containers may not share a network, or the service may use another name.
I therefore separate the checks: first confirm name resolution, then confirm that the required port responds.
The Laravel Endpoint Must Match the Application Network Context
AWS_ENDPOINT=http://localhost:9000AWS_ENDPOINT=http://minio:9000AWS_USE_PATH_STYLE_ENDPOINT=trueIf Laravel runs inside Docker, its S3 endpoint must be reachable from that environment.
For local MinIO, I therefore use the service name minio.
This is not a special Laravel requirement. It follows from where PHP is actually running.
Works in the Browser Does Not Mean Works from the Container
This is the main practical mistake I now check first.
The browser, PHP container, queue worker, another service, and MinIO itself may all run in different network contexts.
An address that works in one place is not guaranteed to work in another.
Before changing Laravel configuration or debugging the S3 driver, I check where the request actually originates.
The Mental Model That Keeps This Clear
If the request starts on the host: localhost-> the host machine If the request starts in a container: localhost-> that container If a container needsanother container: service nameWhat I Check When One Container Cannot Reach Another Service
My short checklist:
- Where does the request actually originate?
- What does localhost mean in that environment?
- Is the target container running?
- Do both containers share a Docker network?
- What is the target service name?
- Can that name be resolved inside the container?
- Which internal port does the service listen on?
- Am I using a host-facing port where an internal port is required?
- Does the service respond when checked directly from the container?
localhost Always Depends on Where You Are
The localhost problem becomes much simpler once localhost is no longer treated as a universal address for the whole machine.
For the browser, localhost means the host machine.
For Laravel inside a container, localhost means the Laravel container.
For Laravel to reach MinIO in another container, it needs the MinIO service name on the shared Docker network.
I now answer "which address should I use?" by first asking "where does this request originate?".
Before changing ports or configuration, I first determine whose localhost I am actually using.



