DEV Community

David Tio
David Tio

Posted on • Originally published at blog.dtio.app

Docker Networking Explained: Connect Your Containers (2026)

๐ŸŒ Docker Networking Explained: Connect Your Containers (2026)

Quick one-liner: Connect your containers to each other and the outside world. Learn bridge networks, port mapping, DNS, and container-to-container communication.


๐Ÿค” Why This Matters

In the last post, you got Redis + RedisInsight working. Your data persisted across a version upgrade, your config was pre-loaded via bind mount, and your Redis data survived the container being deleted and recreated.

But remember what happened when we tried to connect RedisInsight to Redis?

# echo PING | nc dtredis86 6379
nc: bad address 'dtredis86'
Enter fullscreen mode Exit fullscreen mode

Hostname didn't resolve. We had to fall back to docker inspect to find the Redis IP (172.17.0.2) and connect that way. That works for a quick test, but it's not how you want to run anything real:

  • ๐Ÿ—๏ธ If you restart the container, the IP changes
  • ๐Ÿ“‹ You'd need to manually update configs every time
  • ๐Ÿ”’ No network isolation โ€” anything can reach anything
  • ๐ŸŒ No way to access containers from your browser

Today, we're fixing all of that. We'll cover:

  • Port mapping โ€” exposing container ports to your browser
  • Custom bridge networks โ€” containers that find each other by name
  • DNS resolution โ€” no more docker inspect to find IPs
  • Network isolation โ€” keeping your services segmented

By the end, you'll have RedisInsight talking to Redis by name, accessible from your browser, on a clean isolated network. The right way.


โœ… Prerequisites

  • Docker installed (rootless mode recommended)
  • Ep 1-5 completed โ€” you know volumes, environment variables, and bind mounts
  • 5 minutes to wire up your first multi-container setup

๐Ÿ—๏ธ The Default Bridge Network

When you run a container without specifying a network, Docker puts it on the default bridge network:

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a1b2c3d4e5f6   bridge    bridge    local
d4e5f6a1b2c3   host      host      local
e5f6a1b2c3d4   none      null      local
Enter fullscreen mode Exit fullscreen mode

Containers on the default bridge can reach each other by IP address, but not by name. That's exactly what you saw with RedisInsight and Redis in the last post. Let's reproduce it with the same setup โ€” the Redis + RedisInsight you ended Ep 5 with:

$ docker run -d --rm --name dtredis86 \
    -v redisdata:/data \
    -v ./redis.conf:/etc/redis/redis.conf \
    redis:8.6 redis-server /etc/redis/redis.conf

$ docker run -d --rm --name redisinsight \
    -v ./data/ri:/data \
    -e RI_PRE_SETUP_DATABASES_PATH=/data/config.json \
    -e RI_ACCEPT_TERMS_AND_CONDITIONS=true \
    redis/redisinsight
Enter fullscreen mode Exit fullscreen mode

No -p flag โ€” so you can't reach it from your browser, just like in Ep 5.

Try resolving dtredis86 from the RedisInsight container:

$ docker exec redisinsight sh -c "echo PING | nc dtredis86 6379"
nc: bad address 'dtredis86'
Enter fullscreen mode Exit fullscreen mode

Hostname resolution doesn't work on the default bridge. You'd need to inspect the container to find its IP, then connect that way โ€” exactly what you saw in Ep 5 when we had to fall back to 172.17.0.2.

This is fragile. If you restart the containers, the IPs change. Let's fix this properly.


๐ŸŒ Port Mapping: Expose Containers to the Host

In Ep 5, you ran RedisInsight without port mapping and couldn't reach it at http://localhost:5540. The container was alive inside, but your host had no way in.

Let's add port mapping to the same RedisInsight setup from Ep 5:

$ docker stop redisinsight

$ docker run -d --rm --name redisinsight \
    -p 8080:5540 \
    -v ./data/ri:/data \
    -e RI_PRE_SETUP_DATABASES_PATH=/data/config.json \
    -e RI_ACCEPT_TERMS_AND_CONDITIONS=true \
    redis/redisinsight
Enter fullscreen mode Exit fullscreen mode

The ./data/ri directory should already have the correct chown from Ep 5. If not:

$ sudo chown -R 1001000:1001000 ./data/ri
Enter fullscreen mode Exit fullscreen mode
Flag What It Does
-p 8080:5540 Map host port 8080 โ†’ container port 5540 (RedisInsight)

Now open http://localhost:8080 โ€” RedisInsight loads. ๐ŸŽ‰

The format is always host_port:container_port:

$ docker run -d --rm --name dtredis2 -p 6380:6379 redis
Enter fullscreen mode Exit fullscreen mode

Now here's a practical exercise: open RedisInsight at http://localhost:8080 and try adding dtredis2 as a new connection:

What you try Result Why
redis://dtredis2:6379 โŒ DNS fails Hostnames don't resolve on default bridge
redis://127.0.0.1:6379 โŒ Connection refused That's the RedisInsight container's own loopback
redis://127.0.0.1:6380 โŒ Connection refused Same reason โ€” localhost inside a container

The only way to reach dtredis2 from RedisInsight is via your host's IP address. Get it with:

$ hostname -I
<your_host_ip>
Enter fullscreen mode Exit fullscreen mode

Then in RedisInsight: redis://<your_host_ip>:6380 โ€” it connects, but it's ugly. If your host IP changes, you're updating configs everywhere.

RedisInsight connected to Redis via host IP address

It works โ€” but we're using a raw IP address that could change at any time.

Verify the port mapping:

$ docker port dtredis2
6379/tcp -> 0.0.0.0:6380

There has to be a better way. ๐Ÿ‘‡

Rootless note: In rootless mode, you can't bind to ports below 1024. So -p 80:5540 won't work. Use -p 8080:5540 or higher. If you need port 80, put a rootful reverse proxy (nginx, caddy, traefik) in front.

Clean up before moving on:

$ docker stop dtredis86 redisinsight
Enter fullscreen mode Exit fullscreen mode

๐ŸŒ‰ Custom Bridge Networks: The Right Way

Create a custom bridge network. Containers on the same custom network can reach each other by name โ€” Docker provides built-in DNS resolution.

$ docker network create dtapp
Enter fullscreen mode Exit fullscreen mode

Run Redis and RedisInsight on this network:

$ docker run -d --rm --name dtredis --network dtapp redis
$ docker run -d --rm --name ri --network dtapp \
    -p 8080:5540 \
    -v ./data/ri:/data \
    -e RI_PRE_SETUP_DATABASES_PATH=/data/config.json \
    -e RI_ACCEPT_TERMS_AND_CONDITIONS=true \
    redis/redisinsight
Enter fullscreen mode Exit fullscreen mode

First, verify the connection from the command line:

$ docker exec ri sh -c "echo PING | nc dtredis 6379"
Enter fullscreen mode Exit fullscreen mode

It resolves. โœ… No IP inspection, no manual config. The name works automatically.

Now open http://localhost:8080 โ€” RedisInsight loads, and you can add dtredis:6379 as a new connection. No IP addresses needed.

RedisInsight connected to Redis via hostname on custom bridge network

Connect an existing container to a network:

$ docker run -d --rm --name dtredis2 redis
Enter fullscreen mode Exit fullscreen mode

This container is on the default bridge. Connect it to dtapp:

$ docker network connect dtapp dtredis2
Enter fullscreen mode Exit fullscreen mode

Now dtredis2 is on both networks. Verify ri can reach it:

$ docker exec ri sh -c "echo PING | nc dtredis2 6379"
PING
+PONG
Enter fullscreen mode Exit fullscreen mode

Redis responds by hostname. โœ…


๐Ÿงช Exercise: CloudBeaver + PostgreSQL on a Custom Network

Let's wire up a real multi-container stack โ€” PostgreSQL for the database, CloudBeaver as the web UI.

Part 1: Create the Network and Run PostgreSQL

$ docker network create dtstack
$ docker run -d --rm --name dtpg \
    --network dtstack \
    -e POSTGRES_PASSWORD=docker \
    -e POSTGRES_DB=testdb \
    -v pgdata:/var/lib/postgresql/data \
    --tmpfs /var/run/postgresql \
    postgres:17
Enter fullscreen mode Exit fullscreen mode

Rootless note: PostgreSQL tries to chmod /var/run/postgresql for its PID file at startup. Rootless containers can't do that. The --tmpfs flag gives it a writable temp directory on that path without root.

Part 2: Run CloudBeaver on the Same Network

$ docker run -d --rm --name cloudbeaver \
    --network dtstack \
    -p 8978:8978 \
    -v cbdata:/opt/cloudbeaver/workspace \
    dbeaver/cloudbeaver:latest
Enter fullscreen mode Exit fullscreen mode

Wait ~30 seconds for it to start, then open http://localhost:8978 in your browser.

It loads. ๐ŸŽ‰ Now add a connection in the CloudBeaver UI:

  • Server: dtpg
  • Port: 5432
  • Username: postgres
  • Password: docker
  • Database: testdb

CloudBeaver successfully connected to PostgreSQL via hostname dtpg:5432

Part 4: Check the Network

$ docker network inspect dtstack
Enter fullscreen mode Exit fullscreen mode

You'll see both containers listed under Containers:

"Containers": {
    "abc123...": {
        "Name": "dtpg",
        "IPv4Address": "172.18.0.2/16"
    },
    "def456...": {
        "Name": "cloudbeaver",
        "IPv4Address": "172.18.0.3/16"
    }
}
Enter fullscreen mode Exit fullscreen mode

Two containers. One network. DNS works between them. Port mapping gives you browser access. This is how multi-container apps should run.

Part 5: Clean Up

$ docker stop dtpg cloudbeaver
$ docker volume rm pgdata cbdata
$ docker network rm dtstack
Enter fullscreen mode Exit fullscreen mode

The Problem With What We Just Did

Look at what it took to wire up two containers:

$ docker network create dtstack
$ docker run -d --rm --name dtpg --network dtstack -e POSTGRES_PASSWORD=docker -e POSTGRES_DB=testdb -v pgdata:/var/lib/postgresql/data --tmpfs /var/run/postgresql postgres:17
$ docker run -d --rm --name cloudbeaver --network dtstack -p 8978:8978 -v cbdata:/opt/cloudbeaver/workspace dbeaver/cloudbeaver:latest
Enter fullscreen mode Exit fullscreen mode

Three commands. That's not the problem.

The problem is:

  • The second command is a 150-character wall of flags, environment variables, and volume mounts
  • One typo in --tmpfs and PostgreSQL silently starts but fails to accept connections
  • Forget --network dtstack and the containers won't find each other
  • Tear it down and rebuild? Type it all again
  • What about when you have 5 containers? 10? Shared volumes? Secret files? Restart policies?

There's a better way.


๐Ÿ“‹ Network Comparison

Network Type DNS Resolution Isolation Use Case
default bridge โŒ No (IP only) Low Quick testing
custom bridge โœ… Yes (by name) Medium Multi-container apps
host N/A None Performance (container uses host network)
none N/A Total Air-gapped containers

๐Ÿ‘‰ Coming up: We'll replace every single command above with a clean YAML file. Docker Compose โ€” define your entire multi-container stack and launch it with one command.

๐Ÿ“š Want More? This guide covers the basics from Chapter 5: Docker Networking in my book, "Levelling Up with Docker" โ€” 14 chapters of practical, hands-on Docker guides.

๐Ÿ“š Grab the book: "Levelling Up with Docker" on Amazon


Found this helpful? ๐Ÿ™Œ

Top comments (0)