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.)
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.
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
If you can change the container to wait for mysql to be ready do it.
If you don't have the control of the container that you want to connect the database to, you can try to wait for the specific port.
For that purpose, I'm using a small script to wait for a specific port exposed by another container.
In this example, myserver will wait for port 3306 of mydb container to be reachable.
# Your database
mydb:
image: mysql
ports:
- "3306:3306"
volumes:
- yourDataDir:/var/lib/mysql
# Your server
myserver:
image: myserver
ports:
- "....:...."
entrypoint: ./wait-for-it.sh mydb:3306 -- ./yourEntryPoint.sh
You can find the script wait-for-it documentation here
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
Adding an updated solution for the healthcheck approach. Simple snippet:
healthcheck:
test: out=$$(mysqladmin ping -h localhost -P 3306 -u foo --password=bar 2>&1); echo $$out | grep 'mysqld is alive' || { echo $$out; exit 1; }
Explanation:
Since mysqladmin ping
returns false positives (especially for wrong password), I'm saving the output to a temporary variable, then using grep
to find the expected output (mysqld is alive
). If found it will return the 0 error code. In case it's not found, I'm printing the whole message, and returning the 1 error code.
Extended snippet:
version: "3.8"
services:
db:
image: linuxserver/mariadb
environment:
- FILE__MYSQL_ROOT_PASSWORD=/run/secrets/mysql_root_password
- FILE__MYSQL_PASSWORD=/run/secrets/mysql_password
secrets:
- mysql_root_password
- mysql_password
healthcheck:
test: out=$$(mysqladmin ping -h localhost -P 3306 -u root --password=$$(cat $${FILE__MYSQL_ROOT_PASSWORD}) 2>&1); echo $$out | grep 'mysqld is alive' || { echo $$out; exit 1; }
secrets:
mysql_root_password:
file: ${SECRETSDIR}/mysql_root_password
mysql_password:
file: ${SECRETSDIR}/mysql_password
Explanation: I'm using docker secrets instead of env variables (but this can be achieved with regular env vars as well). The use of $$
is for literal $
sign which is stripped when passed to the container.
Output from docker inspect --format "{{json .State.Health }}" db | jq
on various occasions:
Everything alright:
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
{
"Start": "2020-07-20T01:03:02.326287492+03:00",
"End": "2020-07-20T01:03:02.915911035+03:00",
"ExitCode": 0,
"Output": "mysqld is alive\n"
}
]
}
DB is not up (yet):
{
"Status": "starting",
"FailingStreak": 1,
"Log": [
{
"Start": "2020-07-20T01:02:58.816483336+03:00",
"End": "2020-07-20T01:02:59.401765146+03:00",
"ExitCode": 1,
"Output": "\u0007mysqladmin: connect to server at 'localhost' failed error: 'Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2 \"No such file or directory\")' Check that mysqld is running and that the socket: '/var/run/mysqld/mysqld.sock' exists!\n"
}
]
}
Wrong password:
{
"Status": "unhealthy",
"FailingStreak": 13,
"Log": [
{
"Start": "2020-07-20T00:56:34.303714097+03:00",
"End": "2020-07-20T00:56:34.845972979+03:00",
"ExitCode": 1,
"Output": "\u0007mysqladmin: connect to server at 'localhost' failed error: 'Access denied for user 'root'@'localhost' (using password: YES)'\n"
}
]
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With