When developing applications that involve both Docker containers and services running on your local machine, a common challenge is how to allow the containers to communicate with your localhost API. For example, you may have an API running on your machine and want to call it from a containerized service. This blog post explores several practical ways to solve this issue, covering multiple platforms such as Windows, macOS, and Linux.
Scenario Overview
Let’s assume:
- You have an API running on your localhost at
http://127.0.0.1:8000. - You need a Docker container to make HTTP requests to this API.
Here are several solutions based on your platform and setup.
Option 1: Use host.docker.internal (For Windows and macOS)
Docker provides a special DNS name host.docker.internal to access the host machine from inside the container. This approach is the simplest if you’re using Windows or macOS.
How to Use It:
- From inside the container, call the API like this:
curl http://host.docker.internal:8000/api/endpoint
Enter fullscreen mode
Exit fullscreen mode
- Make sure the port (
8000in this case) is open and your API is running.
Example response:
{ "message": "Hello from localhost!" }
Enter fullscreen mode
Exit fullscreen mode
Note: On Linux, host.docker.internal may not be available. If you’re on Linux, proceed to the next options.
Option 2: Use the Host Network (Linux Only)
On Linux, you can use the host network mode to give your container access to your localhost services.
Steps:
- Start your container with the
--network="host"option:
docker run --network="host" your-container
Enter fullscreen mode
Exit fullscreen mode
- Now, from inside the container, you can call your localhost API directly:
curl http://127.0.0.1:8000/api/endpoint
Enter fullscreen mode
Exit fullscreen mode
- The container will share the host’s network stack, meaning it can access anything available on
127.0.0.1.
Warning: This method grants the container full access to the host’s network, which may not be ideal for all environments.
Option 3: Use the Host IP Address
Another approach is to use the host machine’s IP address to reach the localhost services.
Steps:
- Find the IP address of your host machine:
ip addr show eth0 | grep inet | awk '{ print $2 }' | cut -d/ -f1
Enter fullscreen mode
Exit fullscreen mode
Example result:
192.168.1.100
Enter fullscreen mode
Exit fullscreen mode
- Inside the container, use this IP to call your localhost API:
curl http://192.168.1.100:8000/api/endpoint
Enter fullscreen mode
Exit fullscreen mode
This approach works across all platforms but requires knowing the host’s IP address, which may change over time.
Option 4: Create a Custom Docker Network
If you need a more controlled way to allow communication between your containers and localhost, you can use a custom network.
Steps:
- Create a new Docker network:
docker network create my_network
Enter fullscreen mode
Exit fullscreen mode
- Run your container in the new network:
docker run --network my_network -it your-container
Enter fullscreen mode
Exit fullscreen mode
- Bind your localhost API to 0.0.0.0 to make it accessible to other devices in the network:
php -S 0.0.0.0:8000
Enter fullscreen mode
Exit fullscreen mode
- Now, from the container, call your API using the host machine’s IP:
curl http://192.168.1.100:8000/api/endpoint
Enter fullscreen mode
Exit fullscreen mode
Option 5: Use Nginx as a Reverse Proxy
If you need a more advanced setup or plan to run multiple services, you can configure an Nginx reverse proxy.
Steps:
- Create an Nginx configuration file (
nginx.conf):
server {
listen 80;
location / {
proxy_pass http://host.docker.internal:8000;
}
}
Enter fullscreen mode
Exit fullscreen mode
- Build an Nginx container with this configuration:
docker run -d -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro -p 8080:80 nginx
Enter fullscreen mode
Exit fullscreen mode
- Now, you can access your localhost API via the Nginx proxy:
curl http://localhost:8080/api/endpoint
Enter fullscreen mode
Exit fullscreen mode
This method gives you more flexibility by routing requests through Nginx.
Conclusion
Communicating between Docker containers and localhost services can be tricky, especially when dealing with network isolation. However, using one of these methods will solve the problem:
-
host.docker.internal– The easiest solution on Mac and Windows. - Host Network Mode – Simple and effective for Linux.
- Host IP Address – A cross-platform option but requires IP management.
- Custom Docker Networks – For controlled networking setups.
- Nginx Proxy – Ideal for more complex routing needs.
Choose the solution that fits your environment and development needs best! With these approaches, you’ll be able to seamlessly connect your Docker containers with your localhost services, making development more efficient and smooth.
Let me know if this post helps or if you have other questions! 🚀
Photo by Philippe Oursel on Unsplash
Docker containers are isolated from the host machine, which means that they cannot access the host machine’s network by default. However, there are a ways to allow containers to access the host machine’s network, one of which is to use the host.docker.internal hostname.
host.docker.internal is a special hostname that resolves to the IP address of the Docker host machine. This means that containers can access the host machine’s network by connecting to host.docker.internal.
One common use case for using host.docker.internal is to allow a container to access a service ex: database that is running on the host machine. For example, you might have a container that is running a web application that needs to access a MySQL database that is running on the host machine as shown in the diagram below. By configuring web application to use host.docker.internal with port number , application can reach the database service.
Below example shows connecting to service running on host from inside the container on Docker desktop running on Windows
On Windows Docker installation accessing host.docker.internal is automatic, on Linux this requires adding --add-host command line option for docker run command .
docker run --rm -it –add-host=host.docker.internal:host-gateway <<container>>
Conclusion:
host.docker.internal is a useful tool for accessing services running on the host machine from inside a container. It is easy to use, portable, and reliable.
Reference
Docker Networking Documentation
Docker Run command line options
Время на прочтение5 мин
Количество просмотров36K
Докер под Windows — это постоянные приключения. То ему нужно обновить операционку, иначе последние версии не ставятся, то он забывает, как подключаться к сети. В общем, каждый день от него новости. «Поставил и забыл» — это не про Docker Desktop for Windows. Особенно, когда он используется не совсем так, как рекомендуют его разработчики. А они почему-то не одобряют подключение внешних windows сетевых дисков в качестве локальных. И совсем не одобряют доступ к к таким сетевым папкам, которые расположены ещё и на host машине. Пишут, что это ужас-ужас с точки зрения безопасности, требуют всяких ключей типа:
cap_add:
- SYS_ADMIN
- DAC_READ_SEARCH
для работы команды mount в контейнере и прочая, и прочая.
В общем, когда в очередной раз после выгрузки контейнеров на сервер заказчика сервисы перестали видеть сетевые диски, я не особо удивился. Так уже бывало, и даже была написана пошаговая инструкция для группы поддержки, как и что перезагружать, когда ломаются сетевые настройки докера.
Так что я открываю свою инструкцию и начинаю действовать. Перезапускаю контейнеры — не помогает. Перезапускаю через docker-compose с пересозданием инфраструктуры — не помогает. Сбрасываю настройки Docker к заводским, восстанавливаю параметры виртуалки, загружаю заново образы, запускаю через docker-compose — опять всё по старому — не видит сеть. Точнее не подключается к сетевым шарам, хотя пинг из контейнера до SMB сервера проходит нормально. Последний пункт — перезагрузку сервера и переустановку Docker, пока пропускаю, так как перезагружать сервер очень не хочется. На этом инструкция кончилась.
Ок, перехожу на свою домашнюю машину, тут у меня тоже Docker под Windows, но чуть более новой версии. Проверяю на нём. Те же яйца:
Ага. Ну неужели, думаю, Docker накатил обновление с какой-то безопасностью и теперь мои скрипты из-за этого не запускаются? Последняя проверка — начисто удалить Docker с машины, и поставить заново. Это должно быть круче сброса к заводским настройкам. Проделываю весь перечень из предыдущего шага, только в дополнение к этому ещё и перезагружаю свою машину, чтобы уж совсем железно. Ставлю Docker c нуля, заливаю образы, запускаю docker-compose — ёпрст! Все сервисы как не видели сетевых шар, так и продолжают писать при загрузке «mount error(22): Invalid argument»
Пробую запустить скрипт по строкам из командной строки: подключаюсь к контейнеру, запускаю по очереди команды и вижу, что всё подключается и работает как надо:
mkdir -p $SMB_MOUNT_POINT
mount -t cifs $SMB_SHARE $SMB_MOUNT_POINT -o user=$SMB_USER,password=$SMB_PASSWORD,vers=2.0
ls $SMB_MOUNT_POINT
То есть, это что же, какая-то хрень с передачей параметров в скрипт при запуске контейнера?
Ищем ещё идеи. Все варианты с перезагрузкой докера отмели, остались варианты с возможными изменениями в родительском образе. У меня образ собирается на основе openjdk:8-jdk-alpine, конкретной версии не указано, так что какие-нибудь улучшения безопасности могли сломать мои скрипты. Может поменяли что-то в OpenJDK или дочернем Alpine?
Проверяю логи проекта, пробую выбрать более старые openjdk:8-jdk-alpine-3.8, openjdk:8-jdk-alpine-3.7 и т.д. — каждый раз пересобираю контейнер, проверяю — всё по-старому.
Чёрт подери! Может я что-то всё-таки поменял в своей сборке? Выгружаю из GIT’а версию проекта месячной давности, собираю — те же глюки. Трёхмесячной давности — проблема всё ещё тут. Как же так? Что изменилось? Конфигурация докера к настоящему моменту гарантировано рабочая, конфигурация образа — тоже не поменялась, исходники проекта те же самые (GIT всё сохраняет). Чудес не бывает — надо понять, где всё-таки появились изменения. В проде вручную запускаю команды подключения к шарам — так до перезапуска сервисы будут работать нормально и иду спать. Утро вечера мудренее.
Связи нет, но вы держитесь!
Наутро приходит идея — что пора, видимо, узнать, а что собственно говоря не нравится скрипту при выполнении.
Сообщение «mount error(22): Invalid argument» — это не сильно информативно. Нахожу, что есть волшебный ключик -х для баша с которой он выводит отладочную инфу при выполнениии скриптов.
Начинаем отладку внутри sh:
/ # sh -x /entry-point.sh
' echo 'Mount //<private_ip>/Exchange/Pipeline to /pipeline
Mount //<private_ip>/Exchange/Pipeline to /pipeline
' mkdir -p '/pipeline
' mount -t cifs //<private_ip>/Exchange/Pipeline /pipeline -o 'user=<private_user>,password=<private_password>,vers=2.0
mount error(22): Invalid argument
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
mount: mounting //<private_ip>/Exchange/Pipeline on /pipeline failed: Invalid argument
' ls '/pipeline
+ exec
И тут появляются какие-то непонятные моменты — строка начинается с кавычек, потом кавычки в конце… Откуда кавычки?
Идея — может запуск с помощью настоящего bash будет информативнее?
Инсталлирую в контейнер BASH:
apk add bash
Запускаю отладку:
/ # bash -x /entry-point.sh
' echo 'Mount //<private_ip>/Exchange/Pipeline to /pipeline
Mount //<private_ip>/Exchange/Pipeline to /pipeline
+ mkdir -p $'/pipeline\r'
+ mount -t cifs //<private_ip>/Exchange/Pipeline /pipeline -o $'user=<private_user>,password=<private_password>,vers=2.0\r'
mount error(22): Invalid argument
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)
mount: mounting //<private_ip>/Exchange/Pipeline on /pipeline failed: Invalid argument
+ ls $'/pipeline\r'
+ exec
Блин, тут вроде, когда строка начинается с плюса — это хорошо, но появились какие-то \r и параметры $’…’
Ставим Midnight Commander, чтобы уж экспериментировать с удобствами apk add mc и открываем скрипт на редактирование, а там:
Оппа! ^M в конце каждой строки. Ну-ка, ну-ка, смотрим в локальном проекте — а что у нас с окончаниями строк???? CRLF. Работаем под Windows, однако.
Меняем в этом конкретно файле CRLF на LF (да здравствует Notepad++!), собираем проект — бинго! Работает как надо.
Почему раньше было ок, а сейчас всё полетело? Смотрю по коммитам — не было никаких перемен. И тут вспоминаю, что GIT умеет на лету править символы перевода строк текстовых файлов. А я на днях подключил новый репозитарий, и возможно выгрузил оттуда все файлы с конвертацией в CRLF.
В итоге добавляем в проект файл .gitattributes, с указанием, что в отдельных файлах надо-таки сохранять символы конца строк как в UNIX:
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Declare files that will always have LF line endings on checkout.
*.sh text eol=lf
Мораль — иногда виновник даже не попадает в круг первоначальных подозреваемых.
Постскриптум
После обновления на последнюю версию Docker Desktop for Windows 2.2.0.3 всё обратно перестало работать. На этот раз я сразу пошёл внутрь контейнеров, и начал отладку. Оказалось, что перестал работать IP адрес 10.0.75.1 для доступа к хост машине, DockerNat теперь удалён из системы, а виртуалка DockerDesktopVM даже не имеет внешнего сетевого адаптера, т.е. связь с ней идёт не через стандартную сеть, а как-то иначе. И для доступа к хосту надо пользоваться host.docker.internal. Товарищи разработчики так и написали, что
DockerNAT has been removed from Docker Desktop 2.2.0.0 as using an IP address to communicate from the host to a container is not a supported feature. To communicate from a container to the host, you must use the special DNS name host.docker.internal.
Ок, поправил конфиги для тестового окружения, база данных подцепилась, пинг из контейнера до host.docker.internal проходит, а вот сетевые диски не подключаются. Пробую запустить mount вручную из шелла, и получаю знакомую ошибку «mount error(22): Invalid argument».
Убираю по очереди аргументы — запускаю просто «mount -t cifs //host.docker.internal/playground /pipeline» — вроде работает, но стучится от пользователя «root».
Добавляю пользователя: mount -t cifs //host.docker.internal/playground /pipeline -o user=smbuser — спрашивает пароль и подключается!
Полный вариант тоже работает:
mount -t cifs //host.docker.internal/playground /pipeline -o user=smbuser,password=smbpassword
а вот «mount -t cifs //host.docker.internal/playground /pipeline -o user=smbuser,password=smbpassword,vers=2.0» не пашет.
Меняю последний параметр на vers=2.1 — ура, работает!
Похоже, что Docker в последней версии сделал свою собственную имплементацию SMB сервера с блекджеком, но без поддержки 2.0. Ерунда, конечно, по сравнению с другими новостями.
Всем добра, сил и упорства!
Иллюстрация для статьи взята с сайта blog.eduonix.com/software-development/learn-debug-docker-containers
Docker containers can access local services running on the host by connecting to host.docker.internal.
Note:
- it only works on Docker for Windows / Mac by default
- on Linux it’s useless for now but could be available starting from 20.03
- it’s Docker specific so it doesn’t exist in CRI-O or ContainerD with Kubernetes
The Docker docs say:
The host has a changing IP address (or none if you have no network access). We recommend that you connect to the special DNS name
host.docker.internalwhich resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Windows / Mac.Source: https://docs.docker.com/docker-for-windows/networking/#use-cases-and-workarounds
Use Case
What does this allow you to do exactly?
You could spin up a database or perhaps a development version of something like Consul and want to access it from inside the container when using Docker for Mac/Windows. Using host.docker.internal will resolve the correct host IP every time, which will be handy when moving between networks etc (as you can’t use localhost as the docker engine is running in a VM).
Implementation
In 20.03 (moby/moby#40007) added support for a magic string host-gateway that can be passed to ExtraHosts (–add-host) to reliably pass the Docker host IP to your containers.
Adding --add-host=host.docker.internal:host-gateway to docker run adds the
host.docker.internal DNS entry in /etc/hosts and maps it to host-gateway-ip.
docker run -it --add-host=host.docker.internal:host-gateway alpine cat /etc/hosts
Or for docker-compose:
# docker-compose.yml
my_app:
image: ...
extra_hosts:
- "host.docker.internal:host-gateway"
This is also available as daemon flag called host-gateway-ip which defaults to
the default bridge IP.
To have an identical behaviour across all Docker versions (Windows, Linux, Mac):
- add
"dns-resolve-docker-host": false,in the Docker daemon config - add
--add-host host.docker.internal=host-gatewayto your container
To connect Docker to localhost, running Docker for Windows or Docker for Mac, you can use the special DNS name host.docker.internal which resolves to the internal IP address used by the host.
Within your container, all you need to do to access the localhost is point your request to http://host.docker.internal:<port>
This will not work in a production environment outside of Docker Desktop. So only use this in your development environment. For Linux users, just use docker run with –network=”host” and use 127.0.0.1 to point to your localhost.
A practical use case to connect docker to localhost
A typical example of using a connection to localhost is connecting to a database you have installed locally. I’ll use a PostgreSQL installation local on my Mac to demonstrate. For the PostgreSQL installation, I have installed the default settings and haven’t edited any configuration files to perform the following tasks.
I have a table Users in database bernieslearnings and I’ll run a container that can retrieve the data in the table. We’ll start by running an Ubuntu docker image in interactive mode and installing the postgres command line tools.
docker run -it ubuntu:latest
apt-get update && apt-get install -y postgresql-client
root@53ddd71e92c3:/# psql --user postgres --host host.docker.internal
Password for user postgres:
psql (12.4 (Ubuntu 12.4-0ubuntu0.20.04.1))
Type "help" for help.
postgres=# \c bernieslearnings
You are now connected to database "bernieslearnings" as user "postgres".
bernieslearnings=# select * from Users;
pk | name | age
----+--------+-----
1 | Bernie | 38
(1 row)
What about non-Windows/MacOS Docker users?
The above approach is the current way to connect docker to localhost for Docker Desktop For Windows and Docker Desktop for Mac users. But if you’re using a Linux system the approach is a little different.
In order to reach the hosts localhost you need to use –network=”host” in your docker run command. Then, when sending requests to 127.0.0.1 your request will be sent the host machines localhost.
A Linux Example
In this example, I will use a CentOS 7 installation to demonstrate how to connect docker to localhost. You should start by removing any older version of docker that may be installed so that you can use the latest for the following example. Enter the following to remove old docker installations.
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
In order to install docker on CentOS you need to have the yum-utils package installed.
sudo yum install -y yum-utils
Next, we’ll add the stable repository to yum
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
To install the latest version of Docker Engine and contained
sudo yum install docker-ce docker-ce-cli containerd.io
Next, set your user to be part of the newly created docker group.
usermod -a -G docker username
Now we need to start the docker engine
sudo systemctl start docker
Now that we have docker setup, we’ll need to run something to check our connection to localhost is working. This server has a MySQL instance running so we’ll use that as our test case.
Connect docker to localhost MySQL instance
Let’s run an Ubuntu docker container in interactive mode and connect that to the host network
docker run --network="host" -it ubuntu:latest
Next, we need to update apt-get and then install mysql-client so we can query the database running on localhost outside of the container
apt-get update && apt-get install mysql-client
Now that the command-line client for MySQL is installed, all we need to do is to connect to our localhost MySQL instance
0.0.1 -u <username> -p
Enter your password and show the databases available
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mail |
| mysql |
| performance_schema |
+--------------------+
4 rows in set (0.00 sec)
A history trying to connect docker to localhost
For previous iterations of Docker Desktop (for Windows and Mac) there was a different special DNS entry for connecting the hosts localhost.
The previous entry for MacOS was docker.for.mac.localhost and the entry for Windows was docker.for.win.localhost
For more information on any of the above topics, check out the following links:
- https://www.postgresqltutorial.com/postgresql-cheat-sheet/
- https://docs.docker.com/engine/install/centos/
- https://www.howtogeek.com/50787/add-a-user-to-a-group-or-second-group-on-linux/
For a quick overview of Docker itself, check out Docker – A concise, quick overview
