In a multi-service application, the way your components communicate is as critical as what they do. This chapter focuses on establishing secure and isolated networking for our Docker Compose stack. We’ll move beyond Docker’s default networking to create a dedicated network for our services, enhancing both security and clarity.
By the end of this milestone, our web application and database will communicate over a private, isolated network managed by Docker Compose. This ensures that only authorized services within our stack can reach each other, laying a robust foundation for a production-ready deployment.
Project Overview
Our overarching project goal is to build a production-ready multi-service web application stack using Docker and Docker Compose. This involves containerizing a simple web application, integrating it with a database, and applying best practices for deployment, security, and maintainability.
In this chapter, we specifically address the crucial aspect of inter-service communication. We are moving from implicit, default networking to an explicitly defined, isolated network to secure the communication pathways between our web and db services. This is a fundamental security and architectural pattern for any real-world containerized application.
Tech Stack
For this chapter, we continue to leverage:
- Docker Engine: The core containerization platform. (Version unknown, checked 2026-05-22. We recommend using the latest stable release available for your OS).
- Docker Compose: Used to define and run our multi-container Docker application. We adhere to the Compose Specification, which recommends omitting the explicit
versionfield indocker-compose.ymlfiles (checked 2026-05-22). - YAML: The language for defining our
docker-compose.ymlfile.
Milestones for Secure Networking
To achieve our goal of isolated inter-service communication, we will follow these steps:
- Understand Docker Networking Defaults: Briefly review how Docker Compose handles networking by default.
- Define a Custom Bridge Network: Add a
networkssection todocker-compose.ymlto create a dedicated network. - Attach Services to the Network: Explicitly connect our
webanddbservices to this new custom network. - Update Service Discovery: Configure the
webservice to use the database service name for internal resolution. - Verify Network Configuration: Confirm that the network is correctly created and services are attached and communicating.
Planning & Design: Isolated Service Communication
When you run multiple Docker containers that need to communicate, Docker provides robust networking capabilities. By default, Docker Compose places all services in a single, default network. While functional for simple setups, this default network can become less manageable as your application grows, and it doesn’t explicitly segment traffic.
For production systems, it’s a best practice to define custom networks. This allows you to:
- Isolate Services: Only services explicitly attached to a network can communicate over it. This prevents unintended communication paths and reduces the attack surface.
- Improve Name Resolution: Docker’s internal DNS allows services to resolve each other by their service names (e.g.,
webcan reachdbby simply usingdbas the hostname). This is more reliable than relying on dynamic IP addresses. - Enhance Security: By default, custom bridge networks are isolated from the host’s network unless specific ports are published. This minimizes external exposure.
Our goal is to define a custom bridge network within our docker-compose.yml file and explicitly attach our web and db services to it.
Network Architecture Overview
Our application’s communication flow will now look like this, with a dedicated, isolated network layer:
In this refined setup:
Useraccess still comes through a mapped host port (e.g.,80) to theWebServer. This is the only entry point from outside the Docker host.- The
WebServerandDatabasecontainers are now explicitly connected toApp_Network. - Communication between
WebServerandDatabasehappens entirely within this isolatedApp_Network. This traffic never leaves the Docker host’s internal network interface, enhancing security.
Step-by-Step Implementation
We will modify our existing docker-compose.yml file to define a custom network and attach our services.
1. Define the Custom Network
Open your docker-compose.yml file (from the previous chapter). We will add a new top-level networks section.
Add the following to your docker-compose.yml file, typically at the end of the file, alongside the services and volumes (if any) sections:
# docker-compose.yml
# ... (existing services and volumes sections)
networks:
app_network:
driver: bridgeExplanation:
networks:: This is a top-level key indocker-compose.ymlwhere you declare custom networks for your application.app_network:: This is the name we’ve chosen for our custom network. It should be descriptive and unique within your Compose file.driver: bridge: Specifies that Docker should create a standard bridge network. This is the most common type for single-host multi-container applications. It provides network isolation and internal DNS resolution, allowing containers on the same bridge network to communicate by their service names.
2. Attach Services to the Network
Now that we’ve defined app_network, we need to instruct our web and db services to connect to it. We do this by adding a networks key under each service definition.
Modify your docker-compose.yml to include these changes within the web and db service definitions:
# docker-compose.yml
services:
web:
build: .
ports:
- "80:80"
environment:
# ... (existing environment variables)
DATABASE_HOST: db # Crucial: Use the service name for internal resolution
networks:
- app_network # Attach the web service to our custom network
db:
image: postgres:16-alpine # Using a specific, lightweight version as of 2026-05-22
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
networks:
- app_network # Attach the db service to our custom network
# ... (existing volumes if any)
networks:
app_network:
driver: bridgeExplanation:
networks:: Under each service, this key lists the networks the service should connect to. A service can connect to multiple networks if needed, but for our current setup, one is sufficient.- app_network: This line explicitly attaches both thewebanddbservices to ourapp_network. This is a list item, so remember the hyphen.DATABASE_HOST: db: This is a critical change. Because both services are now onapp_network, Docker’s internal DNS will automatically resolve the service namedbto the correct IP address of thedbcontainer within that network. This makes our application configuration robust and independent of dynamically assigned IP addresses.
3. Review the Complete docker-compose.yml
Your complete docker-compose.yml should now reflect these changes, looking similar to this:
# docker-compose.yml
# As of 2026-05-22, the Compose Specification recommends omitting the 'version' field.
# This allows Compose to use the latest specification.
# Reference: https://github.com/jamesatdocker/docker-docs/blob/main/compose/compose-file/compose-versioning.md
services:
web:
build: .
ports:
- "80:80"
environment:
APP_ENV: production
DATABASE_HOST: db # Use the service name for internal resolution
DATABASE_PORT: 5432 # Default PostgreSQL port
networks:
- app_network
db:
image: postgres:16-alpine # Using a specific, lightweight version as of 2026-05-22
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
networks:
- app_network
# volumes:
# - db_data:/var/lib/postgresql/data # Uncomment if you set up volumes previously
# volumes:
# db_data: # Uncomment if you set up volumes previously
networks:
app_network:
driver: bridge⚡ Quick Note: The Docker Engine version is unknown as of 2026-05-22. For Docker Compose (Compose Specification), the best practice is to omit the version field from docker-compose.yml files. This ensures you’re using the latest specification without needing to update a version number manually.
Testing & Verification
After modifying the docker-compose.yml, it’s vital to verify that the network is created correctly and services are communicating as expected.
1. Rebuild and Start Services
First, stop any running containers from previous steps to ensure a clean slate, then bring up the new stack with the updated network configuration.
docker compose down --volumes --remove-orphans
docker compose up -ddocker compose down --volumes --remove-orphans: This command stops and removes containers, networks, and optionally volumes. The--volumesflag is important if you made changes that affect volumes (though not strictly necessary for just network changes), and--remove-orphansremoves services that are no longer defined in the Compose file.docker compose up -d: This command starts the services in detached mode (-d), creating the newapp_networkand attaching the specified services.
2. Verify Network Creation
Check if the app_network has been created by Docker.
docker network lsYou should see an entry similar to yourprojectname_app_network in the output. Docker Compose prefixes network names with the project directory name by default (e.g., if your project folder is my-app, the network might be my-app_app_network).
3. Inspect the Network
To see precisely which containers are attached to the network, use docker network inspect. Replace yourprojectname_app_network with the actual network name identified in the previous step.
docker network inspect yourprojectname_app_networkIn the output, under the Containers section, you should see entries for both your web and db services, along with their assigned IP addresses within that network. This confirms they are correctly connected.
4. Test Inter-Service Communication
You can test if the web service can resolve and communicate with the db service using the ping command.
docker compose exec web ping dbYou should see successful ping responses from the db container. This confirms that Docker’s internal DNS resolution is working correctly within app_network, and the web service can reach db by its service name.
If your web application connects to the database during startup, you should also check its logs for connection success:
docker compose logs webLook for successful database connection messages and crucially, no errors related to DATABASE_HOST or connection refused.
5. Access the Application
Finally, ensure your web application is still accessible and fully functional via your browser at http://localhost. If your application has a database interaction (e.g., retrieving data or displaying a list from the database), verify that this functionality works correctly.
Production Considerations
- Network Segmentation: For larger, more complex applications with many services, consider creating multiple custom networks. For instance, a
frontend_networkfor web servers and load balancers, and abackend_networkfor application servers and databases. This further limits the blast radius if one segment is compromised and improves overall network security.🧠 Important:Granular network segmentation is a key security practice, especially in microservices architectures. - Ingress/Egress Control: By default, custom bridge networks are very secure. The
portsmapping indocker-compose.ymlis the only way to expose a service to the host or external network. Be extremely mindful of which ports you expose and why. Never expose database ports (like5432for PostgreSQL) directly to the host or public network unless absolutely necessary and secured with strict firewall rules and authentication. - Host Firewall Rules: While Docker manages internal container networking, ensure your host’s firewall (e.g.,
ufwon Linux,Windows Defender Firewall) allows traffic to the host ports mapped by Docker Compose (e.g., port80for the web service). - Service Discovery: Docker’s internal DNS is excellent for service discovery within a Compose stack. Always use service names (like
db) for inter-service communication within thedocker-compose.ymlfile and your application code. Avoid hardcoding IP addresses, as they are ephemeral and can change.
Common Issues & Solutions
- Services cannot communicate (e.g.,
webcan’t connect todb):- Cause: The most common reasons are that services are not on the same network, or the
DATABASE_HOST(or similar environment variable) in the consuming service is incorrect. - Solution:
- Double-check
docker-compose.ymlto ensure both services explicitly listapp_networkunder theirnetworkskey with correct indentation. - Verify that environment variables in the consuming service (e.g.,
web) use the service name (e.g.,db) and notlocalhost,127.0.0.1, or an IP address. - Use
docker network inspect yourprojectname_app_networkto confirm both containers are listed. - Use
docker compose exec [service_name] ping [target_service_name]to test basic connectivity and DNS resolution.
- Double-check
- Cause: The most common reasons are that services are not on the same network, or the
docker network lsdoes not show the network afterup:- Cause:
docker compose upwas not run after modifying thedocker-compose.yml, or there’s a YAML syntax error in thenetworkssection preventing Compose from parsing it correctly. - Solution:
- Ensure you ran
docker compose downfollowed bydocker compose up -d. - Carefully review your
docker-compose.ymlfor YAML syntax errors (e.g., incorrect indentation, missing colons). Even a single space can cause issues.
- Ensure you ran
- Cause:
- Application fails to start with network-related errors (e.g., “connection refused”):
- Cause: While defining networks helps with connectivity, it doesn’t solve startup order issues. A service might try to connect to a dependency (like a database) before the dependency is fully ready and listening for connections.
- Solution: For now, check container logs for specific “connection refused” messages. We will address robust service startup order and readiness checks using Docker Compose health checks in a future chapter. For immediate debugging, a simple
sleepcommand in yourwebservice’s entrypoint or a retry logic in your application code can temporarily mitigate this.
Summary & Next Step
You’ve successfully established a secure and isolated network for your multi-service Docker Compose application. Your web and db services now communicate over a private bridge network, using Docker’s internal DNS for seamless service discovery. This is a critical step towards a robust and production-ready architecture, significantly enhancing both security and maintainability by isolating internal traffic.
Next, we’ll dive into managing persistent data with Docker volumes, ensuring that our database’s data survives container restarts, upgrades, and migrations, a non-negotiable requirement for any stateful production application.
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.
References
- Docker Documentation - Networking overview: https://docs.docker.com/network/
- Docker Compose - Define networks: https://docs.docker.com/compose/compose-file/06-networks/
- Compose Specification Versioning: https://github.com/jamesatdocker/docker-docs/blob/main/compose/compose-file/compose-versioning.md
- PostgreSQL Docker Official Image: https://hub.docker.com/_/postgres
- Docker CLI
network inspectcommand: https://docs.docker.com/engine/reference/commandline/network_inspect/ - Docker CLI
compose execcommand: https://docs.docker.com/engine/reference/commandline/compose_exec/ +++