608

I am using rabbitmq and a simple python sample from here together with docker-compose. My problem is that I need to wait for rabbitmq to be fully started. From what I searched so far, I don't know how to wait with container x (in my case worker) until y (rabbitmq) is started.

I found this blog post where he checks if the other host is online. I also found this docker command:

wait

Usage: docker wait CONTAINER [CONTAINER...]

Block until a container stops, then print its exit code.

Waiting for a container to stop is maybe not what I am looking for but if it is, is it possible to use that command inside the docker-compose.yml? My solution so far is to wait some seconds and check the port, but is this the way to achieve this? If I don't wait, I get an error.

docker-compose.yml

worker:
    build: myapp/.
    volumes:
    - myapp/.:/usr/src/app:ro

    links:
    - rabbitmq
rabbitmq:
    image: rabbitmq:3-management

python hello sample (rabbit.py):

import pika
import time

import socket

pingcounter = 0
isreachable = False
while isreachable is False and pingcounter < 5:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('rabbitmq', 5672))
        isreachable = True
    except socket.error as e:
        time.sleep(2)
        pingcounter += 1
    s.close()

if isreachable:
    connection = pika.BlockingConnection(pika.ConnectionParameters(
            host="rabbitmq"))
    channel = connection.channel()

    channel.queue_declare(queue='hello')

    channel.basic_publish(exchange='',
                          routing_key='hello',
                          body='Hello World!')
    print (" [x] Sent 'Hello World!'")
    connection.close()

Dockerfile for worker:

FROM python:2-onbuild
RUN ["pip", "install", "pika"]

CMD ["python","rabbit.py"]

Update Nov 2015:

A shell script or waiting inside your program is maybe a possible solution. But after seeing this Issue I am looking for a command or feature of docker/docker-compose itself.

They mention a solution for implementing a health check, which may be the best option. A open tcp connection does not mean your service is ready or may remain ready. In addition to that I need to change my entrypoint in my dockerfile.

So I am hoping for an answer with docker-compose on board commands, which will hopefully the case if they finish this issue.

Update March 2016

There is a proposal for providing a built-in way to determine if a container is "alive". So docker-compose can maybe make use of it in near future.

Update June 2016

It seems that the healthcheck will be integrated into docker in Version 1.12.0

Update January 2017

I found a docker-compose solution see: Docker Compose wait for container X before starting Y

David Maze
  • 130,717
  • 29
  • 175
  • 215
svenhornberg
  • 14,376
  • 9
  • 40
  • 56
  • 3
    Using healthchecks in has been deprecated in docker-compose 2.3 to encourage making distributed systems fault tolerant. See: https://docs.docker.com/compose/startup-order/ – Kmaid Jul 11 '17 at 15:29
  • I've run across this problem several times. You can overcome it, but docker-compose is going to fight you every step of the way. If you want setup-test-teardown container control, you're better of with something like conducto. – MatrixManAtYrService Dec 18 '20 at 02:21
  • @Kmaid **Update:** Support for `healthchecks` and `depends_on.condition` has actually made a comeback in v3. So you CAN actually use it — see [release notes](https://docs.docker.com/compose/release-notes/#1270) (the important bit is where it says v2 and v3 specs have been merged) + here are the [latest specs](https://github.com/compose-spec/compose-spec/blob/master/spec.md#long-syntax-1). – Arad Alvand Dec 20 '22 at 20:06

20 Answers20

561

Finally found a solution with a docker-compose method. Since docker-compose file format 2.1 you can define healthchecks.

I did it in a example project you need to install at least docker 1.12.0+. I also needed to extend the rabbitmq-management Dockerfile, because curl isn't installed on the official image.

Now I test if the management page of the rabbitmq-container is available. If curl finishes with exitcode 0 the container app (python pika) will be started and publish a message to hello queue. Its now working (output).

docker-compose (version 2.1):

version: '2.1'

services:
  app:
    build: app/.
    depends_on:
      rabbit:
        condition: service_healthy
    links: 
        - rabbit

  rabbit:
    build: rabbitmq/.
    ports: 
        - "15672:15672"
        - "5672:5672"
    healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:15672"]
        interval: 30s
        timeout: 10s
        retries: 5

output:

rabbit_1  | =INFO REPORT==== 25-Jan-2017::14:44:21 ===
rabbit_1  | closing AMQP connection <0.718.0> (172.18.0.3:36590 -> 172.18.0.2:5672)
app_1     |  [x] Sent 'Hello World!'
healthcheckcompose_app_1 exited with code 0

Dockerfile (rabbitmq + curl):

FROM rabbitmq:3-management
RUN apt-get update
RUN apt-get install -y curl 
EXPOSE 4369 5671 5672 25672 15671 15672

Version 3 no longer supports the condition form of depends_on. So i moved from depends_on to restart on-failure. Now my app container will restart 2-3 times until it is working, but it is still a docker-compose feature without overwriting the entrypoint.

docker-compose (version 3):

version: "3"

services:

  rabbitmq: # login guest:guest
    image: rabbitmq:management
    ports:
    - "4369:4369"
    - "5671:5671"
    - "5672:5672"
    - "25672:25672"
    - "15671:15671"
    - "15672:15672"
    healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:15672"]
        interval: 30s
        timeout: 10s
        retries: 5

  app:
    build: ./app/
    environment:
      - HOSTNAMERABBIT=rabbitmq
    restart: on-failure
    depends_on:
      - rabbitmq
    links: 
        - rabbitmq
Gray
  • 2,333
  • 1
  • 19
  • 24
svenhornberg
  • 14,376
  • 9
  • 40
  • 56
  • 2
    Hi, can this be done for postgres as well? I cannot make it work I'm doing the test like this: `test: ["CMD", "curl", "-f", "http://localhost:5432"]` – Willemoes Jan 29 '17 at 22:09
  • 2
    hi, curl is not the right tool, you need to ping that port like `test: ["CMD", "ping", "-c","1", "localhost:5432"]` or call against a http endpoint with curl. – svenhornberg Jan 30 '17 at 08:33
  • 8
    @svenhornberg `ping` uses ICMP so doesn't support TCP ports. Maybe `nc` to test a TCP port. Probably better to use `psql -h localhost -p 5432` and query something. – Matt Feb 13 '17 at 05:14
  • 47
    "depends on" has been removed in version 3 https://docs.docker.com/compose/compose-file/#dependson – nha Apr 03 '17 at 11:08
  • 2
    "Version 3 no longer supports the condition form of depends_on." the official docs say. Does anyone know the reasoning behind it? – Naymesh Mistry Apr 20 '17 at 04:46
  • 5
    I think the main reason is, that an application itself should always be able to wait or recovery if others services are not available (look for resilience in micro service context) – svenhornberg Apr 20 '17 at 08:33
  • 73
    @nha It looks like the `condition` form of `depends_on` is removed, but `depends_on` itself is still around in v3 – akivajgordon Jul 26 '17 at 20:25
  • 29
    How can healthchecks still be used to control startup order if `depends_on` with `condition` has been removed? – Franz Aug 31 '17 at 15:49
  • 1
    @Franz had this problem using version 3+. Took me time to find this solution, that is why I post it here: https://docs.docker.com/compose/startup-order/ In a nutshell it suggests using a script which checks when to run it. – Low Jun 12 '18 at 08:49
  • 1
    I used the `rabbitmq:3-management-alpine` image which has wget available (the `rabbitmq:3-management` image may as well) and then did the health check like so: `["CMD", "wget", "-qO-", "http://localhost:15672"]` – unsunghero Oct 10 '18 at 14:35
  • 12
    unfortunately, `restart: on-failure` is not an option in case of running tests :C – TooroSan Oct 11 '18 at 00:53
  • 6
    Keep using Compose v2, as Docker themselves promote Compose v3 as a _sidegrade_ to v2. More in [this discussion](https://github.com/docker/compose/issues/4958#issuecomment-316227127) – Agustín Nov 13 '18 at 14:00
  • 176
    Hard to believe this such a pain still – npr Mar 29 '19 at 05:28
  • @Agustín I've read that several times now but it still feels wrong to use v2 when v3 is available. It might be a viable option "for now", but who knows when they deprecate v2? What if noone tells me and all of a sudden all our images stop working? No thanks. – Moritz Friedrich Mar 25 '20 at 11:08
  • 1
    You can exploit the bash `/dev/tcp` built-int feature: `["CMD", "bash", "-c", "echo hi > /dev/tcp/localhost/5672"]` – tux_mind Aug 12 '20 at 21:42
  • this is the ugliest solution but still, I didn't find an alternative. Thank you – Charbel.AY Apr 02 '21 at 19:40
  • `["CMD-SHELL", "if rabbitmqctl status; then \nexit 0 \nfi \nexit 1"]` worked without having to change the official image – badchoosed May 04 '21 at 16:48
  • 2
    v3 _does_ support `depends_on`, even the `condition` form. However see https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-file-v3.md#depends_on "The depends_on option is ignored when deploying a stack in swarm mode with a version 3 Compose file." – Benissimo Aug 17 '21 at 18:09
  • [ "CMD", "nc", "-z", "localhost", "5672" ] is also possible – CountZero Sep 24 '21 at 21:19
  • Just to test it out I gave `["CMD", "sleep", "20"]` as the test command and 5s as interval for 3 retries. But my second container always just immediately starts after the first. No sleep at all. Does this not work with 3.8 or am I missing something ? – Devansh Sharma Feb 27 '22 at 17:47
  • For RabbitMQ, [this answer](https://devops.stackexchange.com/a/12200/19069) suggests using `rabbitmq-diagnostics -q ping` so that you don't need an extra installation. – Emre Mar 04 '22 at 14:26
  • Looks like we have another tool, I haven't tested it but looks a lot better than whatever have been used before, please check this link - https://www.datanovia.com/en/lessons/docker-compose-wait-for-container-using-wait-tool/ – Lema Oct 09 '22 at 10:22
  • It does't work. It is not waiting – Matteo Nov 25 '22 at 12:47
  • 5
    Note that the support for `depends_on..condition` has actually made a comeback in v3 (and v2, of course), so you can use it — see [release notes](https://docs.docker.com/compose/release-notes/#1270) and here are the [latest specs](https://github.com/compose-spec/compose-spec/blob/master/spec.md#long-syntax-1). – Arad Alvand Dec 20 '22 at 20:04
112

Quite recently they've added the depends_on feature.

Edit:

As of compose version 2.1+ till version 3 you can use depends_on in conjunction with healthcheck to achieve this:

From the docs:

version: '2.1'
services:
  web:
    build: .
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
  redis:
    image: redis
  db:
    image: redis
    healthcheck:
      test: "exit 0"

Before version 2.1

You can still use depends_on, but it only effects the order in which services are started - not if they are ready before the dependant service is started.

It seems to require at least version 1.6.0.

Usage would look something like this:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres 

From the docs:

Express dependency between services, which has two effects:

  • docker-compose up will start services in dependency order. In the following example, db and redis will be started before web.
  • docker-compose up SERVICE will automatically include SERVICE’s dependencies. In the following example, docker-compose up web will also create and start db and redis.

Note: As I understand it, although this does set the order in which containers are loaded. It does not guarantee that the service inside the container has actually loaded.

For example, you postgres container might be up. But the postgres service itself might still be initializing within the container.

Headmaster
  • 2,008
  • 4
  • 24
  • 51
toast38coza
  • 8,650
  • 3
  • 39
  • 32
  • 13
    [dnephin](https://github.com/dnephin) wrote: depends_on is only ordering. To actually delay the starting of another container there would need to be some way to detect when a process has finished initializing itself. – svenhornberg Mar 01 '16 at 07:15
  • 3
    `depends_on` doesn't wait until the container is in `ready` state(whatever that can mean in your case). It only waits until the container is in 'running' state. – tortuga Aug 31 '17 at 13:20
  • 3
    @akauppi I don't find any such quote. Instead I see that version 3 _does_ support `depends_on` (with a note about how it's not supported if you deploy in swarm mode) See https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on And I've tested locally with docker-compose version 3.7 and it _does_ support `depends_on` with the condition form. – Benissimo Aug 17 '21 at 18:17
  • @Benissimo You do realise my comment is from 2017? Decades in Docker time. – akauppi Aug 18 '21 at 15:00
  • 1
    @akauppi Sure, I recognize a lot has likely changed since then. Even though this question/answer is old it still appears high in search results for how to manage startup order in Docker. Other devs may stumble across these comments as I did and may find updates useful. I suppose the condition form of `depends_on` was removed at some point and later restored. – Benissimo Aug 23 '21 at 11:11
  • 1
    Thank you so much, especially for explanation and reference to docs. I solved my problem with your help. – kokserek Nov 25 '21 at 02:39
88

Natively that is not possible, yet. See also this feature request.

So far you need to do that in your containers CMD to wait until all required services are there.

In the Dockerfiles CMD you could refer to your own start script that wraps starting up your container service. Before you start it, you wait for a depending one like:

Dockerfile

FROM python:2-onbuild
RUN ["pip", "install", "pika"]
ADD start.sh /start.sh
CMD ["/start.sh"]

start.sh

#!/bin/bash
while ! nc -z rabbitmq 5672; do sleep 3; done
python rabbit.py

Probably you need to install netcat in your Dockerfile as well. I do not know what is pre-installed on the python image.

There are a few tools out there that provide easy to use waiting logic, for simple tcp port checks:

For more complex waits:

Henrik Sachse
  • 51,228
  • 7
  • 46
  • 59
  • Could you explain what you mean by CMD ? Does this mean my programm has to do it, like i did it with a port check ? Or do you mean a specific CMD from e.g. linux for this ? – svenhornberg Jul 31 '15 at 22:56
  • thank you for explaining, I upvote your answer.But I think the upcoming feature request, would be the right answer to my question so I leave it unanswered so far. – svenhornberg Aug 01 '15 at 09:27
65

Using restart: unless-stopped or restart: always may solve this problem.

If worker container stops when rabbitMQ is not ready, it will be restarted until it is.

Toilal
  • 3,301
  • 1
  • 24
  • 32
  • 5
    I like this solution for this case, but it doesn't work for containers that don't exit when one of the subprocesses that it runs fails. For example, a Tomcat container would continue to run even if a Java servlet that it ran were to fail to connect to a database server. Granted, Docker containers render servlet containers like Tomcat mostly unnecessary. – Derek Mahar May 06 '16 at 01:50
  • @DerekMahar, if you have a Java-based web application that only serves REST calls, what do you use instead of Jetty/Tomcat? – JoeG Dec 21 '16 at 19:31
  • 2
    @JoeG, I meant Tomcat the servlet container that can host many applications, not embedded Tomcat. Docker renders the former mostly unnecessary, while making the latter more popular for microservices, for example. – Derek Mahar Dec 21 '16 at 20:11
25

If you want to start service only then another service successfully completed (for example migration, data population, etc), docker-compose version 1.29, comes with build in functionality for this - service_completed_successfully.

depends_on:
  <service-name>:
    condition: service_completed_successfully

According to specification:

service_completed_successfully - specifies that a dependency is expected to run to successful completion before starting a dependent service

zooblin
  • 2,172
  • 2
  • 27
  • 33
  • 1
    This is really nice! Would you recommend using this over using ```healthcheck```? What are the differences between the two? – William Le Sep 15 '22 at 17:20
  • 1
    @SeanWilliam no, the purpose of the `healthcheck` is different, it should to determine whenever container is healthy. `depends_on` with `service_completed_successfully` condition can be used for setup, for example create service's database, populate db with data, run migrations, run dependency install, etc.. - instead of putting all setup inside single container, you can separate the whole setup to small dedicated containers, and than configure the actual service to be dependent on setup containers with `service_completed_successfully` – zooblin Sep 16 '22 at 07:37
  • @zooblin When I use it it just pauses for me after the depended on service completes, the depending service just doesn't happen, any help? – Amon Apr 21 '23 at 02:21
24

you can also just add it to the command option eg.

command: bash -c "sleep 5; start.sh"

https://github.com/docker/compose/issues/374#issuecomment-156546513

to wait on a port you can also use something like this

command: bash -c "while ! curl -s rabbitmq:5672 > /dev/null; do echo waiting for xxx; sleep 3; done; start.sh"

to increment the waiting time you can hack a bit more:

command: bash -c "for i in {1..100} ; do if ! curl -s rabbitmq:5672 > /dev/null ; then echo waiting on rabbitmq for $i seconds; sleep $i; fi; done; start.sh"
AmanicA
  • 4,659
  • 1
  • 34
  • 49
19

restart: on-failure did the trick for me..see below

---
version: '2.1'
services:
  consumer:
    image: golang:alpine
    volumes:
      - ./:/go/src/srv-consumer
    working_dir: /go/src/srv-consumer
    environment:
      AMQP_DSN: "amqp://guest:guest@rabbitmq:5672"
    command: go run cmd/main.go
    links:
          - rabbitmq
    restart: on-failure

  rabbitmq:
    image: rabbitmq:3.7-management-alpine
    ports:
      - "15672:15672"
      - "5672:5672"
Edwin O.
  • 4,998
  • 41
  • 44
14

For container start ordering use

depends_on:

For waiting previous container start use script

entrypoint: ./wait-for-it.sh db:5432

This article will help you https://docs.docker.com/compose/startup-order/

quit
  • 282
  • 2
  • 9
  • 7
    @svenhornberg in the comment, you link, there is no explanation about wait-for-it.sh feature. – quit Dec 02 '16 at 19:13
11

Tried many different ways, but liked the simplicity of this: https://github.com/ufoscout/docker-compose-wait

The idea that you can use ENV vars in the docker compose file to submit a list of services hosts (with ports) which should be "awaited" like this: WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017.

So let's say you have the following docker-compose.yml file (copy/past from repo README):

version: "3"

services:

  mongo:
    image: mongo:3.4
    hostname: mongo
    ports:
      - "27017:27017"

  postgres:
    image: "postgres:9.4"
    hostname: postgres
    ports:
      - "5432:5432"

  mysql:
    image: "mysql:5.7"
    hostname: mysql
    ports:
      - "3306:3306"

  mySuperApp:
    image: "mySuperApp:latest"
    hostname: mySuperApp
    environment:
      WAIT_HOSTS: postgres:5432, mysql:3306, mongo:27017

Next, in order for services to wait, you need to add the following two lines to your Dockerfiles (into Dockerfile of the services which should await other services to start):

ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

The complete example of such sample Dockerfile (again from the project repo README):

FROM alpine

## Add your application to the docker image
ADD MySuperApp.sh /MySuperApp.sh

## Add the wait script to the image
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.5.0/wait /wait
RUN chmod +x /wait

## Launch the wait tool and then your application
CMD /wait && /MySuperApp.sh

For other details about possible usage see README

Evereq
  • 1,662
  • 1
  • 18
  • 23
  • I was looking for similiar answer. I've usually worked with https://hub.docker.com/r/dadarek/wait-for-dependencies as it uses netcat underneath. The one you've provided is Rust-based. Can't comment on the quality of yours, but for me no additional layers is a definite pro. – Filip Malczak Jun 15 '19 at 11:00
  • 8
    I strongly recommend against this on grounds of security. You're running an arbitrary executable from a hyperlink. A better solution would be to do the same thing with a static script that's copied in to the image with COPY – Paul K Apr 06 '20 at 14:47
  • 1
    @PaulK of course, it's understandable that running anything from hyperlink is not secure, but it's just a demo above how to make `https://github.com/ufoscout/docker-compose-wait` library working :) The way how you use that library does not change an answer that you can utilize some lib. Security is a complex topic and if we go far, we should check what that library is doing inside anyway, even if we COPY it :) So better to be more specific in your comment like: "I strongly recommend against the usage of that library from hyperlink". Hope you agree, thanks for a hint! – Evereq Apr 07 '20 at 15:23
7

You can also solve this by setting an endpoint which waits for the service to be up by using netcat (using the docker-wait script). I like this approach as you still have a clean command section in your docker-compose.yml and you don't need to add docker specific code to your application:

version: '2'
services:
  db:
    image: postgres
  django:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    entrypoint: ./docker-entrypoint.sh db 5432
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

Then your docker-entrypoint.sh:

#!/bin/sh

postgres_host=$1
postgres_port=$2
shift 2
cmd="$@"

# wait for the postgres docker to be running
while ! nc $postgres_host $postgres_port; do
  >&2 echo "Postgres is unavailable - sleeping"
  sleep 1
done

>&2 echo "Postgres is up - executing command"

# run the command
exec $cmd

This is nowadays documented in the official docker documentation.

PS: You should install netcat in your docker instance if this is not available. To do so add this to your Docker file :

RUN apt-get update && apt-get install netcat-openbsd -y 
maerteijn
  • 489
  • 6
  • 9
5

There is a ready to use utility called "docker-wait" that can be used for waiting.

Adrian Mitev
  • 4,722
  • 3
  • 31
  • 53
  • 1
    Thank you, but it is only a shell script so it is like h3nrik answer or waiting inside python. It is not a feature of docker-compose itself. May you have a look in https://github.com/docker/compose/issues/374 they plan to implement a healthcheck which would be the best way. A open tcp connection does not mean your service is ready or may remain ready. In addition to that I need to change my entrypoint in my dockerfile. – svenhornberg Nov 11 '15 at 14:48
5

basing on this blog post https://8thlight.com/blog/dariusz-pasciak/2016/10/17/docker-compose-wait-for-dependencies.html

I configured my docker-compose.yml as shown below:

version: "3.1"

services:
  rabbitmq:
    image: rabbitmq:3.7.2-management-alpine
    restart: always
    environment:
      RABBITMQ_HIPE_COMPILE: 1
      RABBITMQ_MANAGEMENT: 1
      RABBITMQ_VM_MEMORY_HIGH_WATERMARK: 0.2
      RABBITMQ_DEFAULT_USER: "rabbitmq"
      RABBITMQ_DEFAULT_PASS: "rabbitmq"
    ports:
      - "15672:15672"
      - "5672:5672"
    volumes:
      - data:/var/lib/rabbitmq:rw

  start_dependencies:
    image: alpine:latest
    links:
      - rabbitmq
    command: >
      /bin/sh -c "
        echo Waiting for rabbitmq service start...;
        while ! nc -z rabbitmq 5672;
        do
          sleep 1;
        done;
        echo Connected!;
      "

volumes:
  data: {}

Then I do for run =>:

docker-compose up start_dependencies

rabbitmq service will start in daemon mode, start_dependencies will finish the work.

Igor Komar
  • 381
  • 1
  • 8
  • 16
  • lol, making query via `"curl", "-f", "http://localhost:15672"` for which u need to install `management` plugin and using healthcheck that already deprecated - its best answer. Simple working example with check via `nc` its - downvote. ha, ok... – Igor Komar Jan 12 '18 at 01:26
  • 1
    the answer does not use a native docker feature, its irrelevant if you use curl, nc or other tools. while! nc is the same as already posted in other answers. – svenhornberg Jan 12 '18 at 08:09
  • 1
    native docker features: 1. https://docs.docker.com/compose/startup-order/ 2. https://github.com/docker/compose/issues/5007 3. https://github.com/docker/compose/issues/374 – Igor Komar Jan 12 '18 at 09:00
  • 1
    @IgorKomar, thanks man, you saved my day! :3 I used almost same mechanic to check mysql server is ready before actual application is started. ;) I am passing similar command to the `docker-compose run --name app-test --rm "app" bash -l -c 'echo Waiting for mysql service start... && while ! nc -z db-server 3306; do sleep 1; done && echo Connected! && /bin/bash /script/ci_tests.sh'` – TooroSan Oct 11 '18 at 00:49
4

In version 3 of a Docker Compose file, you can use RESTART.

For example:

docker-compose.yml

worker:
    build: myapp/.
    volumes:
    - myapp/.:/usr/src/app:ro
    restart: on-failure
    depends_on:
    - rabbitmq
rabbitmq:
    image: rabbitmq:3-management

Note that I used depends_on instead of links since the latter is deprecated in version 3.

Even though it works, it might not be the ideal solution since you restart the docker container at every failure.

Have a look to RESTART_POLICY as well. it let you fine tune the restart policy.

When you use Compose in production, it is actually best practice to use the restart policy :

Specifying a restart policy like restart: always to avoid downtime

Mathieu Gemard
  • 514
  • 7
  • 11
4

After trying several approaches, IMO the simplest and most elegant option is using the jwilder/dockerize utility image (mentioned by @Henrik Sachse but he did not show a concret example) with its -wait flag. Here is a simple example where I need a RabbitMQ to be ready before starting my app:

version: "3.8"
services:
  # Start RabbitMQ.
  rabbit:
    image: rabbitmq

  # Wait for RabbitMQ to be joinable.
  check-rabbit-started: 
    image: jwilder/dockerize:0.6.1
    depends_on:
      - rabbit
    command: 'dockerize -wait=tcp://rabbit:5672'
  
  # Only start myapp once RabbitMQ is joinable.
  myapp:
    image: myapp:latest
    depends_on:
      - check-rabbit-started
Ewaren
  • 1,343
  • 1
  • 10
  • 18
3

Not recommended for serious deployments, but here is essentially a "wait x seconds" command.

With docker-compose version 3.4 a start_period instruction has been added to healthcheck. This means we can do the following:

docker-compose.yml:

version: "3.4"
services:
  # your server docker container
  zmq_server:
    build:
      context: ./server_router_router
      dockerfile: Dockerfile

  # container that has to wait
  zmq_client:
    build:
      context: ./client_dealer/
      dockerfile: Dockerfile
    depends_on:
      - zmq_server
    healthcheck:
      test: "sh status.sh"
      start_period: 5s

status.sh:

#!/bin/sh

exit 0

What happens here is that the healthcheck is invoked after 5 seconds. This calls the status.sh script, which always returns "No problem". We just made zmq_client container wait 5 seconds before starting!

Note: It's important that you have version: "3.4". If the .4 is not there, docker-compose complains.

NumesSanguis
  • 5,832
  • 6
  • 41
  • 76
  • 2
    As a naive "wait 5s" solution, this one's quite ingenious. I would upvote, but I won't because this doesn't really work with prod-like setups and I'm afraid that someone would look at number of votes instead reading carefully. Still, I wanted to say "man, that's smart" ;) – Filip Malczak Jun 15 '19 at 10:56
  • PS. For more complicated solutions, see the Evereq's answer – Filip Malczak Jun 15 '19 at 11:06
  • 1
    That's *not* what `start_period` does. That configuration means there is a grace period where failed health checks don't count as retries. If it succeeds early, it's considered healthy. After the start period, a failure will count as a retry. See https://docs.docker.com/engine/reference/builder/#healthcheck – Capi Etheriel Oct 28 '19 at 13:33
1

One of the alternative solution is to use a container orchestration solution like Kubernetes. Kubernetes has support for init containers which run to completion before other containers can start. You can find an example here with SQL Server 2017 Linux container where API container uses init container to initialise a database

https://www.handsonarchitect.com/2018/08/understand-kubernetes-object-init.html

Nilesh Gule
  • 1,511
  • 12
  • 13
  • This would be my suggestion too. Sure the hacks work but a little bit of elbow grease gets you a TON more out of k8s. – Jose Gleeson Jan 11 '23 at 14:19
1

I currently also have that requirement of waiting for some services to be up and running before others start. Also read the suggestions here and on some other places. But most of them require that the docker-compose.yml some how has to be changed a bit. So I started working on a solution which I consider to be an orchestration layer around docker-compose itself and I finally came up with a shell script which I called docker-compose-profile. It can wait for tcp connection to a certain container even if the service does not expose any port to the host directy. The trick I am using is to start another docker container inside the stack and from there I can (usually) connect to every service (as long no other network configuration is applied). There is also waiting method to watch out for a certain log message. Services can be grouped together to be started in a single step before another step will be triggered to start. You can also exclude some services without listing all other services to start (like a collection of available services minus some excluded services). This kind of configuration can be bundled to a profile. There is a yaml configuration file called dcp.yml which (for now) has to be placed aside your docker-compose.yml file.

For your question this would look like:

command:
  aliases:
    upd:
      command: "up -d"
      description: |
        Create and start container. Detach afterword.

profiles:
  default:
    description: |
      Wait for rabbitmq before starting worker.
    command: upd
    steps:
      - label: only-rabbitmq
        only: [ rabbitmq ]
        wait:
          - 5@tcp://rabbitmq:5432
      - label: all-others

You could now start your stack by invoking

dcp -p default upd

or even simply by

dcp

as there is only a default profile to run up -d on.

There is a tiny problem. My current version does not (yet) support special waiting condition like the ony You actually need. So there is no test to send a message to rabbit.

I have been already thinking about a further waiting method to run a certain command on host or as a docker container. Than we could extend that tool by something like

...
        wait:
          - service: rabbitmq
            method: container
            timeout: 5
            image: python-test-rabbit
...

having a docker image called python-test-rabbit that does your check.

The benefit then would be that there is no need anymore to bring the waiting part to your worker. It would be isolated and stay inside the orchestration layer.

May be someone finds this helpful to use. Any suggestions are very welcome.

You can find this tool at https://gitlab.com/michapoe/docker-compose-profile

MichaPoe
  • 101
  • 5
0

Here is the example where main container waits for worker when it start responding for pings:

version: '3'
services:
  main:
    image: bash
    depends_on:
     - worker
    command: bash -c "sleep 2 && until ping -qc1 worker; do sleep 1; done &>/dev/null"
    networks:
      intra:
        ipv4_address: 172.10.0.254
  worker:
    image: bash
    hostname: test01
    command: bash -c "ip route && sleep 10"
    networks:
      intra:
        ipv4_address: 172.10.0.11
networks:
  intra:
    driver: bridge
    ipam:
      config:
      - subnet: 172.10.0.0/24

However, the proper way is to use healthcheck (>=2.1).

kenorb
  • 155,785
  • 88
  • 678
  • 743
0

I guess the docker people really want us to wait on services using code in our own images. I still want to configure the services to wait for in docker-compose.yml. Here's one way if you're willing to use an entrypoint script.

Add this loop to your entrypoint script, using your choice of wait-for-it tool included in the image. I am using https://github.com/vishnubob/wait-for-it/. If you pass no services, the loop does nothing.

for service in "$@"; do
    echo "$0: wait for service $service"
    if ! wait-for-it "$service"; then
        echo "$0: failed on service $service"
        exit 1
    fi
done

Pass required services with this entry for the container in docker-compose.yml:

    command: ["my-data-svc:5000"]

This relies on the behavior that docker commands are passed as arguments to the entrypoint script. You can probably make a convincing argument that I'm abusing the intent of the docker command here. I'm not gonna die on that hill, it just works for me.

chrisinmtown
  • 3,571
  • 3
  • 34
  • 43
-6

I just have 2 compose files and start one first and second one later. My script looks like that:

#!/bin/bash
#before i build my docker files
#when done i start my build docker-compose
docker-compose -f docker-compose.build.yaml up
#now i start other docker-compose which needs the image of the first
docker-compose -f docker-compose.prod.yml up
  • This is not considered a good practice. You cannot deliver the solution consisting of multiple conatiners from one compose file then. – juergi May 13 '20 at 09:16