Free Ebook cover Docker for Beginners: Containers Explained with Simple Projects

Docker for Beginners: Containers Explained with Simple Projects

New course

12 pages

Connecting Services with Docker Networks

Capítulo 6

Estimated reading time: 11 minutes

+ Exercise

Why Docker Networks Matter When You Have More Than One Container

As soon as your project has more than one service (for example, a web app plus a database, or an API plus a cache), the containers need a reliable way to find and talk to each other. Docker networks provide that communication layer. Instead of hard-coding IP addresses or exposing every internal port to your host machine, you connect containers to the same Docker network and let Docker handle service discovery and routing.

A Docker network is a virtual network managed by Docker. Containers attached to the same network can communicate using IP addresses, but more importantly, they can communicate using container names (DNS-based service discovery). This is the key beginner-friendly idea: on a user-defined Docker network, Docker runs an internal DNS server so that http://api:3000 can resolve to the container named api without you needing to know its IP.

Network Drivers You Will Use as a Beginner

Docker supports multiple network drivers. For beginner projects, you will mostly use:

  • bridge: The default for single-host container networking. A user-defined bridge network gives you automatic DNS by container name and better isolation than the default bridge network.
  • host: The container shares the host’s network stack. This removes network isolation and is not ideal for most beginner multi-service setups; it can be useful for troubleshooting or special performance needs.
  • none: No networking. Useful for highly restricted containers, not for connecting services.

In this chapter, the focus is on user-defined bridge networks, because they are the simplest way to connect services on one machine while keeping a clean separation between “internal service-to-service traffic” and “ports exposed to your host.”

Key Concepts: Internal Ports vs Published Ports

When connecting services, it helps to separate two ideas:

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

  • Internal container ports: The ports a service listens on inside the container (for example, PostgreSQL listens on 5432, Redis on 6379, many Node apps on 3000). Containers on the same Docker network can reach these internal ports directly.
  • Published ports: Ports you map from your host to a container using -p HOST:CONTAINER. Publishing is only needed when you want to access a container from outside Docker (for example, your browser on the host). It is not required for container-to-container communication on the same network.

A common beginner mistake is publishing every service port “just in case.” Instead, publish only what you need to access from the host (often just the web entrypoint). Keep databases and caches internal to the network unless you have a specific reason to expose them.

Creating and Inspecting a User-Defined Network

Start by creating a dedicated network for your project. This gives you predictable DNS behavior and isolates your services from other containers.

docker network create app-net

List networks to confirm it exists:

docker network ls

Inspect the network to see its configuration and attached containers:

docker network inspect app-net

At first, the Containers section will be empty. As you attach containers, you will see their IP addresses and endpoints inside this network.

Step-by-Step: Connect a Web App to a Database Using a Docker Network

This example demonstrates the core workflow: create a network, run a database container on it, run an app container on it, and configure the app to connect to the database using the database container name as the hostname.

Step 1: Create the network

docker network create app-net

Step 2: Run a database container attached to the network

Here is a PostgreSQL example. Notice that we do not publish port 5432 to the host. The database is intended to be reachable only by other containers on app-net.

docker run -d --name db --network app-net -e POSTGRES_PASSWORD=secret postgres:16

Docker will attach db to app-net and register the name db in the network DNS.

Step 3: Run an application container on the same network

Assume your app expects a connection string in an environment variable (many frameworks do). The important part is the hostname: use db, not localhost. Inside a container, localhost refers to that same container, not the database container.

docker run -d --name web --network app-net -p 8080:8080 -e DATABASE_HOST=db -e DATABASE_PORT=5432 my-web-image:latest

Now the web container can connect to db:5432 over the internal Docker network. Your browser can reach the web app at http://localhost:8080 because only the web service is published to the host.

Step 4: Verify name resolution and connectivity from inside the container

To confirm that DNS works, you can run a quick command inside the web container. Not all images include diagnostic tools, but you can still verify resolution using basic utilities if present. If your image is minimal, you can use a temporary troubleshooting container on the same network.

Option A: Use a temporary container with networking tools:

docker run --rm -it --network app-net alpine:3.20 sh

Inside the shell, try resolving the database name:

getent hosts db

If getent is not available, you can install tools temporarily (for troubleshooting only):

apk add --no-cache bind-tools
nslookup db

Then test TCP connectivity (again, you may need to install a tool):

apk add --no-cache busybox-extras
nc -zv db 5432

The key learning: if the containers share the same user-defined network, the name db resolves and traffic routes without exposing the database port to the host.

Understanding Docker’s Built-In DNS on User-Defined Networks

On a user-defined bridge network, Docker provides an embedded DNS server. When a container asks to resolve a name, Docker can answer with the IP of another container on the same network. This is why container names become stable hostnames within that network.

Important details:

  • DNS-based discovery works reliably on user-defined networks. The default bridge network behaves differently and is less convenient for name-based discovery.
  • The hostname is typically the container name (for example, db). You can also add extra names using network aliases.
  • Container IP addresses can change when containers are recreated. Rely on names, not IPs.

Network Aliases: One Service, Multiple Names

Sometimes you want a stable service name even if the container name changes, or you want multiple names pointing to the same container (for example, postgres and db). You can add aliases when connecting a container to a network.

Example: run a database container and give it an alias postgres on app-net:

docker run -d --name db --network app-net --network-alias postgres -e POSTGRES_PASSWORD=secret postgres:16

Now other containers can connect to db or postgres as the hostname.

Connecting an Existing Container to Another Network

You are not limited to attaching a container to a network at startup. You can connect and disconnect networks dynamically.

Connect an existing container to a network:

docker network connect app-net some-container

Disconnect it:

docker network disconnect app-net some-container

This is useful when you realize a container needs access to a service network, or when you want to temporarily attach a debugging container to inspect traffic.

Multi-Network Patterns: Frontend and Backend Separation

A practical pattern is to use two networks:

  • A frontend network for traffic between a reverse proxy (or web entrypoint) and your app services.
  • A backend network for traffic between app services and internal dependencies like databases and caches.

This gives you a simple form of segmentation. For example, your database is only on the backend network, so the reverse proxy cannot talk to it directly.

Create two networks:

docker network create front-net
docker network create back-net

Run a database only on the backend:

docker run -d --name db --network back-net -e POSTGRES_PASSWORD=secret postgres:16

Run an API on both networks (it needs to accept requests from the frontend side and talk to the database on the backend side):

docker run -d --name api --network front-net -e DATABASE_HOST=db -e DATABASE_PORT=5432 my-api-image:latest

Then connect the API to the backend network as well:

docker network connect back-net api

Now api can receive traffic from containers on front-net and can reach db on back-net. The database remains isolated from the frontend network.

Step-by-Step: Simple API + Redis Cache on a Shared Network

Another common beginner setup is an API that uses Redis. The networking idea is the same: attach both containers to the same user-defined network and use the Redis container name as the hostname.

Step 1: Create a network

docker network create cache-net

Step 2: Run Redis (internal only)

docker run -d --name redis --network cache-net redis:7

Step 3: Run your API and point it at Redis

docker run -d --name api --network cache-net -p 3000:3000 -e REDIS_HOST=redis -e REDIS_PORT=6379 my-api-image:latest

From the API container’s perspective, redis:6379 is reachable because both containers are on cache-net. You only publish the API port to the host so you can test it from your browser or a REST client.

Troubleshooting Container-to-Container Networking

When services cannot connect, the issue is usually one of these: wrong hostname, wrong port, wrong network, or the service is not listening yet. Use a structured approach.

1) Confirm both containers are on the same network

Inspect the network and check the container list:

docker network inspect app-net

Or inspect a container and look at its networks:

docker inspect web

Look for the Networks section and confirm the expected network is present.

2) Confirm you are not using localhost incorrectly

If your app uses localhost to reach the database, it will try to connect to itself. In multi-container setups, the hostname should be the other container’s name (for example, db) or a network alias.

3) Confirm the target service is listening on the expected port

Inside the target container, check logs or run a command if available. Often the simplest check is to view logs:

docker logs db

If the database is still starting up, your app may fail on the first attempt. Many apps need retry logic or a startup wait strategy.

4) Use a temporary debug container on the same network

This is a reliable technique because it avoids modifying your production-like images. Start a shell on the same network and test DNS and connectivity:

docker run --rm -it --network app-net alpine:3.20 sh

Then resolve and test ports as shown earlier (nslookup, nc).

5) Check whether you accidentally relied on published ports

Published ports are for host-to-container access. Container-to-container access should use the container’s internal port. For example, if you publish -p 15432:5432 for PostgreSQL, other containers should still connect to db:5432, not db:15432.

How Docker Compose Uses Networks (Practical Mental Model)

Even if you are not using Docker Compose yet, it helps to understand the mental model because many multi-service projects use it. Compose automatically creates a user-defined bridge network for your application and connects all services to it (unless you configure otherwise). That is why, in Compose setups, services can typically reach each other by service name.

The key takeaway you can apply even without Compose is: create a user-defined network, attach related containers to it, and use names for connectivity.

Security and Exposure: Keep Internal Services Internal

Networking is not only about connectivity; it is also about reducing unnecessary exposure.

  • Do not publish database ports unless you need host access for a tool. Prefer keeping them internal and using a one-off container for admin tasks on the same network.
  • Use separate networks to prevent unrelated containers from reaching sensitive services.
  • Prefer explicit networks per project instead of attaching everything to a shared network.

A practical workflow for admin access without publishing a database port is to run a temporary client container on the same network. For PostgreSQL, for example, you could run a client container attached to app-net and connect to db by name. This keeps the database unexposed to the host network while still allowing controlled access from within Docker.

Common Beginner Pitfalls (and How to Avoid Them)

Using container IP addresses

Container IPs can change. Use container names or network aliases. If you need a stable name like database, set an alias and keep your app configuration pointing to that alias.

Mixing default bridge and user-defined networks

If one container is on the default bridge network and another is on app-net, they cannot talk by name and may not be able to talk at all. Put related services on the same user-defined network.

Publishing ports for internal dependencies

Publishing is not required for container-to-container communication. Publish only the entrypoints you need from the host (often one HTTP port).

Forgetting that startup order is not readiness

Even if you start the database container first, it may take time to initialize. Your app should handle retries, or you should implement a wait strategy. Networking can be correct while the service is still unavailable.

Now answer the exercise about the content:

In a multi-container setup where a web app must connect to a PostgreSQL container on the same user-defined bridge network, which approach best ensures reliable connectivity without unnecessarily exposing the database to the host?

You are right! Congratulations, now go to the next page

You missed! Try again.

On a user-defined bridge network, Docker provides DNS so containers can reach each other by name. The web app should connect to db:5432 (or an alias) using the internal port; publishing the database port is only needed for host access.

Next chapter

Project: Containerizing a Static Website

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.