Mastering Docker Networking: Solving Bidirectional Communication Between Containers and Host Services
A practical guide to enabling seamless communication between containerized applications and host-based services

Introduction
Docker has revolutionized application deployment by providing consistent, isolated environments. However, this isolation can become a double-edged sword when your containerized application needs to communicate with services running on the host machine.
In this post, we'll explore a real-world scenario where a .NET Core API service running in a Docker container needed to communicate with external services (Service A and Service B) running on Windows IIS, while simultaneously accepting connections from a React Native mobile application via Expo. What started as simple "connection refused" errors led to a deep dive into Docker networking fundamentals and an elegant solution that maintains security while enabling seamless communication.
Technology Stack
Containerized Service: ASP.NET Core 9.0 Web API
Host Services: Service A and Service B on Windows IIS
Client Application: React Native with Expo for cross-platform mobile development
Infrastructure: Docker Compose on WSL2 with Windows host services
The Challenge: When Docker Containers Need to Talk to the Outside World
Picture this scenario: You have a .NET Core 9.0 Web API containerized with Docker Compose, and it needs to:
Make outbound calls to Service A and Service B running on Windows IIS
Accept inbound connections from a React Native mobile app running via Expo
Sounds straightforward, right? Well, Docker's network isolation had other plans.
The Initial Error Symphony
Error: Connection refused (localhost:3001)
Error: Connection refused (mobile app → server)
These cryptic messages marked the beginning of our networking adventure. Little did we know, we were dealing with two distinct but related problems that required different approaches.
Understanding Docker Network Isolation
Before diving into solutions, let's understand why these problems occur in the first place.
Container Network Namespaces
Each Docker container exists in its own network namespace, which means:
localhostinside a container refers to the container itself, not the host machineThe container has its own loopback interface (
127.0.0.1)Services on the host machine are unreachable via
localhostfrom within the container
This isolation is by design—it's what makes containers portable and secure. However, it creates challenges when containers need to interact with host-based services.
The Localhost Confusion
When your containerized application tries to connect to localhost:3001, it's looking for a service running inside the container, not on the host. This is the source of our first "connection refused" error.
Similarly, when a service inside a container binds to localhost:7008, it's only accessible from within that container—external clients (like mobile apps) cannot reach it.
Problem 1: Container to Host Communication
The Core Challenge
A .NET Core API running in a Docker container needed to communicate with Service A running on Windows IIS. Service A was configured with a critical security constraint: it only accepted requests from localhost:4002.
Failed Attempts and Learning Moments
Attempt 1: host.docker.internal
services:
app:
environment:
- API_URL=http://host.docker.internal:3001
This Docker Desktop feature wasn't available in our WSL environment. Lesson learned: Not all Docker features work consistently across platforms.
Attempt 2: Direct IP Access
services:
app:
environment:
- API_URL=http://192.168.10.101:3001
Service A rejected these requests because they appeared to come from 192.168.10.101:4002 instead of the expected localhost:4002. Lesson learned: Security constraints matter, and changing client origins can break service communication.
The Breakthrough: extra_hosts Mapping
The solution was elegantly simple: make the container think it's still using localhost while actually routing to the host IP.
# docker-compose.yml
services:
api-service:
extra_hosts:
- "localhost:${HOST_IP}" # Maps localhost to host IP
// appsettings.json
{
"ExternalApi": {
"ServiceAUrl": "http://localhost:3001/",
"ServiceBUrl": "http://localhost:4002/"
}
}
How It Works
Container perspective: Makes request to
localhost:3001Docker mapping: Resolves
localhostto actual host IP (e.g., 192.168.10.101)Request flow: Container → Host IP → Windows IIS
Service A perspective: Sees request from
localhost:4002(approved origin)Success: Communication works seamlessly
Problem 2: Host to Container Communication
The Binding Challenge
While solving container-to-host communication, we discovered another issue: the React Native mobile app couldn't connect to the containerized API.
Understanding Interface Binding
The Problem: Inside a container, binding to localhost or 127.0.0.1 means:
Only processes within the container can connect
External clients (mobile apps, browsers, other services) cannot reach the service
Docker's port mapping becomes ineffective
The Solution: Bind to 0.0.0.0 (all interfaces):
Accepts connections from any network interface
Works with Docker's port mapping (
7008:7008)Allows external clients to connect through the host IP
Configuration Change
// appsettings.json
{
"ServerConfiguration": {
"Host": "0.0.0.0", // Accept connections from anywhere
"Port": "7008"
}
}
Traffic Flow
Mobile App (192.168.10.101:7008) → Docker Host → Container (0.0.0.0:7008) → .NET API
The Complete Solution: Bidirectional Communication
Architecture Overview
Our final solution enables seamless bidirectional communication:
Mobile App (192.168.10.101:7008) ↔ Container (0.0.0.0:7008) ↔ Host Services (localhost:3001/4002)
Configuration Files
docker-compose.yml:
services:
api-service:
ports:
- "7008:7008"
extra_hosts:
- "localhost:${HOST_IP}" # Enable container → host
Application Configuration:
{
"ServerConfiguration": {
"Host": "0.0.0.0", // Enable host → container
"Port": "7008"
},
"ExternalApi": {
"ServiceAUrl": "http://localhost:3001/",
"ServiceBUrl": "http://localhost:4002/"
}
}
Why This Architecture Works
Network Isolation: Maintains container security boundaries
DNS Resolution:
extra_hostsprovides custom hostname-to-IP mappingInterface Binding:
0.0.0.0exposes services to container's network interfacePort Mapping: Docker forwards external traffic to container ports
Origin Transparency: Host services see expected localhost origins
Implementation: Making It Work Across Platforms
The Environment Variable Challenge
Hard-coding IP addresses in docker-compose.yml creates per-developer configuration issues. Our solution uses environment variables for portability:
WSL/Linux:
export HOST_IP=$(ip route show default | awk '/default/ { print $3 }')
Windows PowerShell:
$env:HOST_IP = (Get-NetRoute -DestinationPrefix "0.0.0.0/0").NextHop | Select-Object -First 1
Docker Compose:
services:
api-service:
extra_hosts:
- "localhost:${HOST_IP}"
Developer Workflow
# Set environment variable (platform-specific)
export HOST_IP=$(ip route show default | awk '/default/ { print $3 }')
# Build and start services
docker compose build --no-cache && docker compose up -d
# Monitor logs
docker compose logs -f api-service
Conclusion and Lessons Learned
Key Takeaways
Docker Network Isolation is Real: Understanding container networking is crucial for complex applications that need to communicate across boundaries.
Security Constraints Matter: Host services with localhost-only restrictions require careful handling when containerizing applications.
Bidirectional Communication Needs Different Solutions: Container-to-host and host-to-container communication each require specific approaches.
Platform Compatibility is Essential: Solutions must work across WSL, Linux, and Windows PowerShell environments.
Environment Variables Save the Day: Avoid hard-coded configurations that break in different development environments.
The Bigger Picture
This networking challenge taught us that containerization isn't just about packaging applications—it's about understanding the fundamental changes in how applications communicate. Docker's isolation features are powerful, but they require thoughtful solutions when applications need to reach beyond container boundaries.
The solution we developed now enables reliable bidirectional communication between mobile clients, containerized APIs, and host-based services. More importantly, it's enabled smooth development workflow across different platforms without networking headaches.
Final Thoughts
Docker networking can seem daunting at first, especially when dealing with legacy systems that have specific security requirements. However, understanding the fundamentals of network namespaces, interface binding, and DNS resolution provides the foundation for solving even complex communication challenges.
The next time you encounter "connection refused" errors in your containerized applications, remember: it's not just about changing an IP address—it's about understanding how networks work in the container world and finding elegant solutions that preserve both functionality and security.
Happy containerizing!


