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
Create Scoped Networks and Keep the Password Local
Network SetupCheck 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.
docker network create --driver bridge --subnet 172.20.0.0/24 frontend-netdocker network create --driver bridge --internal --subnet 172.21.0.0/24 backend-net
mkdir -p secretsumask 077openssl rand -base64 32 > secrets/db_password.txtprintf '\nsecrets/\n' >> .gitignore❯ View Expected Console Output
frontend-net bridgebackend-net bridge internalIf 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
Keep the Database Off the Frontend Network
Docker ComposeSave 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-netdb: backend-netThe 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
Deploy the Containers and Review Their Networks
DeploymentStart 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.
docker compose up -ddocker compose psdocker 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
truebackend-net frontend-netbackend-net
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
Test Outbound Connectivity from the Internal Network
Isolation CheckRun 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.
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 unreachableStep 5: Optionally Give a Container a LAN Address with Macvlan
Create a Macvlan Network for a LAN Service
Optional LAN AttachmentMacvlan 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.
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 1ddocker inspect lan-test --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'❯ View Expected Console Output
192.168.1.201The 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.