Docker Compose · Lesson 2 of 5
Networking in Docker Compose — Service Discovery
How Docker Compose Networking Works
Docker Compose default behaviour:
→ Creates one network for the entire compose file (named: _default)
→ All services on the default network can reach each other by service name
→ No ports are exposed to the host unless you specify "ports:"
Service discovery in containers:
Host machine: services use "localhost:5000", "localhost:1433"
Inside compose: services use the service name as hostname
prescription-service → calls http://patient-service/api/patients
prescription-service → connects to Server=sqlserver;Database=Clinical
(where "sqlserver" is the compose service name) Basic Compose Networking
# docker-compose.yml
services:
prescription-service:
image: clinical/prescription-service:latest
environment:
- ConnectionStrings__Clinical=Server=sqlserver;Database=ClinicalPrescriptions;User Id=sa;Password=${DB_PASSWORD}
- Services__PatientService=http://patient-service
- Services__LabService=http://lab-service
ports:
- "5001:8080" # expose to host for testing
depends_on:
sqlserver:
condition: service_healthy
patient-service:
image: clinical/patient-service:latest
environment:
- ConnectionStrings__Clinical=Server=sqlserver;Database=ClinicalPatients;...
- Fhir__BaseUrl=http://fhir-mock # another compose service
# No "ports:" — not exposed to host. Only accessible to other containers.
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=${DB_PASSWORD}
# Also not exposed to host — only accessible to application services
healthcheck:
test: /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${DB_PASSWORD} -Q "SELECT 1"
interval: 10s
retries: 10
start_period: 30sCustom Networks for Security Isolation
# Separate frontend/backend networks — frontend services cannot directly access the DB
services:
nginx:
image: nginx:alpine
networks: [frontend]
ports:
- "443:443"
prescription-service:
image: clinical/prescription-service:latest
networks:
- frontend # accepts requests from nginx
- backend # can access DB and other services
depends_on:
- sqlserver
- patient-service
patient-service:
image: clinical/patient-service:latest
networks: [backend] # not accessible from frontend directly
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
networks: [backend] # only accessible from backend services
# Cannot be reached from nginx — frontend network has no route to it
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # no outbound internet from backend networkService Discovery in .NET
// In docker-compose, services call each other by service name
// Configure ASP.NET Core to use service names, not localhost
// appsettings.json for compose environment:
{
"Services": {
"PatientService": "http://patient-service",
"LabService": "http://lab-service",
"NotificationSvc": "http://notification-service"
}
}
// Named HttpClient pointing to patient-service:
builder.Services.AddHttpClient<IPatientServiceClient, PatientServiceClient>(client =>
{
client.BaseAddress = new Uri(
builder.Configuration["Services:PatientService"] ?? "http://patient-service");
});
// The "patient-service" hostname resolves via Docker's internal DNS
// to the container's IP on the shared networkPort Conflicts and Mapping
# Ports: "host:container"
services:
prescription-service:
ports:
- "5001:8080" # host port 5001 → container port 8080
# Access from host: http://localhost:5001
# Access from other containers: http://prescription-service:8080
# (other containers use the service name + container port, not host port)
patient-service:
ports:
- "5002:8080" # different host port, same container port — no conflict
# Multiple services CAN use port 8080 inside their containers
# Only host port mappings must be unique
sqlserver:
ports:
- "14330:1433" # map to non-standard host port to avoid conflict
# with local SQL Server running on 1433
# Connection string from host: Server=localhost,14330;...
# Connection string inside compose: Server=sqlserver,1433;... (container port)DNS Resolution and Aliases
# Network aliases — give a service an additional hostname on a specific network
services:
fhir-service:
image: clinical/fhir-service:latest
networks:
backend:
aliases:
- fhir # accessible as "fhir" within the backend network
- fhir.clinical.local # FQDN alias
prescription-service:
image: clinical/prescription-service:latest
networks: [backend]
environment:
- Fhir__BaseUrl=http://fhir # uses the alias
# Useful when: migrating from a hostname to another without changing all consumers,
# or when a service should be reachable by multiple namesProduction issue I've seen: A developer ran a clinical system locally and couldn't understand why their docker-compose environment worked perfectly for some API calls but
patient-servicereturned connection refused for others. The issue: they had two compose files (docker-compose.ymlanddocker-compose.override.yml) that created two separate projects — one for the main services, one for testing infrastructure. Docker Compose creates a separate network per project, so the two networks couldn't communicate. The fix was a shared external network or combining into one compose file. The diagnostic command:docker network lsto see networks, anddocker inspect <container>to see which networks a container is attached to.
Key Takeaway
Docker Compose creates a shared bridge network by default — services communicate by service name, not localhost. Never use "localhost" for inter-container communication — use the compose service name as the hostname. Use custom networks to isolate tiers (frontend/backend) —
internal: trueprevents outbound internet from backend networks. Host ports must be unique per host machine; container ports can be the same across services. DNS aliases let a service be reachable by multiple names within a network.