Skip to content

Isolate Docker Services with Bridge and Macvlan Networks

Docker’s default bridge puts unrelated containers into one shared network. User-defined bridge networks let you scope which containers can communicate and provide service-name DNS. An internal bridge removes normal external routing for attached containers, but it is not a per-port firewall and does not prevent the Docker host from reaching them.

This example puts a web container on a frontend and backend network and a database only on the backend network. The Nginx container demonstrates network membership; it is not configured as a database application. Replace it with your actual application image when following this pattern.


Step 1: Create the Networks and a Database Secret

01

Create Scoped Networks and Keep the Password Local

Network Setup

Check that the example subnets do not overlap with your LAN, VPNs, or existing Docker networks. Create the backend as an internal bridge, then generate a database password file that Compose can mount as a secret. Add the local secret folder to the deployment directory’s ignore file.

Terminal window
docker network create --driver bridge --subnet 172.20.0.0/24 frontend-net
docker network create --driver bridge --internal --subnet 172.21.0.0/24 backend-net
mkdir -p secrets
umask 077
openssl rand -base64 32 > secrets/db_password.txt
printf '\nsecrets/\n' >> .gitignore
❯ View Expected Console Output
frontend-net bridge
backend-net bridge internal

If you have already created these network names, inspect them with docker network inspect instead of trying to create them again.


Step 2: Attach Services to the Required Networks

02

Keep the Database Off the Frontend Network

Docker Compose

Save this as compose.yaml in the directory containing secrets/db_password.txt. The database is not published to the host, and its password is supplied through a Compose secret instead of a literal in the YAML file.

services:
web-app:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
networks:
- frontend-net
- backend-net
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
networks:
- backend-net
secrets:
- db_password
networks:
frontend-net:
external: true
backend-net:
external: true
secrets:
db_password:
file: ./secrets/db_password.txt
❯ View Expected Console Output
web-app: frontend-net, backend-net
db: backend-net

The example exposes Nginx on the Docker host’s loopback address at http://127.0.0.1:8080. Publish it through a trusted reverse proxy if remote users need access.


Step 3: Start the Stack and Inspect Network Membership

03

Deploy the Containers and Review Their Networks

Deployment

Start the Compose project, then inspect each container’s network attachments. Only containers joined to a given user-defined bridge can communicate over that bridge. Services sharing a bridge can generally reach one another’s exposed container ports, so use application authentication and additional firewall controls where needed.

Terminal window
docker compose up -d
docker compose ps
docker network inspect backend-net --format '{{.Internal}}'
docker inspect "$(docker compose ps -q web-app)" --format '{{range $name, $net := .NetworkSettings.Networks}}{{$name}} {{end}}'
docker inspect "$(docker compose ps -q db)" --format '{{range $name, $net := .NetworkSettings.Networks}}{{$name}} {{end}}'
❯ View Expected Console Output
<project>-web-app-1 ... Up
<project>-db-1 ... Up
true
backend-net frontend-net
backend-net
Terminal showing Docker Compose services attached to frontend and internal backend networks, followed by a blocked outbound connectivity check

Figure 1: Inspect the Compose network attachments, then confirm the internal backend has no normal outbound route.


Step 4: Verify the Internal Backend Has No External Route

04

Test Outbound Connectivity from the Internal Network

Isolation Check

Run a temporary test container on the backend network and try a direct IP ping. A failure with “Network is unreachable” is expected for an internal network. This tests outbound routing; it does not prove that the host cannot reach the container or that every same-network port is blocked.

Terminal window
docker run --rm --network backend-net busybox:stable ping -c 2 -W 2 1.1.1.1
❯ View Expected Console Output
ping: sendto: Network is unreachable

Step 5: Optionally Give a Container a LAN Address with Macvlan

05

Create a Macvlan Network for a LAN Service

Optional LAN Attachment

Macvlan gives a container a separate MAC and address on a physical LAN. Use an unused range outside the router’s DHCP pool, choose the correct parent interface, and confirm that the physical switch and virtualization layer allow additional MAC addresses.

Terminal window
ip -br link
# Replace enp3s0 and the addresses with values from your LAN plan.
PARENT_IF="enp3s0"
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
--ip-range=192.168.1.200/29 \
-o parent="$PARENT_IF" \
lan-macvlan
docker run -d --name lan-test \
--network lan-macvlan \
--ip=192.168.1.201 \
busybox:stable sleep 1d
docker inspect lan-test --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
❯ View Expected Console Output
192.168.1.201

The Docker host normally cannot communicate directly with its Macvlan containers without an additional host-side Macvlan interface and route. Remove the example container after verifying with docker rm -f lan-test.

References

Comments