How to expose docker swarm service ports only internally?

edit: I think this is self solved. (check my reply) But please feel free to give further advice on what’s best practice.


I spend way too much time trying to get this working. Maybe someone can help here.

For my externally hosted “home lab” I want to use a single node docker swarm node. My thought was using swarm might be cool, because of secrets support and the possibility to play around more.

My goal

tl;dr: Only the reverse proxy should be exposed to the internet

  • Reverse proxy listens on the host network.
    (this is important because the proxy has to “see” the external IP addresses to utilize fail2bain etc. instead of the internal ones) If there is a way that the reverse proxy sees the external requests without being in the host network my problem would also be solved.
  • All other services should only be accessible from the reverse proxy.

Without docker swarm I’d assign container IP addresses manually in docker-compose. That way the reverse proxy could access them with container IP and no port has to be published. With docker swarm I don’t know how to do this.

It’s possible to get the underlying container IP in a service, but I think it’s not intended to point to it, because they could change.

What I tried to do

  • tried to find ways to expose service ports only to the host node (doesn’t work with swarm? example 127.0.0.1:3000:3000)
  • played around with deploy: mode: global with no success
  • played around with endpoint_mode: dnsrr with no sucess
  • tried to find ways to access services directly somehow without exposing ports
  • tried to get one service in the host and overlay network

Any help would be very much appreciated.

1 Like

I think I approached this the wrong way. I wasn’t aware of the concept of multi-homing containers. In case someone struggles like me, here is what I did, hope that’s the right way to do it.

Have the reverse proxy in every network that it has to reach

networks:
    public:
      external: true
    lasttry1:
      external: true
    lasttry2:
      external: true

Then have the mode set to host so external IP addresses can be seen in reverse proxy logs.

    ports:
      - target: 80
        published: 80
        mode: host
      - target: 443
        published: 443
        mode: host

from within there name resolution works for services.

Container ports aren’t exposed since the reverse proxy can reach them that way.

My goal was to have better segmentation so that not all containers are in one giant network where all ports are open from within the network.

All of this is true for normal compose project deployments as, except mode: hostwhen publishing a port.

DNS-based service discovery is available in all user defined bridge and overlay networks, regardless whether the container attached to it is created by docker run, docker compose, docker service createor docker stack deploy

The global mode will use a virtual ip for a service endpoint The service discovery dns record will resolve to the vip, which then round-robin forwards traffic to the target task containers. It is suited for request/response cycles that are shorter than 900 seconds. For instance a database connection pool of an application might keep connections open to a database, which will get disconnected after 900 seconds. In such a case the dnsrr mode is more suited, as the service discovery dns record will resolve as multivalue entry with the ip’s of each target task. As a result this approach provides direct connection to the replicas, but depending on the client can suffer from dns caching that always resolves a service name to the first container ip it resolved for this service. There is no counterpart for mode: global on compose project deployments, and mode dnsrr is the default.

1 Like