Docker-compose check if mysql connection is ready

I am trying to make sure that my app container does not run migrations / start until the db container is started and READY TO accept connections.

So I decided to use the healthcheck and depends on option in docker compose file v2.

In the app, I have the following

app:
    ...
    depends_on:
      db:
      condition: service_healthy

The db on the other hand has the following healthcheck

db:
  ...
  healthcheck:
    test: TEST_GOES_HERE
    timeout: 20s
    retries: 10

I have tried a couple of approaches like :

  1. making sure the db DIR is created test: ["CMD", "test -f var/lib/mysql/db"]
  2. Getting the mysql version: test: ["CMD", "echo 'SELECT version();'| mysql"]
  3. Ping the admin (marks the db container as healthy but does not seem to be a valid test) test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]

Does anyone have a solution to this?


Solution 1:

version: "2.1"
services:
    api:
        build: .
        container_name: api
        ports:
            - "8080:8080"
        depends_on:
            db:
                condition: service_healthy
    db:
        container_name: db
        image: mysql
        ports:
            - "3306"
        environment:
            MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
            MYSQL_USER: "user"
            MYSQL_PASSWORD: "password"
            MYSQL_DATABASE: "database"
        healthcheck:
            test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
            timeout: 20s
            retries: 10

The api container will not start until the db container is healthy (basically until mysqladmin is up and accepting connections.)

Solution 2:

If you are using docker-compose v3+, condition as an option of depends_on has been removed.

The recommended path is to use rather wait-for-it, dockerize, or wait-for. In your docker-compose.yml file, change your command to be:

command: sh -c 'bin/wait-for db:3306 -- bundle exec rails s'

I personally prefer wait-for since it can run in an Alpine container (sh compatible, no dependance on bash). Drawback is that it depends on netcat, so if you decide to use it, make sure you have netcat installed in the container, or install it in your Dockerfile, for example with:

# for Alpine image:
RUN apk update && apk add netcat-openbsd

# otherwise:
RUN apt-get -q update && apt-get -qy install netcat

Compose published a good doc about Startup Order.

I also forked the wait-for project so it can check for healthy HTTP status (it uses wget). Then you can do something like that:

command: sh -c 'bin/wait-for http://api/ping -- jest test'

PS: A PR is also ready to be merged to add that capacity to wait-for project.

Solution 3:

This should be enough

version: '2.1'
services:
  mysql:
    image: mysql
    ports: ['3306:3306']
    environment:
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypassword
    healthcheck:
      test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD

Solution 4:

Hi for a simple healthcheck using docker-compose v2.1, I used:

/usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\"

Basically it runs a simple mysql command SHOW DATABASES; using as an example the user root with the password rootpasswd in the database.

If the command succeed the db is up and ready so the healthcheck path. You can use interval so it tests at interval.

Removing the other field for visibility, here is what it would look like in your docker-compose.yaml.

version: '2.1'

  services:
    db:
      ... # Other db configuration (image, port, volumes, ...)
      healthcheck:
        test: "/usr/bin/mysql --user=root --password=rootpasswd --execute \"SHOW DATABASES;\""
        interval: 2s
        timeout: 20s
        retries: 10

     app:
       ... # Other app configuration
       depends_on:
         db:
         condition: service_healthy