## Intro

Docker networking is one of the most common sources of confusion when moving from a single container to multi-container applications. Misconfigured networks lead to containers that cannot reach each other, DNS resolution failures, or unintended exposure to the host. Rather than experimenting on a production host, you can build a safe local Docker networking lab to test configurations, troubleshoot behavior, and create repeatable learning workflows. A controlled lab helps you isolate problems, verify fixes, and build confidence before making changes elsewhere in your infrastructure.

This guide walks through setting up such a lab with concrete commands and expected outputs. We will create dedicated Docker bridge networks, launch containers with static IP addresses and custom hostnames, test connectivity and DNS resolution, demonstrate network isolation, and then examine common failure modes along with recovery steps. All examples are designed to run on a single Linux host with Docker installed, including Docker Desktop on Windows or macOS. The commands are safe for your local environment and do not affect other Docker networks or running containers.

A local lab is especially valuable when you are learning Docker networking concepts or troubleshooting an issue you suspect is network-related. Instead of guessing or reading through lengthy documentation, you can reproduce the problem in minutes, test fixes, and document what works. The ability to inspect network internals directly inside containers gives you immediate insight into how Docker manages networking under the hood.

## Version and Environment Inventory

Before starting, confirm your environment meets the following prerequisites:

- Docker Engine version 20.10 or later, or Docker Desktop equivalent.
- A Linux host (native or virtual machine) with a Bash shell. Windows Subsystem for Linux 2 (WSL2) works well, as does Docker Desktop with Linux containers.
- Basic familiarity with the Docker CLI and container lifecycle commands such as `docker run`, `docker exec`, and `docker rm`.
- No existing user-defined networks named `labnet1` or `labnet2`, and no containers named `lab1`, `lab2`, or `lab3` (we will create them).

To check your Docker version, run:

```bash
docker version --format '{{.Server.Version}}'
```

Expected output similar to:

```
20.10.21
```

Also confirm you can list current networks:

```bash
docker network ls
```

You should see output similar to the following (network IDs will vary):

```
NETWORK ID     NAME      DRIVER    SCOPE
abc123def456   bridge    bridge    local
ghi789jkl012   host      host      local
mno345pqr678   none      null      local
```

If you see the default `bridge`, `host`, and `none` networks, you are ready to proceed. This lab uses user-defined bridge networks, which provide automatic DNS resolution between containers and isolation from the host network stack.

## Safe Configuration Path

We will build the lab incrementally, verifying each step before moving to the next. This approach limits the scope of changes and makes rollback straightforward: if something goes wrong, you can remove only the resources you just created without affecting the rest of your environment.

### Step 1: Create an isolated user-defined bridge network

Create a network named `labnet1` with a specific subnet to avoid conflicts with existing networks and to allow static IP assignments:

```bash
docker network create \
  --driver bridge \
  --subnet 192.168.100.0/24 \
  --gateway 192.168.100.1 \
  labnet1
```

Expected output is the network ID, for example:

```
6f8d4c9b2a1e
```

Verify the network exists and has the correct configuration:

```bash
docker network inspect labnet1 --format '{{.Name}} {{.Driver}} {{.IPAM.Config}}'
```

Expected output:

```
labnet1 bridge [{192.168.100.0/24 192.168.100.1 map[]}]
```

This confirms that the network is a bridge driver with the specified subnet and gateway.

### Step 2: Launch two containers on the network with static IPs

We will run two Alpine Linux containers because they are lightweight and start quickly. Each container gets a static IP address and a custom hostname. Start the first container, `lab1`, with IP `192.168.100.10`:

```bash
docker run -dit \
  --name lab1 \
  --network labnet1 \
  --ip 192.168.100.10 \
  --hostname lab1 \
  alpine:latest
```

Expected output is the container ID, for example:

```
abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
```

Start the second container, `lab2`, with IP `192.168.100.20`:

```bash
docker run -dit \
  --name lab2 \
  --network labnet1 \
  --ip 192.168.100.20 \
  --hostname lab2 \
  alpine:latest
```

Verify both containers are running:

```bash
docker ps --filter "name=lab" --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
```

Expected output:

```
NAMES   IMAGE            STATUS
lab1    alpine:latest    Up 10 seconds
lab2    alpine:latest    Up 8 seconds
```

### Step 3: Test basic connectivity between containers

Execute a ping from `lab1` to `lab2` using its IP address:

```bash
docker exec lab1 ping -c 2 192.168.100.20
```

Expected output (timing values may vary):

```
PING 192.168.100.20 (192.168.100.20): 56 data bytes
64 bytes from 192.168.100.20: seq=0 ttl=64 time=0.123 ms
64 bytes from 192.168.100.20: seq=1 ttl=64 time=0.098 ms

--- 192.168.100.20 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.098/0.110/0.123 ms
```

Now test using the container name to verify DNS resolution:

```bash
docker exec lab1 ping -c 2 lab2
```

Expected output should be similar to the previous ping, showing that `lab2` resolved to `192.168.100.20`. This confirms that Docker's embedded DNS server works automatically on user-defined networks.

### Step 4: Expose a simple service and test from another container

Testing at the network layer is useful, but a practical lab should also verify service-level connectivity. We will run a lightweight web server in `lab2` using Python's built-in HTTP server. Alpine Linux does not include Python by default, so first install it inside `lab2`:

```bash
docker exec lab2 apk add --no-cache python3
```

Then start a simple HTTP server on port 8000 inside `lab2`:

```bash
docker exec -d lab2 python3 -m http.server 8000
```

The `-d` flag runs the server in detached mode inside the container. Verify the server is listening:

```bash
docker exec lab2 netstat -tuln | grep 8000
```

Alternatively, use `ss -tuln` if `netstat` is not available. Expected output shows a line with `0.0.0.0:8000` indicating the server is listening on all interfaces.

Now test from `lab1` using `wget` (available in Alpine by default) to fetch a file from the server. Since the server's root directory is `/` inside `lab2`, you can fetch `/etc/hostname` to see the hostname:

```bash
docker exec lab1 wget -qO- http://192.168.100.20:8000/etc/hostname
```

Expected output:

```
lab2
```

You can also use the container name in the URL to test DNS-based service discovery:

```bash
docker exec lab1 wget -qO- http://lab2:8000/etc/hostname
```

This should produce the same output, confirming that the embedded DNS resolves `lab2` and that the HTTP service is reachable.

### Step 5: Create a second network to demonstrate isolation

Create another network `labnet2` with a different subnet:

```bash
docker network create \
  --driver bridge \
  --subnet 10.10.10.0/24 \
  --gateway 10.10.10.1 \
  labnet2
```

Attach a new container `lab3` to `labnet2`:

```bash
docker run -dit \
  --name lab3 \
  --network labnet2 \
  --ip 10.10.10.10 \
  --hostname lab3 \
  alpine:latest
```

Now test connectivity from `lab1` (on `labnet1`) to `lab3` (on `labnet2`) by IP address:

```bash
docker exec lab1 ping -c 2 10.10.10.10
```

Expected output will show 100% packet loss, because the two networks are isolated unless you explicitly connect them. This demonstrates Docker network isolation: containers on different user-defined bridge networks cannot communicate directly, even if they know each other's IP addresses.

### Step 6: Connect a container to multiple networks

If needed, you can connect `lab1` to `labnet2` as well, allowing it to communicate with `lab3`. This is a common pattern for multi-tier applications where a container needs to access services on different networks.

```bash
docker network connect labnet2 lab1
```

After connecting, `lab1` receives an additional IP address on `labnet2`. Docker automatically assigns the next available IP in that subnet, for example `10.10.10.2`. Verify by inspecting the network interfaces inside `lab1`:

```bash
docker exec lab1 ip addr show
```

Look for an interface (likely `eth1`) with an IP address in the `10.10.10.0/24` subnet. Then test ping again from `lab1` to `lab3`:

```bash
docker exec lab1 ping -c 2 10.10.10.10
```

Now it should succeed, showing connectivity after attaching to the same network. This demonstrates how Docker allows containers to be part of multiple networks, which is useful for controlled communication between different tiers.

## Verification and Diagnostics

Proper verification ensures your lab behaves as expected. Use the following commands to inspect network configuration and connectivity.

### Inspect network details

```bash
docker network inspect labnet1
```

This returns a JSON object with the subnet, gateway, containers attached, and their IP addresses. For a concise view, use:

```bash
docker network inspect labnet1 --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
```

Expected output:

```
lab1 192.168.100.10/24
lab2 192.168.100.20/24
```

This confirms IP assignments.

### Check DNS resolution

From `lab1`, resolve the hostname `lab2` using `nslookup` (available via BusyBox in Alpine):

```bash
docker exec lab1 nslookup lab2
```

Expected output similar to:

```
Server:    127.0.0.11
Address:   127.0.0.11:53

Name:      lab2
Address:   192.168.100.20
```

The DNS server at `127.0.0.11` is Docker's embedded DNS, which runs inside each container on user-defined networks.

### Verify isolation

Check that containers on separate networks cannot reach each other without explicit connection. From `lab1` (if not connected to `labnet2`), ping `lab3`'s IP and confirm failure. A successful ping after connecting demonstrates the change.

### Examine container network interfaces

```bash
docker exec lab1 ip addr show
```

Look for the `eth0` interface with the assigned IP. You can also check routing:

```bash
docker exec lab1 ip route
```

Expected output shows the default gateway via `192.168.100.1`, along with the subnet route.

## Common Pitfalls and How to Avoid Them

Even in a controlled lab, certain mistakes are common. Recognizing them early can save time and frustration.

### Pitfall 1: Using the default bridge network

Many beginners place containers on the default `bridge` network and then wonder why DNS resolution fails. The default bridge network does not provide automatic DNS resolution between containers; you must use legacy `--link` flags (deprecated) or, better, create a user-defined bridge network. **Why it happens:** Docker's default bridge is designed for backward compatibility and does not have the embedded DNS server or automatic service discovery that user-defined networks provide. **How to avoid:** Always create a dedicated user-defined network for your applications. This lab demonstrates the correct approach.

### Pitfall 2: Static IP conflicts

Assigning a static IP that is already in use by another container on the same network causes an error such as `Address already in use`. **Why it happens:** Docker tracks IP allocations in its IPAM (IP Address Management) database, and each IP on a subnet must be unique. **How to avoid:** Before assigning a static IP, inspect the network to see existing allocations using `docker network inspect`. Alternatively, use a subnet with enough addresses and plan your IP scheme. If a conflict occurs, choose a different IP or remove the conflicting container.

### Pitfall 3: Forgetting to connect a container to the second network

When testing multi-network connectivity, it is easy to forget that a container is not automatically attached to all networks. Pinging across networks will fail until you explicitly connect the container. **Why it happens:** Docker networks are isolated by default, and containers only join networks they are explicitly connected to. **How to avoid:** Always verify network membership with `docker network inspect` before expecting cross-network communication. If needed, use `docker network connect` as shown in Step 6.

### Pitfall 4: Misinterpreting DNS resolution failures

If `ping lab2` returns `Name or service not known`, it is tempting to think Docker's DNS is broken. **Why it happens:** Usually, the containers are on the default bridge network, or the container name is misspelled, or the containers are on different user-defined networks. **How to avoid:** Check the network membership of both containers with `docker network inspect`. Ensure they are on the same user-defined network. If not, connect them or create a new network and attach both.

### Pitfall 5: Leaving test containers and networks running

After experimenting, you may forget to clean up, leaving orphaned containers and networks that consume resources and clutter your environment. **Why it happens:** It is easy to move on to the next task without deleting lab resources. **How to avoid:** Adopt a habit of tearing down the lab when finished, as described in the cleanup section below. You can also use labels or naming conventions to identify lab resources easily.

## Failure Modes and Recovery

Understanding common failure modes helps you recover quickly and keeps your lab productive.

### IP address conflict

If you attempt to assign a static IP already in use, Docker returns an error like:

```
Error response from daemon: Address already in use
```

Recovery: Choose a different IP or inspect existing allocations with `docker network inspect labnet1` to find the conflict. Remove the conflicting container if necessary and retry.

### Container cannot resolve DNS names

If DNS resolution fails (e.g., `ping lab2` gives `Name or service not known`), check if the containers are on the same user-defined network. DNS resolution only works on user-defined networks, not the default bridge. Verify with `docker network inspect` and ensure both containers are attached.

Recovery: Connect both containers to the same user-defined network using `docker network connect`.

### Network isolation breaks unexpectedly

If containers that should be isolated suddenly can communicate, check if they are connected to a common network (e.g., both attached to the same bridge). Use `docker network inspect` to review connections.

Recovery: Disconnect the unintended network connection with `docker network disconnect <network> <container>`.

### Container loses connectivity after network changes

Modifying network settings (e.g., disconnecting a container) can disrupt active connections.

Recovery: Reconnect the container to the network if needed, or restart the container with `docker restart <container>` to re-establish networking. In some cases, recreating the container may be necessary.

### Port publishing misconfiguration

If you expose a service inside a container but cannot reach it from the host, you may have forgotten to publish the port with `-p`. Without port publishing, the service is only accessible from other containers on the same network.

Recovery: Re-run the container with the appropriate `-p` flag, or use `docker network connect` to allow access from another container on the same network without exposing it to the host.

### Rollback strategy

Because the lab is isolated, rollback often means removing the test networks and containers. Save your setup commands in a script for reproducibility. To tear down everything:

```bash
docker rm -f lab1 lab2 lab3
docker network rm labnet1 labnet2
```

Verify removal with `docker network ls` and `docker ps -a`.

## Operations Checklist

Use this checklist to run the lab reliably and extend it safely. Each step includes a command or check and the expected outcome. For each major action, assign an owner (yourself in a personal lab, or a specific team member in a shared environment) and review the results before proceeding. In a team setting, revisit the lab setup monthly to ensure it still meets current needs and that no conflicts have arisen.

| Step | Action | Command / Check | Owner | Review Frequency |
|------|--------|-----------------|-------|------------------|
| 1 | Verify Docker is running | `docker info` | Lab operator (e.g., Alex) | At each lab session |
| 2 | List existing networks | `docker network ls` (ensure no conflict with lab names) | Lab operator | At each lab session |
| 3 | Create lab network with subnet | `docker network create --subnet 192.168.100.0/24 --gateway 192.168.100.1 labnet1` | Lab operator | At each lab build |
| 4 | Launch test containers with static IPs | `docker run -dit --name lab1 --network labnet1 --ip 192.168.100.10 alpine:latest` (repeat for lab2) | Lab operator | At each lab build |
| 5 | Test IP connectivity | `docker exec lab1 ping -c 2 192.168.100.20` (expect 0% loss) | Lab operator | After container start |
| 6 | Test DNS-based connectivity | `docker exec lab1 ping -c 2 lab2` (expect success) | Lab operator | After container start |
| 7 | Verify isolation | Ping `10.10.10.10` from lab1 before connecting (expect failure) | Lab operator | When testing isolation |
| 8 | Connect to additional network if needed | `docker network connect labnet2 lab1` | Lab operator | When multi-network needed |
| 9 | Inspect network configuration | `docker network inspect labnet1 --format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'` | Lab operator | After any network change |
| 10 | Clean up when done | `docker rm -f lab1 lab2 lab3 && docker network rm labnet1 labnet2` | Lab operator | End of session |

Repeat these steps as needed for experiments. Document any variations and their outcomes for future reference. Maintaining a lab journal helps you track what you have tested and the results, making it easier to reproduce or share findings with your team.

## Conclusion

A local Docker networking lab provides a safe, repeatable environment to learn and test network configurations. In this guide, we created isolated bridge networks, deployed containers with static IPs and hostnames, verified connectivity and DNS, demonstrated network isolation, and discussed common failure modes with recovery steps. By following the operations checklist and avoiding the pitfalls described, you can extend the lab to test more complex scenarios such as overlay networks, network policies, or multi-host setups.

Start with a narrow pilot network, inspect behavior locally, and then apply verified changes to your development or production environments with confidence. The skills you build in this lab will transfer directly to real-world Docker deployments, helping you troubleshoot networking issues faster and design more robust container architectures.