.NET Backend DevOps Tools

Waiting for dependencies in Docker Compose

Not so long ago, I got a quite interesting problem with my docker-compose file which surprisingly turned out to be quite common. In a nutshell, I had several ASP.NET Core applications (yup…microservices) which tried to connect to RabbitMQ queue during startup. Since I wanted to run both infrastructure (RabbitMQ, MongoDB) and microservices using only one command, I created mentioned docker-compose file. After all, I typed on my terminal:

 


docker-compose up --build

 

And… it didn’t work. The issue? None of the services couldn’t connect to RabbitMQ container. At first, I thought that I made a typo somewhere in my code or I used the incorrect address. Neither of these. The reason was very stupid. I forgot that starting the Docker container does not automatically mean that the service is up and running so it can take the request and process it. In my case, RabbitMQ was indeed starting but wasn’t „ready” by the time each microservice tried to connect.

 

The example

To be sure you got the problem, I prepared an example. I created very simple .NET Core console application which also connects (via RestEase) to external API during startup:

 


    public interface IApi
    {
        [Get("api/values")]
        Task<IEnumerable<string>> GetValuesAsync();
    }

    class Program
    {
        private readonly IApi _api;
        public Program()
        {
            _api = RestClient.For<IApi>("http://api:5000/");
        }

        static void Main(string[] args)
            => new Program()
                .GetValues()
                .ToList()
                .ForEach(v => Console.WriteLine(v));        
        
        IEnumerable<string> GetValues()
            =>  _api.GetValuesAsync().Result;
    }

 

In this case, the external API is not RabbitMQ but default ASP.NET Core app, therefore I’m not going to present the code. However to be sure that API won’t be ready on time I delayed its startup very naively:

 


        public static void Main(string[] args)
        {            
            System.Console.WriteLine("WAITING.....");
            Thread.Sleep(10000);
            BuildWebHost(args).Run();
        }

 

So, we have a console app which needs some data from external API and the API itself which will need some time to be „ready”. Now let’s take a look at the docker-compose file (I skipped multi-stage Dockerfiles since I took them from the official Docker page here):


version: '2'

services:
  my_console_application:
    build: ./Application
    depends_on: 
      - api

  api:
    build: ./API
    ports:
      - '5000:5000'


 

Typical docker-compose file, right? Let’s run it using the following command:


docker-compose up

 

After a while, we got this result:

 

 

So, we got to the issue from the beginning of this article. How to solve this? Well, the answer seems obvious – we need to be sure that infrastructure is up and running before we run Docker containers for other services.

 

Extending docker-compose file

Of course, at this point, I could create some bash script which would do the trick BUT! There’s no need since there’s a GitHub project already solving this issue 馃榾 The usage is very simple. First, we need to add one service to docker-compose file:

 


version: '2'

services:
  my_console_application:
    build: ./Application
    depends_on: 
      - api

  api:
    build: ./API
    ports:
      - '5000:5000'

  start_dependencies:
    image: dadarek/wait-for-dependencies
    depends_on:
      - api
    command: api:5000

 

start_dependencies uses Docker image with a magic script which does one thing. It waits until the particular endpoints are accessible over TCP. Simple is that. There are only two things you need to do:

  1. 聽List all your infrastructure dependencies under depends_on section, so you’ll be sure they will be up and running
  2. List endpoints (under command section) which need to be accessible before you launch other services (in my case that was external API from the example)

Having this done, we need to call two commands:

 


docker-compose run --rm start_dependencies
docker-compose up my_console_application

 

Because I like having everything in one place I put these two in a dedicated *.sh file, but it’s not a requirement. Let’s see the result:

 

 

Now everything works like a charm. Hopefully, this will help you someday 馃槈