Docker & DevOps

Docker networks: bridge, host and custom networks explained

How Docker networking works on a VPS. Covers the default bridge, user-defined networks with built-in DNS, host mode, connecting containers across Compose projects and inspecting networks.

IntermediateLinux server5 min read

Every container gets a network connection, but which containers can talk to each other, and by what name, depends on the network they join. Getting this right lets your app reach its database by name, keeps internal services off the public internet and lets one reverse proxy serve several Compose projects. This guide covers the network types you will use on a VPS.

Before you start

  • A Linux VPS with Docker Engine and the Compose plugin. See Install Docker and Docker Compose.
  • A user that can run docker commands.
  • A basic idea of ports and IP addresses.

List the networks Docker creates on install:

Bash
docker network ls

You will see bridge, host and none.

The default bridge network

A container started with docker run and no --network option joins the default network called bridge. Docker gives it a private address, usually in 172.17.0.0/16, and uses NAT so it can reach the internet.

The default bridge has one big limitation: containers on it cannot find each other by name. They can only use IP addresses, which change when containers are recreated. Treat it as a fallback, not something to build on.

User-defined bridge networks

A network you create yourself is also a bridge, but it includes an internal DNS server. Containers on it can reach each other by container name or service name.

Bash
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=change-me postgres:16
docker run -d --name app --network appnet -e DB_HOST=db myapp:1.0

The app connects to db:5432. You did not publish port 5432 on the host, so the database is not reachable from the internet. Only containers on appnet can see it.

Compose does this for you. Every project gets a network called <project>_default, and each service can reach the others by service name:

YAML
services:
  app:
    image: myapp:1.0
    environment:
      DB_HOST: db
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: change-me

Tip: Only publish ports (ports:) for services the outside world must reach, usually just the reverse proxy. Everything else talks over the internal network.

Several networks in one project

You can split a stack so the proxy never sees the database:

YAML
services:
  proxy:
    image: nginx:stable
    ports:
      - "80:80"
    networks: [frontend]
  app:
    image: myapp:1.0
    networks: [frontend, backend]
  db:
    image: postgres:16
    networks: [backend]
networks:
  frontend:
  backend:
    internal: true

internal: true also blocks the backend network from reaching the internet, which suits a database that never needs outbound access.

Connect containers across Compose projects

A common VPS layout is one reverse proxy stack plus several app stacks. They need a shared network. Create it once, outside any project:

Bash
docker network create proxy

In each Compose file that should join it, mark the network as external:

YAML
services:
  app:
    image: myapp:1.0
    networks: [proxy, default]
networks:
  proxy:
    external: true

The proxy container can now reach app by its service name. Keeping default in the list lets the app still talk to its own database. Run many Docker Compose projects on one VPS shows a full folder layout built on this pattern.

You can also attach a running container to a network without recreating it:

Bash
docker network connect proxy myapp-app-1

Host networking

With --network host, the container shares the VPS's own network stack. It has no separate IP, and port mapping does not apply: if the app listens on 8080, it is on the host's port 8080 directly.

Bash
docker run -d --name exporter --network host prom/node-exporter

Use host mode for tools that need to see the host's real interfaces, such as some monitoring agents, or for very high packet rates. For ordinary web apps, a bridge network is safer because it isolates ports.

The none network

--network none gives a container only a loopback interface. Use it for batch jobs that should never touch the network, such as processing files in a volume.

Inspect networks

See a network's subnet, gateway and attached containers:

Bash
docker network inspect appnet

Show which networks a container is on:

Bash
docker inspect app --format '{{json .NetworkSettings.Networks}}'

Remove networks no container uses:

Bash
docker network prune

Check it works

Test name resolution from inside the app container:

Bash
docker run --rm --network appnet busybox:stable nslookup db
docker run --rm --network appnet busybox:stable ping -c 2 db

The first command should return an address for db. The second should get replies from the database container.

Troubleshooting

  • could not translate host name "db": the two containers are on different networks, or one of them is on the default bridge. Put both on the same user-defined network.
  • network proxy declared as external, but could not be found: create it first with docker network create proxy.
  • Pool overlaps with other one on this address space: a new network's subnet clashes with an existing one or with a VPN. Remove unused networks with docker network prune, or set default-address-pools in /etc/docker/daemon.json.
  • A port is reachable from the internet even though UFW denies it: Docker publishes ports through its own firewall rules. Bind to 127.0.0.1 or see Fix UFW and Docker bypassing the firewall.

Next steps

  • Fix outbound problems: Fix DNS and internet access inside Docker containers.
  • Lay out several stacks behind one proxy: Run many Docker Compose projects on one VPS.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.