User-defined Network #

If there’s one concept in Docker networking that’s mandatory to master for production applications, it’s the user-defined network. Docker provides many network drivers, but for almost every single-host scenario, the user-defined bridge is the best choice. It combines everything we need: internal DNS, good isolation, flexible configuration, and stable service discovery.

Many developers still use the default bridge (docker0) without realizing its limitations — no internal DNS, all containers on one subnet, and inter-container communication requires IP addresses. User-defined networks solve all of those with one command: docker network create.

This article covers user-defined networks in depth: how to create them, configuration options (subnet, gateway, internal), multi-network per container, Docker Compose integration, and architecture patterns for production applications. By the end, you’ll be able to design safe, scalable, maintainable container network topologies.

What Is a User-defined Network? #

A user-defined network is a network you create explicitly with docker network create, as opposed to the built-in networks Docker automatically creates (bridge, host, none).

# Create a user-defined network
docker network create my-network

# List all networks
docker network ls
# NETWORK ID     NAME          DRIVER    SCOPE
# abc123...      bridge        bridge    local  <- default
# def456...      host          host      local  <- default
# ghi789...      none          null      local  <- default
# jkl012...      my-network    bridge    local  <- yours

User-defined network characteristics (default bridge driver):

  • Automatic internal DNS — containers can resolve each other by name.
  • Per-network isolation — containers on network A don’t talk to network B.
  • Containers on many networks — one container can be multi-network.
  • Custom configuration — subnet, gateway, IP range, specific drivers.
  • Service discovery — Compose automatically uses user-defined networks.
User-defined networks = the default best practice. For almost every case, docker network create my-net and run containers on that network. The default bridge is only for quick experiments.

How to Create a User-defined Network #

Basic Syntax #

docker network create [OPTIONS] NETWORK_NAME

The most frequently used options:

OptionFunction
--driverDriver type (default: bridge)
--subnetCustom subnet (CIDR)
--gatewayCustom gateway IP
--ip-rangeIP range for containers
--internalNetwork with no outbound access
--attachableContainers can attach manually
--labelMetadata (for Compose, K8s)

Examples #

# Simplest - Docker picks all defaults
docker network create app-network

# Custom subnet
docker network create \
  --subnet=192.168.50.0/24 \
  --gateway=192.168.50.1 \
  custom-net

# Internal network (no internet access)
docker network create --internal backend-net

# Multi-driver (overlay for Swarm)
docker network create --driver overlay swarm-net

Why the Default Bridge Isn’t Enough #

The default bridge (docker0) is the built-in bridge created when the Docker Engine is first installed. It has serious limitations for multi-container applications.

AspectDefault BridgeUser-defined Network
Internal DNS❌ No✅ Yes
Container name resolution❌ (must use IP or --link)
Service names (Compose)
Per-project isolation❌ (all on one bridge)
Multi-network per container
Custom subnets
Container scalingLimitedFree
# Default bridge - DNS inactive
docker run -d --name web --network bridge nginx
docker run -d --name db  --network bridge postgres
docker exec web sh -c 'ping -c 1 db'
# ping: bad address 'db'  <- FAILS

# User-defined - DNS active
docker network create app-net
docker run -d --name web --network app-net nginx
docker run -d --name db  --network app-net postgres
docker exec web sh -c 'ping -c 1 db'
# PING db (172.18.0.2): 56 data bytes  <- WORKS
The default bridge = an anti-pattern for multi-container applications. Don’t use it for anything other than quick testing or standalone single containers.

Custom Subnet and Gateway Configuration #

For more control, you can set your own subnet, gateway, and IP range.

docker network create \
  --driver bridge \
  --subnet 10.10.0.0/16 \
  --gateway 10.10.0.1 \
  --ip-range 10.10.1.0/24 \
  --label env=production \
  --label app=backend \
  production-backend

When do you need a custom subnet?

  • Avoiding conflicts with other networks — if the host has a VPN on 192.168.0.0/16 and Docker’s default is also in that range, they can clash.
  • Predictable IP allocation — if you need containers to have specific IPs for compliance reasons.
  • Multi-host setups — structured subnets make routing easier.
# Inspect the details
docker network inspect production-backend

The output will show IPAM.Config with the customized Subnet, Gateway, and IPRange.

Best practice: use an easy-to-remember subnet that doesn’t clash with common ranges (10.0.0.0/8 is huge, 172.16.0.0/12 is medium, 192.168.0.0/16 is small). Avoid 192.168.0.0/24 and 192.168.1.0/24 because they clash with home/office routers.

Internal Networks: Total Isolation from the Internet #

A user-defined network can be created internal — no outbound gateway, no internet access, no outbound NAT.

docker network create --internal backend-net

Internal network characteristics:

  • No gateway in the container routing table.
  • Containers can’t access the internet — even DNS to 8.8.8.8 fails.
  • Containers can still talk to other containers on the same network.
  • Good for: databases, caches, message brokers, internal services.
# docker-compose.yml
networks:
  backend:
    internal: true
  frontend:
    # Default - has internet access
# Verify: a container on an internal network can't ping google
docker run --rm --network backend-net alpine ping -c 1 8.8.8.8
# ping: connect: Network is unreachable
Internal network = no internet, no exceptions. This isn’t a bypassable feature. If a service on an internal network suddenly has internet access, there’s a misconfiguration. Audit the Docker and iptables configuration.

Multi-Network per Container: Layered Security #

One container can connect to many user-defined networks at once. This is a common layered security pattern.

# An API container bridging frontend and backend
docker network create frontend
docker network create backend --internal

docker run -d --name api --network frontend my-api
docker network connect backend api
flowchart LR
    subgraph Front["frontend (public)"]
        W[web]
        A[api]
    end
    subgraph Back["backend (internal)"]
        A
        D[(db)]
    end
    W --> A
    A --> D

The api container has interfaces on frontend (open to the internet) and backend (internal, hidden). It becomes the only gateway. The database is truly hidden — unreachable from the internet without going through api.

# docker-compose.yml - segmentation pattern
version: "3.9"
services:
  web:
    image: nginx
    networks:
      - frontend
  
  api:
    image: my-api
    networks:
      - frontend
      - backend
  
  db:
    image: postgres
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true

User-defined Networks in Docker Compose #

Docker Compose always creates a user-defined network for a project, unless you override it.

version: "3.9"
services:
  web:
    image: nginx
  api:
    image: my-api
  db:
    image: postgres

Without a networks declaration, Compose automatically:

  • Creates one default network: <project>_default (e.g. myapp_default).
  • Connects all services to that network.
  • Enables service-name-based internal DNS.
docker compose up -d
docker network ls
# myapp_default    bridge    local  <- created automatically

docker network inspect myapp_default
# Containers:
#   - myapp-web-1
#   - myapp-api-1
#   - myapp-db-1

Custom Multi-Network in Compose #

version: "3.9"
services:
  web:
    image: nginx
    networks:
      - frontend
  
  api:
    image: my-api
    networks:
      - frontend
      - backend
  
  worker:
    image: my-worker
    networks:
      - backend
  
  db:
    image: postgres
    networks:
      - backend

networks:
  frontend:
    name: my-frontend
  backend:
    name: my-backend
    internal: true
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-backend
driver_opts.com.docker.network.bridge.name lets you set the Linux bridge interface name. Useful for monitoring (tcpdump -i br-backend) or auditing (ip link show on the host).

User-defined Networks for Multi-Host (Swarm) #

For multi-host deployments, user-defined networks can be created with the overlay driver in Docker Swarm.

docker network create \
  --driver overlay \
  --subnet 10.0.0.0/16 \
  --attachable \
  swarm-net

Containers deployed in Swarm automatically spread across many hosts, but stay in one network. Communication runs over VXLAN tunnels underneath.

flowchart LR
    subgraph Host1["Host 1"]
        C1A[Container A]
        C1B[Container B]
    end
    subgraph Host2["Host 2"]
        C2A[Container A]
        C2B[Container C]
    end
    subgraph Host3["Host 3"]
        C3A[Container C]
    end
    C1A <-->|VXLAN| C2A
    C1B <-->|VXLAN| C2B
    C2B <-->|VXLAN| C3A
# docker-stack.yml (Swarm mode)
version: "3.9"
services:
  web:
    image: nginx
    networks:
      - frontend
    deploy:
      replicas: 5
  
  api:
    image: my-api
    networks:
      - frontend
      - backend
    deploy:
      replicas: 10

networks:
  frontend:
    driver: overlay
  backend:
    driver: overlay
    internal: true

web containers on different hosts can still find each other via web (Swarm DNS load-balances to the nearest replica). api containers on different hosts can talk to db, which may be on yet another host.


Common Architecture Topologies #

Single Tier: One Network #

flowchart LR
    N[app-network] --> W[web]
    N --> A[api]
    N --> D[(db)]
    N --> C[(cache)]

Good for: development, single-host, MVPs.

Three Tiers: Edge-App-Data #

flowchart TB
    subgraph Edge["edge"]
        LB[Load Balancer]
    end
    subgraph App["app"]
        API[API]
        W1[Worker]
    end
    subgraph Data["data (internal)"]
        DB[(DB)]
        Cache[(Cache)]
        Broker[(Kafka)]
    end
    LB --> API
    API --> DB
    API --> Cache
    W1 --> Broker
    Broker --> DB

Good for: single-host production, multi-service with segmentation.

Multi-Tenant: Per-Tenant Networks #

flowchart TB
    subgraph A["tenant-a"]
        AN[a-network]
        A1[app]
        A2[(db)]
    end
    subgraph B["tenant-b"]
        BN[b-network]
        B1[app]
        B2[(db)]
    end
    Admin[admin-network] --> A1
    Admin --> B1

Good for: SaaS, hosting environments, per-customer dev/staging/prod.

Microservices with a Broker #

flowchart LR
    subgraph Edge["edge"]
        GW[Gateway]
    end
    subgraph App["app"]
        API1[API Service]
        API2[Worker]
    end
    subgraph Broker["broker"]
        K[(Kafka)]
    end
    subgraph Data["data"]
        DB1[(DB 1)]
        DB2[(DB 2)]
    end
    GW --> API1
    API1 --> K
    K --> API2
    API1 --> DB1
    API2 --> DB2

Good for: event-driven architectures, large microservices.


Managing User-defined Networks #

Inspection #

# List all networks
docker network ls

# Network details
docker network inspect my-network
# Contains: driver, subnet, gateway, connected containers

# See only the containers on a network
docker network inspect my-network --format '{{range .Containers}}{{.Name}} {{end}}'

# One container's IP
docker network inspect my-network --format '{{json .Containers}}' | python3 -c "import json,sys; print(json.load(sys.stdin)['container-id']['IPv4Address'])"

Modification #

# Connect a container to a network
docker network connect my-network existing-container

# Disconnect
docker network disconnect my-network existing-container

# Remove a network (must have no containers)
docker network rm my-network

Cleanup #

# Remove all unused networks
docker network prune

# Remove all containers + networks
docker system prune --volumes
docker network rm fails if containers are still connected. Disconnect the containers first (or remove them). In Compose, docker compose down automatically removes the project’s networks.

User-defined Network vs Default Bridge: When to Use Which #

SituationChoice
Quick 1-container experimentDefault bridge (or no network at all)
Multi-container developmentUser-defined network
Production with many servicesUser-defined network + segmentation
Multi-host (Swarm, K8s)User-defined network with overlay/calico drivers
No-network sandboxNone network
High performanceHost network (but be careful)

In practice: for 95% of cases, user-defined bridges are the right choice. The remaining 5% are special cases genuinely needing another mode.


User-defined Network Best Practices #

1. Always Use User-defined for Multi-Container Setups #

# ✓ Best practice
docker network create app-net
docker run --network app-net --name db postgres
docker run --network app-net --name api my-api

2. Segment by Layer #

networks:
  edge:        # public-facing
  app:         # business logic
  data:        # internal: true, no internet

3. Use Descriptive Network Names #

# ✓ Good
docker network create frontend-prod
docker network create backend-prod
docker network create data-prod

# ✗ Not great
docker network create net1
docker network create net2

4. Custom Subnets to Avoid Conflicts #

# Avoid common ranges 192.168.x.x (home) and 10.x.x.x (widely used)
docker network create --subnet 172.25.0.0/16 app-net

5. Internal Networks for Sensitive Services #

networks:
  data:
    internal: true  # databases, caches, brokers

6. Docker Compose: Leave Default, or Customize Explicitly #

# Default (Compose auto-creates one network)
services:
  web:
  api:

# Explicit custom (for full control)
services:
  web:
    networks: [frontend]
  api:
    networks: [frontend, backend]
networks:
  frontend:
  backend:
    internal: true

7. Routine Cleanup #

# Remove orphaned networks
docker network prune

# Or all unused resources at once
docker system prune

Migrating from the Default Bridge to User-defined #

If your project already uses the default bridge, migration is easy:

# 1. Create a user-defined network
docker network create app-net

# 2. Connect all existing containers
for container in web api db; do
  docker network connect app-net $container
done

# 3. Test communication
docker exec web ping api

# 4. Update docker-compose.yml for the future
# Update docker-compose.yml
version: "3.9"
services:
  web:
    networks: [app-net]
  api:
    networks: [app-net]
  db:
    networks: [app-net]

networks:
  app-net:

Summary #

  • User-defined networks = networks you create yourself with docker network create. They overcome all the default bridge’s limitations.
  • Mandatory for multi-container applications, production, or anything needing name-based service discovery.
  • Automatic internal DNS — containers resolve container names or service names without extra configuration.
  • Full configuration: --subnet, --gateway, --ip-range, --internal, --driver, --label. Custom subnets avoid conflicts with host networks.
  • Internal networks = no internet access. Mandatory for databases, caches, brokers, and services that must not have outbound access.
  • Multi-network per container enables layered security: an API container on frontend and backend becomes an auditable gateway.
  • Docker Compose automatically creates a user-defined network. For full control, declare networks: in Compose and assign per-service.
  • Multi-host (Swarm, K8s): user-defined networks with the overlay driver, or external drivers (Calico, Flannel, Cilium).
  • The default bridge = an anti-pattern for multi-container setups. Only for quick experiments or single containers.
  • Best practices: per-layer segmentation (edge/app/data), descriptive network names, internal for sensitive services, custom subnets to avoid conflicts.
  • The main principle: containers that work together → user-defined network. Standalone containers → the default bridge is fine. Public containers → minimal port mapping and clear segmentation.
  • Migration from the default bridge to user-defined is easy: create the network, connect existing containers, update Compose.

← Previous: Network Isolation   Next: What is a Dockerfile? →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact