Docker Networking Explained: Hands-On Learning with Bridge Networks, Network Isolation, and Host Mode
As I continue building my DevOps skills, I’m learning that containers do not operate in isolation from networking concerns. Even when individual containers run successfully, the overall application still depends on how those containers communicate with one another and how they are exposed to external users.
In Assignment 03, I explored Docker Networking through a practical scenario involving Nginx, BusyBox, and MongoDB. The objective was to understand Docker’s default bridge network, custom bridge networks, communication across multiple networks, and host networking.
Rather than developing an application, this exercise focused on the networking principles behind containerized services: connectivity, service discovery, isolation, and controlled external access.
Here is the workflow, the commands involved, and the key lessons from the assignment.
1. Understanding the network design
A typical application may have a frontend, a backend, and a database. These components need to communicate, but they do not all need to be accessible from the internet.
For this assignment, I explored four networking scenarios:
- Default bridge: Run a standalone Nginx container and publish its web port.
- Custom bridge: Connect a web container and client container using container-name-based service discovery.
- Multiple bridge networks: Separate frontend-to-backend communication from backend-to-database communication.
- Host networking: Run Nginx using the host machine’s network stack.
The objective was to understand not only how to establish connectivity, but also how to restrict unnecessary communication.
2. Task One: Deploy Nginx using the default bridge network
Step 1: Inspect Docker networks
I started by listing the networks available in Docker:
echo "Full Name: <YOUR FULL NAME>"
docker network lsThe expected output includes Docker’s built-in networks:
bridgehostnone
The default bridge network is commonly used when a container does not explicitly join another network.
Step 2: Pull the Nginx image
I downloaded the lightweight Nginx Alpine image:
echo "Full Name: <YOUR FULL NAME>"
docker pull nginx:alpineUsing a standard image kept the focus on networking rather than application development.
Step 3: Start the web container
I launched Nginx and mapped port 80 on the VM to port 80 inside the container:
docker run -d \
--name myweb \
-p 80:80 \
nginx:alpineI then checked the container:
echo "Full Name: <YOUR FULL NAME>"
docker psThe port mapping should resemble:
0.0.0.0:80->80/tcpThis indicates that Docker has published the container’s port 80 on the host’s port 80. The exact output may differ depending on the host configuration.
Step 4: Verify external access
I opened the VM’s public IP address in a browser:
http://<YOUR-VM-PUBLIC-IP>The expected result is the Nginx welcome page.
For access to work, port 80 must also be allowed by the relevant cloud security group, network security group, or host firewall. Publishing a Docker port does not automatically override cloud-level network restrictions.
What this task demonstrates: A container can serve a web page externally when the appropriate port is published and the surrounding network rules permit access.
3. Task Two: Connect containers through a custom bridge network
The next task focused on service discovery.
Instead of relying on the default bridge network, I created a custom bridge network and attached two containers to it.
Step 1: Clean up the first task
After capturing the required evidence, I removed the original container:
docker rm -f myweb 2>/dev/null || trueThis removes the named container if it exists. It does not remove the Nginx image, which can be reused.
Step 2: Create a custom network
docker network create mynetworkecho "Full Name: <YOUR FULL NAME>"
docker network lsThe expected result is a network named mynetwork using the bridge driver.
Step 3: Start the web and client containers
I started an Nginx container without publishing a host port:
docker run -d \
--name web \
--network mynetwork \
nginx:alpineNext, I started a BusyBox client on the same network:
docker run -d \
--name client \
--network mynetwork \
busybox:1.36 \
sleep infinityBoth containers should now be running on mynetwork. Neither requires a published host port for communication with the other.
Step 4: Test service discovery by container name
From the client container, I tested access to Nginx:
echo "Full Name: <YOUR FULL NAME>"
docker exec client wget -qO- http://webThe expected output is the HTML returned by the Nginx welcome page.
This test demonstrates an important Docker feature: containers on the same user-defined bridge network can discover one another by container name.
Rather than manually looking up a container’s IP address, the client can use the name web.
Step 5: Inspect network membership
echo "Full Name: <YOUR FULL NAME>"
docker network inspect mynetworkThe output should show both web and client connected to the custom network.
What this task demonstrates: A custom bridge network supports convenient container-name-based service discovery and internal communication without requiring public port mappings.
4. Task Three: Implement multi-network isolation
This was the most important network-design exercise because it introduced separation between application tiers.
The assignment specified two networks:
frontend-network
- Connects
frontendandbackend. - Supports frontend-to-backend communication.
backend-network
- Connects
backendanddb. - Supports backend-to-database communication.
The backend is the only container connected to both networks. This arrangement allows the frontend and database to remain on separate networks.
Step 1: Remove the previous task’s containers and network
docker rm -f web client 2>/dev/null || true
docker network rm mynetwork 2>/dev/null || trueI would run cleanup commands only after capturing the required evidence and confirming that the named resources belong to this exercise.
Step 2: Create both networks
docker network create frontend-network
docker network create backend-networkecho "Full Name: <YOUR FULL NAME>"
docker network lsBoth networks should appear in the network listing.
Step 3: Start MongoDB on the backend network
docker run -d \
--name db \
--network backend-network \
mongo:7.0Notice that no -p option is used.
MongoDB’s port 27017 is not published on the VM. The database is intended to be accessible through its Docker network, rather than directly through a public host port.
Step 4: Start the backend and connect it to both networks
docker run -d \
--name backend \
--network backend-network \
nginx:alpineThen connect the backend to the frontend network:
docker network connect frontend-network backendThe backend now belongs to both backend-network and frontend-network.
Get Tope Adedavids’s stories in your inbox
Join Medium for free to get updates from this writer.
An important detail is that this container is running Nginx as a networking demonstration, not as a fully implemented backend application. Docker membership alone does not make Nginx act as an application-layer gateway between the two networks.
Step 5: Start the frontend
docker run -d \
--name frontend \
--network frontend-network \
-p 80:80 \
nginx:alpineThen inspect the running containers:
echo "Full Name: <YOUR FULL NAME>"
docker psThe expected configuration is:
frontendhas port 80 published.backendhas no published host port.dbhas no published host port.
This reflects the intended exposure model: the frontend is accessible externally, while the backend and database remain internal.
Step 6: Verify the network memberships
Inspect the frontend network:
echo "Full Name: <YOUR FULL NAME>"
docker network inspect frontend-networkExpected members: frontend and backend.
Inspect the backend network:
echo "Full Name: <YOUR FULL NAME>"
docker network inspect backend-networkExpected members: backend and db.
These inspections are important because they provide evidence of the actual network configuration, not merely that the containers are running.
Step 7: Test frontend-to-backend communication
echo "Full Name: <YOUR FULL NAME>"
docker exec frontend wget -qO- http://backendThe expected output is the Nginx welcome page HTML.
This communication works because frontend and backend share frontend-network. Docker's embedded DNS resolves the container name backend on that network.
Step 8: Test backend-to-database connectivity
The assignment uses Netcat to test whether the backend can connect to MongoDB’s port.
First, install the required testing utility inside the backend container:
docker exec backend apk add --no-cache netcat-openbsdThen run:
echo "Full Name: <YOUR FULL NAME>"
docker exec backend nc -zv -w 5 db 27017The expected result is a successful TCP connection to db on port 27017, provided MongoDB has finished starting and is listening on that port.
If the first test fails, wait for MongoDB to finish initializing and try again.
This test verifies network-level connectivity. It does not prove that database authentication, application queries, or the database’s full functionality work.
Step 9: Test frontend-to-database isolation
Next, I used Netcat in the frontend container to test whether it could reach MongoDB:
docker exec frontend apk add --no-cache netcat-openbsdThen:
echo "Full Name: <YOUR FULL NAME>"
docker exec frontend sh -c \
'nc -zv -w 5 db 27017 && echo "Unexpected success" || echo "Expected result: frontend cannot reach db"'The intended result is:
Expected result: frontend cannot reach dbBecause the frontend is not connected to backend-network, it cannot resolve the database's container name through that network. Consequently, the connection should fail.
This is the central lesson of the task: the frontend can reach the backend, and the backend can reach the database, but that does not automatically give the frontend direct access to the database.
The separation reduces unnecessary communication paths and helps enforce a least-privilege network design.
Step 10: Verify public access to the frontend
I opened the VM’s public IP in the browser:
http://<YOUR-VM-PUBLIC-IP>The expected result is the Nginx welcome page from the frontend container.
The backend and database remain without published host ports. Their network membership and lack of port publishing provide the intended internal-only configuration, subject to the host’s other network and security settings.
5. Task Four: Explore Docker host networking
The final task compared bridge networking with host network mode.
Step 1: Clean up the multi-network containers
After capturing the required evidence, I removed the named containers and networks:
docker rm -f frontend backend db 2>/dev/null || true
docker network rm frontend-network backend-network 2>/dev/null || trueStep 2: Start Nginx in host network mode
Before running this command, port 80 on the VM must be available.
docker run -d \
--name fastapp \
--network host \
nginx:alpineUnlike the bridge examples, this command does not use -p 80:80.
In host mode, the container shares the host’s network stack. Nginx listens on the host’s port 80 directly, so a separate Docker port mapping is not required.
Step 3: Verify the network mode
echo "Full Name: <YOUR FULL NAME>"
docker psecho "Full Name: <YOUR FULL NAME>"
docker inspect fastapp | grep '"NetworkMode"'The inspection output should include:
"NetworkMode": "host",If the container fails to start, check whether another service is already listening on port 80.
Step 4: Verify browser access
I would then open:
http://<YOUR-VM-PUBLIC-IP>The expected result is the Nginx welcome page, provided the container is running and the relevant network rules permit access.
Step 5: Stop and remove the container
After capturing the required evidence:
echo "Full Name: <YOUR FULL NAME>"
docker stop fastapp
docker rm fastappThis completes the host-networking exercise and removes the named container created for it.
6. Bridge networking versus host networking
The assignment illustrates several important differences.
FeatureBridge networkingHost networkingNetwork isolationContainer uses a separate Docker networkContainer shares the host network stackPort publishingTypically uses -p for external accessNo Docker port mapping is neededContainer-name discoveryAvailable on user-defined bridge networksNot provided by host mode in the same wayPort conflictsHost port conflicts can occur when publishing portsApplications compete directly for host portsTypical useIsolated services and application tiersSpecific workloads that need host networking behavior
The default bridge network is useful for simple standalone containers, while custom bridge networks provide better service discovery and more deliberate communication boundaries.
Host networking removes the normal network separation between the container and the host. That can be useful in particular scenarios, but it requires careful consideration of port conflicts and security.
7. Troubleshooting considerations
During a practical networking exercise, a failed connection does not always mean the network design is wrong. The issue could be caused by startup timing, a missing utility, an incorrect network attachment, or an unavailable host port.
Here are the first checks I would perform.
Check container status:
docker ps -aInspect container logs:
docker logs backend
docker logs dbInspect network membership:
docker network inspect frontend-network
docker network inspect backend-networkCheck published ports:
docker port frontendFor the database test, confirm that MongoDB has finished starting. For the host-network test, check whether port 80 is already occupied. For the service-discovery test, verify that the relevant containers share the intended user-defined bridge network.
The assignment also requires screenshots to include the learner’s full name in terminal evidence. Browser screenshots showing the standard Nginx page should have the name added as a caption in the submission document, as instructed.
8. What I learned
This assignment helped me understand that Docker networking is not simply about allowing containers to communicate. It is also about controlling where communication is permitted.
The most important lessons were:
- Default bridge: publish a port when external access is required.
- Custom bridge: use container names for service discovery between connected containers.
- Multiple bridge networks: separate application tiers and restrict unnecessary communication.
- Host networking: understand that the container shares the host network stack and does not need Docker port mapping.
- Network validation: use
docker network inspect,docker ps, and connectivity tests to verify configuration rather than relying on assumptions.
I also learned the difference between network-level connectivity and application-level functionality. A successful TCP connection to MongoDB’s port does not prove that an application can authenticate or execute a database query.
Conclusion
Working through Docker Networking strengthened my understanding of how containerized services can be connected, isolated, and exposed.
The multi-network scenario was particularly valuable because it demonstrated a practical security principle: the frontend does not need direct access to the database when the intended design only requires frontend-to-backend and backend-to-database communication.
The next step is to apply these principles in more complete application stacks, where network boundaries work alongside application authentication, secrets management, monitoring, and deployment automation.
I’m continuing to build my DevOps foundation through hands-on assignments, focusing on understanding not just how to run containers, but how to design and verify the systems around them.
#Docker #DockerNetworking #DevOps #Containerization #Linux #CloudEngineering #CloudNative #CyberSecurity #DevOpsLearning #LearningByDoing
P.S. This post is part of the DevOps Micro Internship (DMI) with Agentic AI — Cohort 3 — by Pravin Mishra. My graded progress is public: https://dmi.pravinmishra.com/s/accelage.html · Start your DevOps journey: https://dmi.pravinmishra.com/?utm_source=student&utm_medium=ps-blog&utm_campaign=cohort3








