Docker: Wait until MySQL is available

At work we're extensively using Docker to pack up web applications. After starting a container stack (at least web + database container), database tasks like migrations or cache clearing needs to be done.

Unfortunately, docker reports a MySQL/MariaDB database container as available when MySQL itself is not ready to take client connections. This means calling docker-compose up and right after that running the migration will fail because the appplication cannot connect to the database.

MySQL is not available immediately after starting the container, because at the beginning it either has to run initialization .sql scripts or load and check the existing database files, which takes time.

Up to now the solution was to sleep 30 and hope that this is long enough for MySQL to finish startup. But this also means wasted time when MySQL only takes 5 seconds.

These are the things I tried:

depends_on

In docker-compose.yml, you can say that the web container may be started only after the database container started with depends_on.

As described above, the container itself is running, but MySQL is not - so from docker's persepective depends_on is fulfilled after the container started, which does not help us.

mysqladmin ping

Some suggest to use mysqladmin ping to check if the database is available.

I found this to be not reliable; on my machines the ping succeeded but the clients still could not connect.

Client connection test

A couple of projects try to do a real MySQL client connection to check if the database is up, see Wordpress and Bonita. See the description in docker-library.

I ended up doing the same by adding a script to the MySQL container that can be started from outside:

/root/waitForMySQL.sh
#!/bin/sh
# wait until MySQL is really available
maxcounter=45
 
counter=1
while ! mysql --protocol TCP -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "show databases;" > /dev/null 2>&1; do
    sleep 1
    counter=`expr $counter + 1`
    if [ $counter -gt $maxcounter ]; then
        >&2 echo "We have been waiting for MySQL too long already; failing."
        exit 1
    fi;
done

This script is added in the Dockerfile to the default MySQL container:

Dockerfile
FROM mysql:5.7

ADD waitForMySQL.sh /root/
RUN chmod +x /root/waitForMySQL.sh

Now we can run it after running docker-compose up:

$ docker-compose -p projectprefix up
$ docker exec projectprefix_mysql /root/waitForMySQL.sh
$ docker exec projectprefix_web\
    su -s/bin/sh -c 'cd /var/www && ./artisan migrate'\
    www-data

Update 2018-11

It's better to use --protocol TCP to be sure that not only the socket connection is ready.

Written by Christian Weiske.

Comments? Please send an e-mail.