Welcome back, intrepid Mac developer! In our journey so far, you’ve mastered the basics of running, building, and managing individual Linux containers right on your macOS system using Apple’s powerful container CLI. You’ve seen how easy it is to bring up isolated environments, but what happens when your application isn’t just one container, but a collection of services that need to talk to each other?
That’s where advanced networking comes into play. In this chapter, we’ll peel back another layer of the container onion to reveal how you can create custom networks for your containers, enabling them to communicate securely and efficiently. We’ll explore the magic of DNS-based service discovery, allowing your containers to find each other by name instead of by fickle IP addresses. By the end of this chapter, you’ll be able to orchestrate multi-container applications with robust networking configurations, a crucial skill for any modern development workflow.
Ready to connect the dots (and the containers)? Let’s dive in!
Prerequisites
Before we embark on this networking adventure, make sure you’re comfortable with:
- Running basic
containercommands (from Chapters 1-3). - Building container images from
Dockerfiles (from Chapter 5). - The fundamental concept of port mapping (from Chapter 4).
For this chapter, we’ll be using Apple’s container CLI, which as of February 25, 2026, is widely adopted and stable. We’ll reference features available in container CLI version 1.0.0 (a hypothetical stable release based on typical project progression since its WWDC 2025 announcement). If you’re using a different version, some command outputs or specific options might vary slightly, but the core concepts remain consistent.
Core Concepts: Building a Container Neighborhood
Imagine you’re building a small town. Initially, everyone lives in their own house, isolated. But for a town to function, people need roads, postal services, and ways to find each other (like street names!). In the world of containers, networks are those roads, and DNS provides the street names.
The Default Network: A Shared Highway
When you run a container without specifying a network, Apple’s container CLI automatically places it on a default bridge network. Think of this as a shared highway where all containers can technically see each other, but finding a specific service can be like finding a specific car on that highway without a license plate. They get assigned internal IP addresses, but these can change, making it tricky for containers to reliably communicate by IP.
While convenient for single containers, this default setup isn’t ideal for multi-service applications for a few key reasons:
- Isolation: All containers share the same network segment. If you have different projects, you might want to keep their network traffic completely separate.
- Service Discovery: How does your web app container know the IP address of your database container? Hardcoding IPs is brittle and prone to breakage.
- Security: Less isolation means potentially more exposure between unrelated services.
This is where custom networks come to the rescue!
Custom Bridge Networks: Your Private LAN Segment
A custom bridge network, created with container network create, is like setting up a private, isolated local area network (LAN) just for a specific group of containers. When you connect containers to this custom network:
- Isolation: Only containers attached to this specific network can communicate with each other directly using their internal network interface.
- Automatic DNS Resolution: This is the real magic! Containers on the same custom network can resolve each other by their service name (the
--nameyou give them when running) without any extra configuration. Thecontainerdaemon acts as a built-in DNS server for these networks. - Predictable IP Ranges: While you don’t typically manage individual container IPs,
containerallocates a dedicated IP subnet for each custom network, ensuring clear separation.
Let’s visualize this with a simple diagram:
In this diagram:
- The
macOS Host Networkis where your browser lives. - The
myapp-netis a custom network created by thecontainerCLI. Web_AppandDB_Containerare connected tomyapp-net.- The
Web_Appcan find theDB_Containersimply by asking formy-db. - Your
User_Browserconnects to theWeb_Appvia a port mapping on the macOS host, bridging the host network to the container’s internal network.
How Service Discovery Works with DNS
When you run a container with a specific --name on a custom network, the container daemon automatically registers that name with its internal DNS server for that network. Any other container on the same network can then use that name to reach the service.
For example, if you run a database container named my-db on myapp-net, your web application container (also on myapp-net) can simply try to connect to my-db on the database’s default port. The container DNS resolver will translate my-db into the correct internal IP address of the database container. This makes your application configuration much simpler and more robust, as you don’t have to worry about changing IP addresses.
Step-by-Step Implementation: Building a Connected App
Let’s put these concepts into practice. We’ll create a simple multi-container application: a Redis database and a Python Flask web application that connects to it.
First, let’s make sure our environment is clean from previous exercises:
# Stop and remove all running containers
container ps -aq | xargs -r container stop
container ps -aq | xargs -r container rm
# Remove all custom networks (be careful in a real environment!)
container network ls -q | grep -v 'bridge' | xargs -r container network rm
This ensures we start with a fresh slate. The xargs -r is a useful trick to prevent xargs from running if ps -aq or network ls -q return no results, avoiding errors.
Step 1: Create Your Custom Network
Our first step is to create the dedicated network for our application. We’ll call it myapp-net.
# Create a new custom bridge network
container network create myapp-net
What happened here?
The container network create command instructs the container daemon to provision a new isolated network segment.
You should see output similar to this, indicating success:
Network 'myapp-net' created.
To verify its creation, you can list all networks:
container network ls
You’ll see myapp-net listed alongside the default bridge network. Note the ID and DRIVER (which will typically be bridge).
Step 2: Run Your Database Container on the Custom Network
Now, let’s launch a Redis database container and attach it to our newly created myapp-net. We’ll give it a meaningful name: my-redis-db.
# Run a Redis container, attach it to myapp-net, and name it my-redis-db
container run --network myapp-net --name my-redis-db -d redis:alpine
Breaking it down:
container run: The command to start a new container.--network myapp-net: This is the crucial part! It tellscontainerto connect this container to our custommyapp-netnetwork.--name my-redis-db: We give our Redis container a friendly name. This is the name other containers onmyapp-netwill use for DNS resolution.-d: Runs the container in detached mode (in the background).redis:alpine: The image we want to use.alpineis a lightweight version.
You should see the container ID printed. To confirm it’s running and attached to the network:
container ps
Step 3: Create and Run Your Web App Container
Next, we’ll create a simple Python Flask web application that attempts to connect to our Redis database. The key is that it will try to connect to my-redis-db by name, relying on the custom network’s DNS.
First, create a new directory for your web app:
mkdir my-webapp
cd my-webapp
Now, create a file named app.py inside my-webapp with the following content:
# my-webapp/app.py
import os
import time
from flask import Flask
from redis import Redis, ConnectionError
app = Flask(__name__)
redis_host = os.environ.get('REDIS_HOST', 'localhost') # Default to localhost if not set
redis_port = os.environ.get('REDIS_PORT', '6379') # Default Redis port
@app.route('/')
def hello():
try:
# Connect to Redis using the hostname 'my-redis-db'
# which will be resolved by the container's DNS
r = Redis(host=redis_host, port=int(redis_port), socket_connect_timeout=1)
r.ping() # Test connection
return f"Hello from Flask! Connected to Redis at {redis_host}:{redis_port} successfully!"
except ConnectionError as e:
return f"Hello from Flask! Could not connect to Redis at {redis_host}:{redis_port}. Error: {e}", 500
except Exception as e:
return f"An unexpected error occurred: {e}", 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Explanation of app.py:
- It’s a basic Flask app.
- It attempts to connect to a Redis instance.
- Crucially, it gets the Redis host from an environment variable
REDIS_HOST. IfREDIS_HOSTis set tomy-redis-db(which we will do!), it will use that for connection.
Next, create a Dockerfile in the same my-webapp directory:
# my-webapp/Dockerfile
# Use a lightweight Python base image
FROM python:3.10-alpine
# Set the working directory in the container
WORKDIR /app
# Copy the requirements file and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the application code
COPY app.py .
# Expose the port our Flask app runs on
EXPOSE 8000
# Define the command to run our application
CMD ["python", "app.py"]
And finally, create a requirements.txt file in my-webapp:
# my-webapp/requirements.txt
Flask==2.3.3
redis==4.5.1
(Note: These are stable versions as of early 2026. Always check for the latest compatible versions for your projects.)
Now, let’s build our web app image:
# Build the web app image
container build -t my-web-app .
You should see the build process complete, creating an image tagged my-web-app.
Finally, run the web app container, connecting it to myapp-net and mapping its port to your host:
# Run the web app container
container run \
--network myapp-net \
--name my-web \
-p 8000:8000 \
-e REDIS_HOST=my-redis-db \
-d my-web-app
Breaking it down:
--network myapp-net: Connects our web app to the same custom network asmy-redis-db. This is essential for DNS resolution.--name my-web: Gives our web app container a name.-p 8000:8000: Maps port 8000 on your macOS host to port 8000 inside themy-webcontainer, allowing you to access it from your browser.-e REDIS_HOST=my-redis-db: This sets an environment variable inside the container. Ourapp.pyuses this to know where to find Redis. Because both containers are onmyapp-net,my-redis-dbwill resolve to the correct internal IP address.-d my-web-app: Runs themy-web-appimage in detached mode.
Step 4: Test the Application
Now, open your web browser or use curl to access the Flask application on your host:
curl http://localhost:8000
You should see output similar to:
Hello from Flask! Connected to Redis at my-redis-db:6379 successfully!
Congratulations! Your web app container successfully connected to the Redis container using its service name (my-redis-db) thanks to the custom network’s built-in DNS resolver. This is a powerful pattern for building microservices and multi-tier applications.
Step 5: Inspecting Networks and Containers
To get more details about your network setup, you can use container network inspect.
container network inspect myapp-net
The output will be a JSON array containing detailed information about the myapp-net network, including:
- Its ID, creation time, and driver.
- The
Containerssection, which lists all containers connected to this network, their assigned IP addresses, and their names. You’ll seemy-redis-dbandmy-webhere, along with their internal IP addresses onmyapp-net. - The
IPAM(IP Address Management) configuration, showing the subnet allocated to this network.
This command is invaluable for troubleshooting networking issues.
Step 6: Cleaning Up
When you’re done, it’s good practice to stop and remove your containers and networks to free up resources.
# Stop the containers
container stop my-web my-redis-db
# Remove the containers
container rm my-web my-redis-db
# Remove the custom network
container network rm myapp-net
Why this order? You must remove all containers from a custom network before you can remove the network itself. If you try to remove myapp-net while my-web or my-redis-db are still attached, the command will fail.
Mini-Challenge: Network Isolation
You’ve seen how containers on the same custom network can communicate. Now, let’s test the isolation properties.
Challenge:
- Create a second custom network, call it
isolated-net. - Run a new simple
nginxcontainer onisolated-net, namedmy-nginx. - From your
my-webcontainer (or a new simplealpine/bashcontainer attached tomyapp-net), try topingorcurlthemy-nginxcontainer using its name. - What do you observe? Why does this happen?
Hint: Remember our analogy of private LAN segments. If two computers are on completely different LANs, can they talk to each other directly without a router?
Click for Solution & Explanation
Here’s how you’d perform the challenge:
# 1. Create the second custom network
container network create isolated-net
# 2. Run an nginx container on isolated-net
container run --network isolated-net --name my-nginx -p 8080:80 -d nginx:alpine
# 3. Try to access my-nginx from a container on myapp-net
# First, let's restart our web app or just run a temporary alpine container on myapp-net
container run --network myapp-net --name temp-tester -it --rm alpine/bash
# Inside the temp-tester container, try to ping my-nginx:
# ping my-nginx
What you observe:
If you try to ping my-nginx from inside the temp-tester container (which is on myapp-net), the ping command will fail, reporting something like bad address 'my-nginx' or ping: bad address 'my-nginx'. Similarly, curl http://my-nginx would fail with a hostname resolution error.
Why this happens:
This demonstrates network isolation. The DNS resolution provided by Apple’s container daemon is scoped to the custom network. A container on myapp-net can only resolve names of other containers that are also on myapp-net. It has no knowledge of containers existing on isolated-net, nor does isolated-net know about myapp-net. They are completely separate virtual network segments. This isolation is a key feature for security and preventing unintended communication between different application stacks.
To make my-web talk to my-nginx, you would either need to:
- Connect
my-nginxtomyapp-netas well. - Connect
my-webtoisolated-netas well. - Expose
my-nginx’s port to the host and then havemy-webconnect tohost.container.internal:8080(orlocalhost:8080if the host port is mapped to the same port) – this method bypasses direct container-to-container DNS resolution and uses the host network as an intermediary.
Remember to clean up after the challenge:
container stop temp-tester my-nginx
container rm temp-tester my-nginx
container network rm isolated-net
Common Pitfalls & Troubleshooting
Networking can sometimes be tricky. Here are a few common issues and how to troubleshoot them:
Containers Can’t Find Each Other:
- Symptom: Connection refused, hostname resolution failure (
bad address). - Check: Did you use the
--networkflag for all containers that need to communicate? Are they all on the same custom network? - Check: Is the
--nameof the target container correctly spelled in the connecting container’s configuration (e.g.,REDIS_HOST=my-redis-db)? - Tool: Use
container network inspect <your-network-name>to see which containers are actually connected to the network and their assigned names/IPs. - Tool: You can use
container exec <connecting-container-name> ping <target-container-name>to test basic connectivity and DNS resolution from within a container.
- Symptom: Connection refused, hostname resolution failure (
Port Conflicts:
- Symptom:
Error: Bind for 0.0.0.0:8000 failed: port is already allocated. - Check: You’re trying to map a host port (
-p 8000:8000) that is already in use by another process on your macOS system, or by another running container. - Solution: Stop the conflicting process/container, or choose a different host port (e.g.,
-p 8001:8000). Uselsof -i :8000on your macOS terminal to find processes using a specific port.
- Symptom:
Application Configuration Errors:
- Symptom: Your application starts but reports connection errors in its logs.
- Check: Is your application inside the container correctly configured to use the container’s name for service discovery? (e.g.,
REDIS_HOSTenvironment variable, or configuration file settings). - Tool: Use
container logs <container-name>to inspect your application’s output for specific error messages.
macOS Firewall Interference:
- While
containergenerally handles firewall rules for port mappings, in rare cases, a very strict macOS firewall or third-party security software might interfere with connections to mapped ports. - Check: Temporarily disable your macOS firewall (System Settings > Network > Firewall) for testing purposes, but remember to re-enable it. This is usually not the primary cause of container-to-container communication issues, but can affect host-to-container access.
- While
Summary
You’ve successfully leveled up your container networking skills! Here’s a recap of what we covered:
- Custom Networks: We learned that while the default
bridgenetwork is fine for single containers, custom networks provide crucial isolation and organization for multi-service applications. - Service Discovery: The power of DNS-based service discovery within custom networks means your containers can find each other by their
--name(e.g.,my-redis-db) instead of hardcoding volatile IP addresses. container networkCommands: You’re now familiar withcontainer network create,container network ls,container network inspect, andcontainer network rmfor managing your container networks.- Practical Application: We built a two-tier application (web app + database) where services communicated seamlessly over a custom network.
- Network Isolation: You reinforced your understanding of how custom networks isolate traffic, preventing unintended communication between different application stacks.
Understanding advanced networking is a cornerstone of building robust, scalable, and maintainable containerized applications. You’re now well-equipped to design and implement complex service architectures on your Mac!
What’s Next?
In the next chapter, we’ll shift our focus to data persistence. You’ll learn how to ensure your containerized applications can store and retrieve data reliably, even when containers are stopped, removed, or updated. Get ready to dive into the world of volumes and bind mounts!
References
- Apple Container GitHub Repository
- Apple Container Documentation - How-to Guide
- Apple Container Documentation - Tutorial
- Flask Official Documentation
- Redis-py (Python Redis Client) Documentation
- Dockerfile Reference (Docker Docs)
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.