· Sysadmin.id · Docker  · 7 min read

How to Configure Docker Compose Networks and Set Static IPs

Learn how Docker Compose networking works — from the default bridge network to custom networks, static IP assignment, network aliases, and multi-network setups for real-world infrastructure.

Learn how Docker Compose networking works — from the default bridge network to custom networks, static IP assignment, network aliases, and multi-network setups for real-world infrastructure.

Networking is one of the most important — and most misunderstood — parts of Docker Compose. By default, Docker Compose creates a network for you automatically, but understanding how to control that network gives you precise control over how containers communicate, what IPs they use, and how they’re isolated.

This guide covers everything from the default network behavior to assigning static IPs and building multi-network architectures.

Prerequisites

Before you start, make sure you have:

  • Docker Engine installed and running
  • Docker Compose v2 (docker compose command available)
  • Basic familiarity with docker-compose.yml syntax

Note: This guide uses Docker Compose v2 syntax (docker compose without the hyphen). If you’re on an older system with Compose v1, replace docker compose with docker-compose.


How Docker Compose Networking Works by Default

When you run docker compose up, Compose automatically creates a bridge network for your project. Every service defined in your docker-compose.yml is attached to that network and can reach other services by their service name as a hostname.

For example, with this minimal setup:

services:
  web:
    image: nginx
  db:
    image: postgres

The web container can connect to the database at hostname db, and db can reach the web server at web. Docker’s internal DNS handles the resolution automatically.

The auto-created network is named <project>_default, where <project> is the directory name of your project.


Step 1: Define a Custom Network

Instead of relying on the auto-created default network, define your own. This gives you control over the network name, driver, subnet, and IP range.

services:
  web:
    image: nginx
    networks:
      - appnet

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      - appnet

networks:
  appnet:
    driver: bridge

Now both containers share the appnet network and can reach each other by service name. The network name in Docker will be <project>_appnet.

Use an External Name

If you want the Docker network to have a specific name regardless of the project name, use name:

networks:
  appnet:
    driver: bridge
    name: my-app-network

Step 2: Assign a Static IP to a Container

To assign a static IP, you must define a subnet on the network first. Docker needs to know the address pool before it can assign a specific IP within it.

services:
  web:
    image: nginx
    networks:
      appnet:
        ipv4_address: 172.20.0.10

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      appnet:
        ipv4_address: 172.20.0.11

networks:
  appnet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24
          gateway: 172.20.0.1

Key points:

  • The ipam block defines the IP Address Management config
  • subnet sets the network range — here 172.20.0.0/24 gives you addresses from 172.20.0.1 to 172.20.0.254
  • gateway is optional but good to be explicit about
  • Each container’s ipv4_address must fall within the defined subnet
  • Reserve .1 for the gateway — start assigning containers from .10 or higher to avoid conflicts

Verify the assigned IPs after starting:

docker compose up -d
docker inspect <container_name> | grep IPAddress

Step 3: Use Network Aliases

A network alias lets a container be reachable under an additional hostname on a network. This is useful when you want a service to be accessible under a different name — for example, when migrating services or when a client expects a specific hostname.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      appnet:
        aliases:
          - database
          - postgres

networks:
  appnet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

Now other containers can reach the database as db, database, or postgres — all resolve to the same container.


Step 4: Connect a Container to Multiple Networks

A real-world setup often requires isolation — for example, a web container that can reach both the app tier and a monitoring network, while the database is only on the internal network.

services:
  web:
    image: nginx
    networks:
      - frontend
      - backend

  app:
    image: myapp:latest
    networks:
      - backend

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      - backend

  prometheus:
    image: prom/prometheus
    networks:
      - frontend
      - monitoring

networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/24

  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/24

  monitoring:
    driver: bridge
    ipam:
      config:
        - subnet: 172.23.0.0/24

In this setup:

  • db is only on backend — not directly reachable from web or prometheus
  • web bridges frontend and backend — it can talk to users and to app
  • prometheus bridges frontend and monitoring — it can scrape metrics but can’t touch db directly

This is a clean, secure network topology for a typical multi-tier application.


Step 5: Combine Static IPs with Multiple Networks

You can assign specific IPs per network when a container is on multiple networks:

services:
  web:
    image: nginx
    networks:
      frontend:
        ipv4_address: 172.21.0.10
      backend:
        ipv4_address: 172.22.0.10

  app:
    image: myapp:latest
    networks:
      backend:
        ipv4_address: 172.22.0.11

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      backend:
        ipv4_address: 172.22.0.20

networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/24
          gateway: 172.21.0.1

  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/24
          gateway: 172.22.0.1

Every container now has a predictable, fixed IP on each network it belongs to.


Step 6: Use an Existing External Network

Sometimes you want your Compose stack to join a network that already exists in Docker — for example, a shared monitoring or proxy network created by another stack.

First, create the network manually:

docker network create \
  --driver bridge \
  --subnet 172.30.0.0/24 \
  --gateway 172.30.0.1 \
  shared-proxy

Then reference it in your Compose file with external: true:

services:
  web:
    image: nginx
    networks:
      - shared-proxy
      - appnet

networks:
  shared-proxy:
    external: true

  appnet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/24

Docker will not try to create or destroy shared-proxy — it just attaches containers to it. This is common when using a reverse proxy like Traefik or Nginx Proxy Manager that manages its own network.


Step 7: Full Production-Ready Example

Here is a complete docker-compose.yml that combines everything — custom networks, static IPs, aliases, and an external proxy network:

services:
  nginx:
    image: nginx:alpine
    container_name: web
    ports:
      - "80:80"
      - "443:443"
    networks:
      proxy:
      frontend:
        ipv4_address: 172.21.0.10

  app:
    image: myapp:1.0
    container_name: app
    restart: unless-stopped
    networks:
      frontend:
        ipv4_address: 172.21.0.11
      backend:
        ipv4_address: 172.22.0.10
        aliases:
          - appserver

  db:
    image: postgres:16-alpine
    container_name: postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: strongpassword
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      backend:
        ipv4_address: 172.22.0.20
        aliases:
          - database

  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    networks:
      backend:
        ipv4_address: 172.22.0.30
        aliases:
          - cache

volumes:
  pgdata:

networks:
  proxy:
    external: true

  frontend:
    driver: bridge
    name: app-frontend
    ipam:
      config:
        - subnet: 172.21.0.0/24
          gateway: 172.21.0.1

  backend:
    driver: bridge
    name: app-backend
    ipam:
      config:
        - subnet: 172.22.0.0/24
          gateway: 172.22.0.1

This setup:

  • nginx handles incoming traffic, sits on the proxy and frontend networks
  • app bridges frontend and backend — talks to both nginx and the databases
  • db and redis are isolated to the backend network — not directly reachable from outside
  • All IPs are static and predictable
  • Service aliases provide clean hostnames (database, cache, appserver)

Useful Networking Commands

CommandDescription
docker network lsList all Docker networks
docker network inspect <name>Show network details, subnets, and connected containers
docker network create <name>Create a new network manually
docker network rm <name>Remove a network
docker network connect <net> <container>Connect a running container to a network
docker network disconnect <net> <container>Disconnect a container from a network
docker inspect <container>Show container details including all IPs

Check which IP a running container has on each network:

docker inspect <container_name> \
  --format '{{range $net, $cfg := .NetworkSettings.Networks}}{{$net}}: {{$cfg.IPAddress}}{{"\n"}}{{end}}'

Common Mistakes to Avoid

1. Assigning an IP without defining a subnet

Docker will reject ipv4_address if there’s no ipam.config.subnet defined. Always define the subnet first.

2. IP outside the subnet range

If your subnet is 172.20.0.0/24, valid host addresses are 172.20.0.1172.20.0.254. Assigning 172.20.1.10 will fail.

3. Subnet conflicts with the host

Avoid subnets that overlap with your host’s network interfaces or existing Docker networks. Use docker network ls and docker network inspect to check existing ranges before picking a new subnet.

4. Forgetting to restart after network changes

Changes to the networks block require bringing the stack down and back up — docker compose restart is not enough:

docker compose down
docker compose up -d

5. Using the default network with static IPs

You cannot assign static IPs to the auto-created default network. You must define a custom network with ipam config to use ipv4_address.


Summary

Here’s what you covered:

  1. How Docker Compose creates a default network automatically
  2. Defining custom named networks with explicit subnets
  3. Assigning static IPs using ipv4_address and ipam config
  4. Adding network aliases for flexible hostname resolution
  5. Connecting containers to multiple networks for proper tier isolation
  6. Joining external networks created outside of Compose
  7. A full production-ready multi-service example

Controlling your Docker network layout is the difference between a fragile, hard-to-debug stack and a clean, predictable infrastructure. Static IPs and named networks make your setup easier to document, firewall, and monitor.

Need help designing or debugging your Docker infrastructure? Get in touch — I’m happy to help.

  • docker
  • docker-compose
  • networking
  • devops
  • linux
Share:
Back to Blog

Related Posts

View All Posts »
How to Install Docker on Ubuntu 24.04 LTS

How to Install Docker on Ubuntu 24.04 LTS

A complete step-by-step guide to installing Docker Engine on Ubuntu 24.04 LTS (Noble Numbat) — from adding the official repository to running your first container and setting up Docker Compose.