You add network_mode: service:vpn to the app, restart the stack, and the app dies because db no longer resolves. Nothing was renamed in the compose file, so the obvious suspects are DNS and the database. The actual change is smaller and stranger: the app stopped being a member of the Compose network where that name exists. Until you decide which namespace the database should answer from, every hostname fix is guesswork.

Why the db hostname stopped resolving

A service using network_mode has no address of its own on a Compose network; it borrows the named service's namespace. That namespace brings its own interfaces, routes, port space and resolver, and it replaces the app's attachment to the backend network. The name db was never abstract: it lived on that network and resolved through its embedded DNS. Drop the attachment and the name has nowhere to resolve.

Confirm that from inside the containers rather than from the host. A lookup that fails in the app but succeeds in the database container means the name is fine and the membership is not. That result also puts the fix in the compose topology instead of the app's resolver settings.

Three questions before you edit the file

Answer these in order, because each one narrows the topology. What must the database answer on: a hostname other containers use, or only the app on loopback? Must the app publish a port to the host, and which service would own it? Must the database be reachable from the host or from another machine, or only from inside the tunnel?

If the database needs its own name and the app publishes nothing, keep them on separate attachments. If the app must publish a port and everything routes through the tunnel, put the database in the VPN namespace and use loopback. Mixing the two produces a stack that starts cleanly and fails at connect time.

Keep the database where it is

The smaller change attaches the namespace owner to the network that already contains the database. The app inherits that attachment because it shares the vpn stack, and db resolves exactly as it did before.

services:
  vpn:
    networks:
      - backend
  app:
    network_mode: service:vpn
  db:
    networks:
      - backend
networks:
  backend:

Do not add networks to the app as well. Compose rejects that property alongside network_mode in the same service, and the error points at the file rather than at the pair. If the VPN client filters the backend destination inside its namespace, adjust its supported local-network policy too; a shared attachment offers a path but does not override that filter.

Move the database into the namespace

The larger change puts the database in the same namespace and points the app at 127.0.0.1 with the real port. Two processes cannot both bind the same address and port just because they started in different containers, so a database that also listens on the wildcard address will collide with whatever else does. Check the listener before you move it.

Published ports belong on the namespace-owning service. Adding ports to the app is rejected alongside network_mode, and inventing a separate app address describes an interface that does not exist.

Readiness, blocking and the resolver

depends_on orders container startup; it is not evidence that the tunnel is up, and it does not stop traffic when the tunnel drops. Treat reachability, tunnel readiness and failure blocking as three separate requirements. A stack that satisfies only the first passes a manual test on a good day and leaks on a bad one. Write the three answers beside the compose file, where the next person will look.

Namespace sharing also means the app inherits the VPN container's resolver, which helps when the tunnel pushes DNS servers and confuses when it does not. Check what the namespace actually contains instead of assuming the host's resolver applies. The Compose services reference documents network_mode, ports and dependency behaviour in one place, including why a network_mode service has no address of its own.

Ũhoro ũhĩtĩte