How To Use Docker Swarm DNS and Its Service Names in Nginx Upstream

Of course there are other great tools out there like e.g. traefik with a lot of add-on functionality but this post quickly shows how you can use a regular Nginx Docker image as a kind of ingress controller to your Docker Swarm stacks and its services.

If you have one or more Swarm nodes and Nginx services running on the same Swarm with ports 80 and 443 exposed and mapped to the Docker host you can then use Docker DNS service on 127.0.0.11 in the Nginx resolver parameter for dynamically resolving upstream Docker stack services connected to the same user-defined overlay network (backend services can be separated on further dedicated networks).

Nginx Config

For an upstream service Nginx configuration upstream_web_service.tempplate all it takes is this:

server {
  <...>
  location / {
    <...>
    resolver 127.0.0.11 valid=30s ipv6=off;
    set $upstream_addr <swarm_stack_service_name>:<port>; 
    proxy_pass https://$upstream_addr;
    <...>
  }
  <...>
}

The <swarm_stack_service_name> by default is prefixed with the environment variable COMPOSE_PROJECT_NAME and after that the service name as given in the Docker compose/stack file. You can check this e.g. with docker stack ps "${COMPOSE_PROJECT_NAME}".

You can as well move the resolver statement into your overall nginx.conf into the http parameter layer so that it is being used for any Nginx server locations.

http {
  <...>
  resolver 127.0.0.11 valid=30s ipv6=off;
  <...>
}

Docker Stack Config

These are the relevant fragments from the docker-compose/docker-stack files:

Nginx:

version: "3.9"

services:
  nginx_ingress:
    networks:
      - net1_ingress
    ports:
      - "80:80"
      - "443:443"
    # ports mapped to the host and incomming requests will be resolved according to Docker DNS above and routed over the net1_ingress network to upstream services on their internally exposed ports (not mapped externally to the docker host)

If you are using the <ip>:<host_port>:<container_port notation for ports then keep this github moby issue in mind that ports are always mapped to all network interfaces on 0.0.0.0 (and use e.g. your firewall and or load balancer rules accordingly).

Some upstream web service:

version: "3.9"

services:
  upstream_web_service:
    networks:
      - net1_ingress
      - net2_backend
    # no ports mapped to the host and in both the general net1_ingress network and its own service's backend_network
  upstream_db_service:
    networks:
      - net2_backend
    # no ports mapped to the host and only in the dedicated service's backend_network so that only the upstream_web_service can access it

This basic setup shows the "Option 2: reverse proxy in Swarm" as explained here.

Further information:

https://docs.docker.com/network/#dns-services
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#resolver
https://traefik.io/solutions/docker-swarm-ingress
https://medium.com/@farren.alex/docker-swarm-ingress-how-to-route-traffic-in-production-e749306168a1
https://stackoverflow.com/questions/43896846/how-to-use-docker-swarm-dns-service-names-in-nginx-upstream/44303568#44303568
https://medium.com/@prajwal.chin/understanding-docker-dns-2ed4b070a0