Cover

Using depends_on: A basic approach

In the Microservices Udemy course we make heavy use of a docker-compose.yml file to start our services. Some of these services have a dependency on infrastructure services like Postgres, RabbitMQ or MongoDB. In the course we use the depends_on feature of Docker to ensure the order of containers starting using this approach:

1services:
2  auction-svc:
3    image: dockerusername/auction-svc:latest
4    depends_on:
5      - postgres
6      - rabbitmq

However, if we ran this we would find the auction-svc service is started before the RabbitMQ service is actually ready to receive connections so we would see some nasty warnings in the logs as the service is not able to make a connection to RabbitMQ as it is not yet ready to receive connections. It is not a big deal as the MassTransit service will use a retry approach and the auction-svc will eventually make the connection.

Enhancing reliability with health checks.

Docker’s healthcheck directive adds robustness by enabling you to define explicit conditions that determine when a service is considered “healthy.” A health check is a custom script or command that Docker runs periodically to verify the health of a service. Only once a service passes the health check is it marked as healthy, allowing dependent services to start safely

Let’s look at the improved version of the previous Compose setup with health checks:

1services:
2  postgres:
3    image: postgres
4    healthcheck:
5      test: ["CMD-SHELL", "pg_isready -U postgres"]
6      interval: 10s
7      timeout: 5s
8      retries: 5
9
10  rabbitmq:
11    image: rabbitmq:3-management-alpine
12    healthcheck:
13      test: ["CMD-SHELL", "rabbitmqctl status"]
14      interval: 10s
15      timeout: 5s
16      retries: 5
17
18  auction-svc:
19    image: dockerusername/auction-svc:latest
20    depends_on:
21      postgres:
22        condition: service_healthy
23      rabbitmq:
24        condition: service_healthy

In this configuration, postgres and rabbitmq each have a health check that ensures they are ready before other services that depend on them (like auction-svc) can start. The auction-svc now depends on the health of postgres and rabbitmq, rather than just their startup.

Benefits of this approach:

Waits for readiness: The service is only marked as ready once the health check passes. This prevents race conditions where one service tries to connect to another before it’s ready.

Customizable: You can define the check frequency, timeout, and retry mechanism to suit the needs of your services.

Ensures operational stability: Services will not attempt to interact with dependencies until they are fully functional, reducing the chance of crashes and errors.

What to use and when?

Simple setups: In the course I just used the ‘depends_on’ for simplicity, with the knowledge that we will be introducing Kubernetes later on which does not have a depends on type feature, and the recommendation is to handle application dependancies in code rather than infrastructure.

Mission critical setups: In scenarios where service availability and readiness are crucial, especially for databases or message brokers, always use healthcheck to avoid failures due to timing issues.

Summary

While depends_on is a simple and easy-to-use directive for basic service startup control, it falls short when managing more complex services with longer or uncertain initialization times. By incorporating health checks into your Docker Compose configuration, you can significantly improve the reliability of your containerized services, ensuring that they are not only started but are fully ready to serve requests.

Complete example

Example docker-compose.yml file you can use in the course with health checks in place of depends_on:

1services:
2  postgres:
3    image: postgres
4    environment:
5      - POSTGRES_PASSWORD=jiiNfamPf4eYWDfGCKXm
6    ports:
7      - 5432:5432
8    volumes:
9      - /var/lib/postgresql/data
10    healthcheck:
11      test: ["CMD-SHELL", "pg_isready -U postgres"]
12      interval: 10s
13      timeout: 5s
14      retries: 5
15
16  mongodb:
17    image: mongo
18    environment:
19      - MONGO_INITDB_ROOT_USERNAME=root
20      - MONGO_INITDB_ROOT_PASSWORD=jiiNfamPf4eYWDfGCKXm
21    ports:
22      - 27017:27017
23    volumes:
24      - /data/db
25    healthcheck:
26      test: echo 'db.runCommand("ping").ok' | mongosh autoshost:27017/test --quiet
27
28  rabbitmq:
29    image: rabbitmq:3-management-alpine
30    environment:
31      - RABBITMQ_DEFAULT_USER=rabbit
32      - RABBITMQ_DEFAULT_PASS=jiiNfamPf4eYWDfGCKXm
33    container_name: rabbitmq
34    ports:
35      - "5672:5672"
36      - "15672:15672"
37    healthcheck:
38      test: ["CMD-SHELL", "rabbitmqctl status"]
39      interval: 10s
40      timeout: 5s
41      retries: 5
42
43  auction-svc:
44    image: dockerusername/auction-svc:latest
45    build: 
46      context: .
47      dockerfile: src/AuctionService/Dockerfile
48    environment:
49      - ASPNETCORE_ENVIRONMENT=Development
50      - ASPNETCORE_URLS=http://+:80
51      - ASPNETCORE_URLS=http://+:7777
52      - RabbitMq__Host=rabbitmq
53      - RabbitMQ__Username=rabbit
54      - RabbitMQ__Password=jiiNfamPf4eYWDfGCKXm
55      - ConnectionStrings__DefaultConnection=Server=postgres;User Id=postgres;Password=jiiNfamPf4eYWDfGCKXm;Database=auctions
56      - IdentityServiceUrl=http://identity-svc
57      - Kestrel__Endpoints__Grpc__Protocols=Http2
58      - Kestrel__Endpoints__Grpc__Url=http://+:7777
59      - Kestrel__Endpoints__WebApi__Protocols=Http1
60      - Kestrel__Endpoints__WebApi__Url=http://+:80
61    ports:
62      - 7001:80
63      - 7777:7777
64    depends_on:
65      postgres:
66        condition: service_healthy
67      rabbitmq:
68        condition: service_healthy
69
70  search-svc:
71    image: dockerusername/search-svc:latest
72    build:
73      context: .
74      dockerfile: src/SearchService/Dockerfile
75    environment:
76      - ASPNETCORE_ENVIRONMENT=Development
77      - ASPNETCORE_URLS=http://+:80
78      - RabbitMq__Host=rabbitmq
79      - RabbitMQ__Username=rabbit
80      - RabbitMQ__Password=jiiNfamPf4eYWDfGCKXm
81      - ConnectionStrings__MongoDbConnection=mongodb://root:jiiNfamPf4eYWDfGCKXm@mongodb
82      - AuctionServiceUrl=http://auction-svc
83    ports:
84      - 7002:80
85    depends_on:
86      mongodb:
87        condition: service_healthy
88      rabbitmq:
89        condition: service_healthy
90
91  identity-svc:
92    image: dockerusername/identity-svc:latest
93    build:
94      context: .
95      dockerfile: src/IdentityService/Dockerfile
96    environment:
97      - ASPNETCORE_ENVIRONMENT=Docker
98      - ASPNETCORE_URLS=http://+:80
99      - IssuerUri=http://id.carsties.local
100      - ClientApp=http://app.carsties.local
101      - ConnectionStrings__DefaultConnection=Server=postgres; User Id=postgres; Password=jiiNfamPf4eYWDfGCKXm; Database=identity
102      - VIRTUAL_HOST=id.carsties.local
103    depends_on:
104      postgres:
105        condition: service_healthy
106
107  gateway-svc:
108    image: dockerusername/gateway-svc:latest
109    build:
110      context: .
111      dockerfile: src/GatewayService/Dockerfile
112    environment:
113      - ASPNETCORE_ENVIRONMENT=Docker
114      - ASPNETCORE_URLS=http://+:80
115      - ClientApp=http://app.carsties.local
116      - VIRTUAL_HOST=api.carsties.local
117
118  bid-svc:
119    image: dockerusername/bid-svc:latest
120    build: 
121      context: .
122      dockerfile: src/BiddingService/Dockerfile
123    environment:
124      - ASPNETCORE_ENVIRONMENT=Development
125      - ASPNETCORE_URLS=http://+:80
126      - RabbitMq__Host=rabbitmq
127      - RabbitMQ__Username=rabbit
128      - RabbitMQ__Password=jiiNfamPf4eYWDfGCKXm
129      - ConnectionStrings__BidDbConnection=mongodb://root:jiiNfamPf4eYWDfGCKXm@mongodb
130      - IdentityServiceUrl=http://identity-svc
131      - GrpcAuction=http://auction-svc:7777
132    ports:
133      - 7003:80
134    depends_on:
135      mongodb:
136        condition: service_healthy
137      rabbitmq:
138        condition: service_healthy
139
140  notify-svc:
141    image: dockerusername/notify-svc:latest
142    build: 
143      context: .
144      dockerfile: src/NotificationService/Dockerfile
145    environment:
146      - ASPNETCORE_ENVIRONMENT=Development
147      - ASPNETCORE_URLS=http://+:80
148      - RabbitMq__Host=rabbitmq
149      - RabbitMQ__Username=rabbit
150      - RabbitMQ__Password=jiiNfamPf4eYWDfGCKXm
151    ports:
152      - 7004:80
153    depends_on:
154      rabbitmq:
155        condition: service_healthy
156
157  web-app:
158    image: dockerusername/web-app
159    build: 
160      context: .
161      dockerfile: frontend/web-app/Dockerfile
162    volumes:
163      - /var/lib/web/data
164    environment:
165      - AUTH_SECRET="7vgUxWjehgeKTOFH2dZu0zSeKP61o9gl0b1vuHCqeMo="
166      - AUTH_URL=http://app.carsties.local
167      - AUTH_URL_INTERNAL=http://web-app:3000
168      - API_URL=http://gateway-svc/
169      - ID_URL=http://id.carsties.local
170      - ID_URL_INTERNAL=http://identity-svc
171      - NOTIFY_URL=http://api.carsties.local/notifications
172      - VIRTUAL_HOST=app.carsties.local
173      - VIRTUAL_PORT=3000
174
175  nginx-proxy:
176    image: nginxproxy/nginx-proxy
177    container_name: nginx-proxy
178    environment:
179      - HSTS=off
180    ports:
181      - 80:80
182    volumes:
183      - /var/run/docker.sock:/tmp/docker.sock:ro