123

When running a service inside a container, let's say mongodb, the command

docker run -d myimage

will exit instantly, and return the container id. In my CI script, I run a client to test mongodb connection, right after running the mongo container. The problem is: the client can't connect because the service is not up yet. Apart from adding a big sleep 10in my script, I don't see any option to wait for a container to be up and running.

Docker has a command wait which doesn't work in that case, because the container doesn't exist. Is it a limitation of docker?

rypel
  • 4,686
  • 2
  • 25
  • 36
Gravis
  • 30,149
  • 5
  • 23
  • 20
  • **See Also**: [Docker Compose wait for container X before starting Y](https://stackoverflow.com/q/31746182/1366033) – KyleMit Nov 19 '20 at 23:13

16 Answers16

106

Found this simple solution, been looking for something better but no luck...

until [ "`docker inspect -f {{.State.Running}} CONTAINERNAME`"=="true" ]; do
    sleep 0.1;
done;

or if you want to wait until the container is reporting as healthy (assuming you have a healthcheck)

until [ "`docker inspect -f {{.State.Health.Status}} CONTAINERNAME`"=="healthy" ]; do
    sleep 0.1;
done;
Resigned June 2023
  • 4,638
  • 3
  • 38
  • 49
superhero
  • 6,281
  • 11
  • 59
  • 91
  • 19
    one liner using while loop instead ```while [ "`docker inspect -f {{.State.Health.Status}} $container_id`" != "healthy" ]; do sleep 2; done``` – Mouath Dec 21 '18 at 05:44
  • Just a note, docker is in /usr/local/bin/docker on osx. Might be worth adding $(which docker) to make the script cross-platform? – ConorSheehan1 Jun 17 '19 at 19:01
  • @con-- sure, that would be an improvement, though I consider documentation of "cross platform scripts" as external concern to the scope of the question. Never the less, if you like to make an edit, go for it :) – superhero Jun 18 '19 at 12:30
  • 1
    this is how it worked with me: #!/bin/bash until `/usr/bin/docker inspect -f {{.State.Running}} local_mysql` ==true $ do sleep 0.1; done; echo "mysql is on" – M.Hefny Jan 23 '20 at 23:27
  • @M.Hefny That's the same example that's expressed in the answer. – superhero Jan 24 '20 at 10:05
  • yes indeed... except there is a missing do and the " & ' that I removed. – M.Hefny Jan 24 '20 at 17:18
  • 3
    In my shell I had to use spaces like this ... CONTAINERNAME`" == "healthy" – julste Feb 04 '20 at 13:24
  • timeout 30s bash -c "while [[ ! $(docker inspect -f {{.State.Health.Status}} CONTAINERNAME 2>/dev/null || echo "error") = "healthy" ]]; do sleep 1; done" – Jamie Dec 05 '21 at 16:31
  • Nice, same check works with podman too! `podman inspect -f {{.State.Running}} CONTAINERNAME` – Tuhin Jun 15 '22 at 11:34
  • For any syntax pendants: `until [ "$(docker inspect -f "{{.State.Running}}" $docker_name)" == "true" ]; do sleep 1; done;` – Alex Feb 15 '23 at 05:02
61

As commented in a similar issue for docker 1.12

HEALTHCHECK support is merged upstream as per docker/docker#23218 - this can be considered to determine when a container is healthy prior to starting the next in the order

This is available since docker 1.12rc3 (2016-07-14)

docker-compose is in the process of supporting a functionality to wait for specific conditions.

It uses libcompose (so I don't have to rebuild the docker interaction) and adds a bunch of config commands for this. Check it out here: https://github.com/dansteen/controlled-compose

You can use it in Dockerfile like this:

HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

Official docs: https://docs.docker.com/engine/reference/builder/#/healthcheck

Bobík
  • 1,828
  • 20
  • 19
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • 1
    I don't think the engine waits for the container to become healthy before marking it as `started`. Or does it? – Kolyunya Dec 28 '22 at 16:46
  • @Kolyunya I suspect it depends if you have setup a health check or not ([example](https://stackoverflow.com/a/74578627/6309)). If you did, you could get a status `health: starting` [as in here](https://stackoverflow.com/q/65949957/6309) instead of just '`started`'. – VonC Dec 28 '22 at 20:51
34

If you don't want to expose the ports, as is the case if you plan to link the container and might be running multiple instances for testing, then I found this was a good way to do it in one line :) This example is based on waiting for ElasticSearch to be ready:

docker inspect --format '{{ .NetworkSettings.IPAddress }}:9200' elasticsearch | xargs wget --retry-connrefused --tries=5 -q --wait=3 --spider

This requires wget to be available, which is standard on Ubuntu. It will retry 5 times, 3 seconds between tries, even if the connection is refused, and also does not download anything.

David Lemphers
  • 3,568
  • 3
  • 18
  • 10
  • I think you want to use `--waitretry=3` instead of `--wait=3` – jpbochi Sep 07 '15 at 17:34
  • for the curious, wget man page `--wait=seconds Wait the specified number of seconds between the retrievals.` and `--waitretry=seconds If you don't want Wget to wait between every retrieval, but only between retries of failed downloads, you can use this option. Wget will use linear backoff, waiting 1 second after the first failure on a given file, then waiting 2 seconds after the second failure on that file, up to the maximum number of seconds you specify.` – aimless May 26 '16 at 08:25
28

If the containerized service you started doesn't necessarily respond well to curl or wget requests (which is quite likely for many services) then you could use nc instead.

Here's a snippet from a host script which starts a Postgres container and waits for it to be available before continuing:

POSTGRES_CONTAINER=`docker run -d --name postgres postgres:9.3`
# Wait for the postgres port to be available
until nc -z $(sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $POSTGRES_CONTAINER) 5432
do
    echo "waiting for postgres container..."
    sleep 0.5
done

Edit - This example does not require that you EXPOSE the port you are testing, since it accesses the Docker-assigned 'private' IP address for the container. However this only works if the docker host daemon is listening on the loopback (127.x.x.x). If (for example) you are on a Mac and running the boot2docker VM, you will be unable to use this method since you cannot route to the 'private' IP addresses of the containers from your Mac shell.

Mark Henwood
  • 951
  • 2
  • 11
  • 9
  • I believe that the `--format` option has changed since this reply; what works now is `docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [NAME|ID...]` (see the example on https://docs.docker.com/engine/reference/commandline/inspect/). – Kurt Peek Feb 09 '17 at 17:40
17

Assuming that you know the host+port of your MongoDB server (either because you used a -link, or because you injected them with -e), you can just use curl to check if the MongoDB server is running and accepting connections.

The following snippet will try to connect every second, until it succeeeds:

#!/bin/sh
while ! curl http://$DB_PORT_27017_TCP_ADDR:$DB_PORT_27017_TCP_PORT/
do
  echo "$(date) - still trying"
  sleep 1
done
echo "$(date) - connected successfully"
jpetazzo
  • 14,874
  • 3
  • 43
  • 45
  • 3
    But you need to bind the port on the host :( – Gravis Jan 18 '14 at 17:28
  • I've got similar problems. Trying to set up monit using pidfiles and not being able to trigger a granular event on Docker start/stop without manually injecting vars is a pain, it means I can't just write generic wrappers as easily. – Alex Lynham Aug 18 '14 at 08:08
  • You can go with `IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' mysql)` to get the IP address of your mysql container (where "mysql" is the name or container id) and replace the url with: `http://$IP:3306`. works for me! – Danyel Sep 17 '14 at 14:33
16

I've ended up with something like:

#!/bin/bash

attempt=0
while [ $attempt -le 59 ]; do
    attempt=$(( $attempt + 1 ))
    echo "Waiting for server to be up (attempt: $attempt)..."
    result=$(docker logs mongo)
    if grep -q 'waiting for connections on port 27017' <<< $result ; then
      echo "Mongodb is up!"
      break
    fi
    sleep 2
done
Gravis
  • 30,149
  • 5
  • 23
  • 20
11

Throwing my own solution out there:

I'm using docker networks so Mark's netcat trick didn't work for me (no access from the host network), and Erik's idea doesn't work for a postgres container (the container is marked as running even though postgres isn't yet available to connect to). So I'm just attempting to connect to postgres via an ephemeral container in a loop:

#!/bin/bash

docker network create my-network
docker run -d \
    --name postgres \
    --net my-network \
    -e POSTGRES_USER=myuser \
    postgres

# wait for the database to come up
until docker run --rm --net my-network postgres psql -h postgres -U myuser; do
    echo "Waiting for postgres container..."
    sleep 0.5
done

# do stuff with the database...
Community
  • 1
  • 1
Ell Neal
  • 6,014
  • 2
  • 29
  • 54
  • That's what we're doing today. Be careful with the postgres image, as the server is starting once, before restarting... – Gravis May 09 '16 at 18:15
  • This works well, with a few minor modifications. 1. `postgres` host doesn't get resolved, so I use `docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' postgres` instead. 2. `psql` needs a password, `pg_isready` is a better fit. 3. With `pg_isready` there's also no need in `-U myuser`. – Alec Mev Nov 28 '18 at 16:49
6

If you want to wait for an opened port, you can use this simple script:

until </dev/tcp/localhost/32022; do sleep 1; done

For wait until port 32022 be able to connect.

3

I had to tackle this recetly and came up with an idea. When doing research for this task I got here, so I thought I'd share my solution with future visitors of this post.

Docker-compose-based solution

If you are using docker-compose you can check out my docker synchronization POC. I combined some of the ideas in other questions (thanks for that - upvoted).

The basic idea is that every container in the composite exposes a diagnostic service. Calling this service checks if the required set of ports is open in the container and returns the overall status of the container (WARMUP/RUNNING as per the POC). Each container also has an utility to check upon startup if the dependant services are up and running. Only then the container starts up.

In the example docker-compose environment there are two services server1 and server2 and the client service which waits for both servers to start then sends a request to both of them and exits.

Excerpt from the POC

wait_for_server.sh

#!/bin/bash

server_host=$1
sleep_seconds=5

while true; do
    echo -n "Checking $server_host status... "

    output=$(echo "" | nc $server_host 7070)

    if [ "$output" == "RUNNING" ]
    then
        echo "$server_host is running and ready to process requests."
        break
    fi

    echo "$server_host is warming up. Trying again in $sleep_seconds seconds..."
    sleep $sleep_seconds
done

Waiting for multiple containers:

trap 'kill $(jobs -p)' EXIT

for server in $DEPENDS_ON
do
    /assets/wait_for_server.sh $server &
    wait $!
done

Diagnostic srervice basic implementation (checkports.sh):

#!/bin/bash

for port in $SERVER_PORT; do
    nc -z localhost $port;

    rc=$?

    if [[ $rc != 0 ]]; then
        echo "WARMUP";
        exit;
    fi
done

echo "RUNNING";

Wiring up the diagnostic service to a port:

nc -v -lk -p 7070 -e /assets/checkports.sh
jannis
  • 4,843
  • 1
  • 23
  • 53
2

test/test_runner

#!/usr/bin/env ruby

$stdout.sync = true

def wait_ready(port)
  until (`netstat -ant | grep #{port}`; $?.success?) do
    sleep 1
    print '.'
  end
end

print 'Running supervisord'
system '/usr/bin/supervisord'

wait_ready(3000)

puts "It's ready :)"

$ docker run -v /tmp/mnt:/mnt myimage ruby mnt/test/test_runner

I'm testing like this whether the port is listening or not. In this case I have test running from inside container, but it's also possible from outside whether mongodb is ready or not.

$ docker run -p 37017:27017 -d myimage

And check whether the port 37017 is listening or not from host container.

banyan
  • 3,837
  • 2
  • 31
  • 25
1

You can use wait-for-it, "a pure bash script that will wait on the availability of a host and TCP port. It is useful for synchronizing the spin-up of interdependent services, such as linked docker containers. Since it is a pure bash script, it does not have any external dependencies".

However, you should try to design your services to avoid these kind of interdependencies between services. Can your service try to reconnect to the database? Can you let your container just die if it can't connect to the database and let a container orchestrator (e.g. Docker Swarm) do it for you?

Guido
  • 46,642
  • 28
  • 120
  • 174
1

Docker-compose solution

After docker-compose I dont know name of docker container, so I use

docker inspect -f {{.State.Running}} $(docker-compose ps -q <CONTAINER_NAME>)

and checking true like here https://stackoverflow.com/a/33520390/7438079

DarkAiR
  • 75
  • 2
1

In order to verify if a PostgreSQL or MySQL (currently) Docker container is up and running (specially for migration tools like Flyway), you can use the wait-for binary: https://github.com/arcanjoaq/wait-for.

arcanjoaq
  • 35
  • 7
0

For mongoDB docker instance we did this and works like a charm:

#!/usr/bin/env bash

until docker exec -i ${MONGO_IMAGE_NAME} mongo -u ${MONGO_INITDB_ROOT_USERNAME} -p ${MONGO_INITDB_ROOT_PASSWORD}<<EOF
exit
EOF
do
    echo "Waiting for Mongo to start..."
    sleep 0.5
done
Alex Arvanitidis
  • 4,403
  • 6
  • 26
  • 36
0

Here is what I ended up with which is similar to a previous answer just a little more concise,

until [[ $(docker logs $db_container_name) == *"waiting for connections on port 27017"* ]]
do
  echo "waiting on mongo to boot..."
  sleep 1
done
Aaron Mast
  • 249
  • 3
  • 4
0

1 : A container attached to a service with docker-compose doesn't launch when a Synology NAS starts up.

I had a problem launching a docker container on a Synology NAS that was attached to another container via docker-compose like this:

...
---
version: "3"
services:
  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
...
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    # Connect the service to gluetun
    network_mode: "service:gluetun"
...

The docker used by Synology is different or not up to date and apparently does not appreciate that a container is attached to another container with network_mode, the Synology docker considers that the container is not attached to any network and therefore can not launch the container. However in command line it works very well so I wanted to make a script to launch it automatically at the startup of my NAS by a scheduled task.

note : I creat my docker compose with portainer

2 : The until loop does not work even with all the different ways of writing the condition.

If like me on your Synology NAS you did not manage to make the until loop work as described superhero : here you will have to go through the while loop.

however with the -x argument of bash to debug my code the String comparison was well done:

output line (same with all ways of describing the expression):

...
+ [' false = true ']'
...

No matter what the result, nothing worked, I checked every time and there was always a moment when it did not work as I wanted.

4: THE SOLUTION FOR SYNOLOGY

Environment

DSM : 7.1.1
bash : 4.4.23
docker : 20.10.3

After finding the right syntax, we had to solve another following problem:

The docker container status check can only work if the synology docker package is running.

so i used synopkg with is_onoff, is_active doesn't work and status was giving too much string. so my solution gave this :

#!/bin/bash

while [ "$(synopkg is_onoff Docker)" != "package Docker is turned on" ]; do
    sleep 0.1;
done;

echo "Docker package is running..."
echo ""

while [ "$(docker inspect -f {{.State.Running}} gluetun)" = "false" ]; do
    sleep 0.1;
done;

echo "gluetun is running..."
echo ""

if [ "$(docker ps -a -f status=exited -f name=qbittorrent --format '{{.Names}}')" ]; then
    echo "Qbittorrent is not running I try to start this container"
    docker start qbittorrent
else
    echo "Qbittorrent docker is already started"
fi

So I was able to do a scheduled task with the root user at Boot-Up in the DSM configurations and it worked fine after a reboot, without checking the Synology Docker package launch status with synopkg it did not work.

NOTE

I think the version of Bash in DSM doesn't like the until loop or it is misinterpreted. Maybe this solution can work with systems where bash is in an older version and for X reasons you can't update it or you don't want to update the binaries of Bash to avoid breaking your system.

LinkPhoenix
  • 385
  • 3
  • 7