Can't access localhost API from within docker container using host.docker.internal

Hi I am running docker from within WSL2, docker dekstop version 4.55.0.

I have a simple Spring boot API that is exposing prometheus endpoint. I can’t seem to curl it from within my docker container using host.docker.internal.

❯ docker run --rm -it --add-host=host.docker.internal:host-gateway curlimages/curl sh
~ $ curl http://host.docker.internal:8081/actuator/prometheus
curl: (7) Failed to connect to host.docker.internal port 8081 after 3 ms: Could not connect to server

However, when I ran the command

❯ ifconfig | grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}" | grep -v 127.0.0.1 | awk '{ print $2 }' | cut -f2 -d: | head -n1
172.31.230.230

I was able to curl it using this IP address.

❯ docker run --rm -it --add-host=host.docker.internal:host-gateway curlimages/curl sh
~ $ curl -v http://172.31.230.230:8081/actuator/prometheus
*   Trying 172.31.230.230:8081...
* Established connection to 172.31.230.230 (172.31.230.230 port 8081) from 172.17.0.2 port 34284
* using HTTP/1.x
> GET /actuator/prometheus HTTP/1.1
> Host: 172.31.230.230:8081
> User-Agent: curl/8.18.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200
< Content-Type: text/plain;version=0.0.4;charset=utf-8
< Content-Length: 24137
< Date: Thu, 08 Jan 2026 14:21:30 GMT
<
# HELP application_ready_time_seconds Time taken for the application to be ready to service requests
# TYPE application_ready_time_seconds gauge
application_ready_time_seconds{main_application_class="com.ecommerce.product.ProductApplication"} 14.495

Is there any reason as to why host.docker.internal wasn’t working?
The reason I ask is that I am running prometheus inside docker and was trying to scrape from host.docker.internal:8081 and it wasn’t working. After changing it to that IP address it worked. Why is that the case? Why didn’t host.docker.internal work.

Docker was developed as a Linux tool. Docker Desktop on Windows is usually running all containers inside a Linux VM. As far as I know host.docker.internal is provided by Docker Desktop automatically (doc):

host.docker.internal Resolves to the internal IP address of your host

Using --add-host=host.docker.internal:host-gateway is usually a workaround for Docker-CE on Linux, when not using Docker Desktop.

Running the container without --add-host=host.docker.internal:host-gateway on WSL I can still curl the API using curl -v http://172.31.230.230:8081/actuator/prometheus, but when I use host.docker.internal I get

~ $ curl -v http://host.docker.internal:8081/actuator/prometheus
* Host host.docker.internal:8081 was resolved.
* IPv6: (none)
* IPv4: 192.168.65.254
*   Trying 192.168.65.254:8081...
* connect to 192.168.65.254 port 8081 from 172.17.0.2 port 58662 failed: Connection refused
* Failed to connect to host.docker.internal port 8081 after 4 ms: Could not connect to server
* closing connection #0
curl: (7) Failed to connect to host.docker.internal port 8081 after 4 ms: Could not connect to server

host.docker.internal in Docker Desktop points to the localhost of the actual host. The IP you shared looks like the IP of WSL. If the service on port 8081 isn’t listening on localhost, only on the WSL IP, host.docker.internal will not help.

I rarely use WSL for this, so I am not sure about when and how ports are forwarded from Windows to WSL, but I think the service on port 8081 has to be available on the localhost of Windows.

Cany ou try

curl http://localhost:8081/actuator/prometheus

on Windows in Powershell? Or just try http://localhost:8081/actuator/prometheus in a web browser on Windows.

I can access the http://localhost:8081/actuator/prometheus in a web browser and get this from powershell:

StatusCode        : 200
StatusDescription :
Content           : # HELP application_ready_time_seconds Time taken for the application
                    to be ready to service requests
                    # TYPE application_ready_time_seconds gauge
                    application_ready_time_seconds{main_application_class="...
RawContent        : HTTP/1.1 200
                    Keep-Alive: timeout=60
                    Connection: keep-alive
                    Content-Length: 23600
                    Content-Type: text/plain;version=0.0.4;charset=utf-8
                    Date: Thu, 08 Jan 2026 18:54:07 GMT

                    # HELP application_re...
Forms             : {}
Headers           : {[Keep-Alive, timeout=60], [Connection, keep-alive], [Content-Length,
                    23600], [Content-Type, text/plain;version=0.0.4;charset=utf-8]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : mshtml.HTMLDocumentClass
RawContentLength  : 23600

Please share the output of netstat -p tcp -q | Select-String -Pattern ":8081", so we can see that the process actually binds 0.0.0.0:8081, and not just 127.0.0.1:8081.

This is the output I get from running it in powershell:

>netstat -p tcp -q | Select-String -Pattern ":8081"

  TCP    172.31.224.1:52946     172.31.230.230:8081    ESTABLISHED

I am surprised by the output. It only shows an active connection, but doesn’t show that port :8081 listens at all…

After discussing with @rimelek seems I was mistaken about how host.docker.internal works, and that it should work, even if the process only binds on 127.0.0.1:8081 on the host.

I cannot reproduce this issue. I can make host.docker.internal not work, but than the URL doesn’T work from the web browser either.

It could still help if you could tell us more about how you start that Sprint boot API with the prometheus endpoint. Is it running in a container to which you forward ports? Is it running directly on Windows or inside a WSL distribution? Do you know what IP addresses that is supposed to listen on? Can you find any configuration related to that if you didn’t set it?

The Spring Boot API is running inside WSL, it’s not running inside any container.I am not sure about the last point I’ve set it to run on port 8081.

Yes, but on what IP? I gues if you don’t know, it is on default setting. I don’t know what the default is, just assume it should be on all avalable IP addresses.

I am trying to figure out how it is possible that the endpoint works for you from a webbrowser on “localhost” but not when you are using host.docker.internal without --add-host.

I know you are using Docker Desktop, but have you also installed Docker inside the WSL distribution? Docker Desktop should override it, but I have no other ideas what could break host.docker.internal

I think it’s on default setting. I think I might have installed docker inside the WSL distribution. Running the command docker context ls gives the following output:

NAME            DESCRIPTION                               DOCKER ENDPOINT                             ERROR
default *       Current DOCKER_HOST based configuration   unix:///var/run/docker.sock      
desktop-linux   Docker Desktop                            npipe:////./pipe/dockerDesktopLinuxEngine

and also:

❯ which docker
/usr/bin/docker

Could this cause the issue?

That is just the client. The question is whether you installed the daemon or not. And if that is running. Running the following command should show if a Docker CE daemon is running in WSL:

sudo systemctl status docker
❯ sudo systemctl status docker
Unit docker.service could not be found.

So no daemon. Do you have anything on Windows with which you can run a service on any port? I used python3 on Windows like this:

python3 -m http.server --bind 127.0.0.1 8082

and in an other terminal on Windows

docker run --rm -it curlimages/curl http://host.docker.internal:8082

Just to see what works for you and what not.

1 Like
❯ python3 -m http.server --bind 127.0.0.1 8087
Serving HTTP on 127.0.0.1 port 8087 (http://127.0.0.1:8087/) ...
127.0.0.1 - - [08/Jan/2026 20:55:47] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [08/Jan/2026 20:56:05] "GET / HTTP/1.1" 200 -
❯ docker run --rm -it curlimages/curl http://host.docker.internal:8087
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href=".data/">.data/</a></li>
<li><a href="docker-compose.yaml">docker-compose.yaml</a></li>
<li><a href="grafana/">grafana/</a></li>
<li><a href="init.sql">init.sql</a></li>
<li><a href="logging/">logging/</a></li>
<li><a href="prometheus/">prometheus/</a></li>
</ul>
<hr>
</body>
</html

Strange it worked for this. It could be a spring boot related issue that isn’t mapping it properly.

Can you try the same, but now running the python http server inside the WSL distribution the same way you did on Windows?

The output above was the output from running it inside WSL

Then please, try running the python http server on Windows. That was my original recommendation

EDIT: On second thought, it might not be necessary, as your spring boot is also running in WSL.