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:
| Option | Function |
|---|---|
--driver | Driver type (default: bridge) |
--subnet | Custom subnet (CIDR) |
--gateway | Custom gateway IP |
--ip-range | IP range for containers |
--internal | Network with no outbound access |
--attachable | Containers can attach manually |
--label | Metadata (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.
| Aspect | Default Bridge | User-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 scaling | Limited | Free |
# 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/16and 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/8is huge,172.16.0.0/12is medium,192.168.0.0/16is small). Avoid192.168.0.0/24and192.168.1.0/24because 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.8fails. - 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 --> DThe 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.namelets you set the Linux bridge interface name. Useful for monitoring (tcpdump -i br-backend) or auditing (ip link showon 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 --> DBGood 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 --> B1Good 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 --> DB2Good 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 rmfails if containers are still connected. Disconnect the containers first (or remove them). In Compose,docker compose downautomatically removes the project’s networks.
User-defined Network vs Default Bridge: When to Use Which #
| Situation | Choice |
|---|---|
| Quick 1-container experiment | Default bridge (or no network at all) |
| Multi-container development | User-defined network |
| Production with many services | User-defined network + segmentation |
| Multi-host (Swarm, K8s) | User-defined network with overlay/calico drivers |
| No-network sandbox | None network |
| High performance | Host 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
frontendandbackendbecomes 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.