tl;dr
You would need 3 things to make it work:
- A predefined external network for access to the host via a fixed IP
hostname entry for each participating service
- An additional Docker service as router in order to redirect requests to the host. We will also use the
aliases entry to allow other services to reach this router service via a predefined hostname.
How to
Note: Tested for Linux. Not sure, if it works for Windows.
- Create the external network with a gateway. E.g. via the custom IP
172.30.0.1 defined beloew, each docker service should be able to access the host.
docker network create \
--driver=bridge \
--subnet=172.30.0.0/16 \
--gateway=172.30.0.1 \
host-net
- Add custom
hostname for each participating service in your docker-compose.yml, e.g.
services:
service-1:
hostname: service-1.in.docker
service-2:
hostname: service-2.in.docker
So, service-2 can now access to service-1 via the domain service-1.in.docker and it should work vice versa.
- Create an additional service that utilizes the well-known multipurpose relay tool socat to act like a router, e.g.
services:
router:
image: alpine/socat
command: TCP-LISTEN:5900,fork TCP:172.30.0.1:15900
Here based on TCP, it listens on localhost at port 5900 and redirects the incoming requests to the destination 172.30.0.1 (the gateway -> host machine) at port 15900.
Example
version: "3.9"
services:
router:
image: alpine/socat
entrypoint: >
sh -c "
socat TCP-LISTEN:5900,fork TCP:172.30.0.1:15900 &
socat TCP-LISTEN:5901,fork TCP:172.30.0.1:15901 &
wait
"
networks:
host-net:
bridged:
aliases:
- service-1.in.docker
service-1:
# hostname: service-1.in.docker
image: hashicorp/http-echo
command: -listen=:5900 -text="hello world"
networks:
bridged:
service-2:
hostname: service-2.in.docker
image: curlimages/curl:7.75.0
command: ["sh", "-c", "while true; do curl service-1.in.docker:5900 && sleep 5; done"]
networks:
bridged:
networks:
host-net:
external: true
bridged:
Notes:
Here the router service includes two important things: host-net as network, and aliases entry for the network bridged, in which other services are also involved.
The router service works as follows:
(request) -> [localhost:5900] -> (redirect) -> [172.30.0.1:15900]
(request) -> [localhost:5901] -> (redirect) -> [172.30.0.1:15901]
Since we commented the line hostname: service-1.in.docker, the curl requests made by service-2 (curl service-1.in.docker:5900) will go to the router service (due to the alias service-1.in.docker). Subsequently, these requests are forwarded to the host machine on port 15901.
If we comment out the line hostname: service-1.in.docker, service-1 would respond to service2 with "hello world".
If you have the possibility to dynamically change the hostnames used by the individual services, such as env vars or properties files etc., you can make use of them, so you don't have to comment in and out every time:
services:
router:
image: alpine/socat
networks:
bridged:
aliases:
- service-1.in.router
service-2:
hostname: service-2.in.docker
command: ["sh", '-c', 'while true; do curl ${DEST_SERVICE_HOST_ADDR_SET_BY_ENV_VAR} && sleep 5; done']
DEST_SERVICE_HOST_ADDR_SET_BY_ENV_VAR can be provided with service-1.in.router or service-1.in.docker as per need.
Footnote:
You can run a test server on your host maschine to test this, e.g.:
python3 -m http.server 15901