Andrew Mercer
on this page

Galera Cluster: Docker Deployment

This walks through a 3-node MariaDB Galera cluster on a single Docker host, using a dedicated bridge network with static IPs so wsrep_cluster_address can be set explicitly. See Overview for the concepts referenced below (bootstrapping, quorum, GTID).

1. Create the Network

docker network create --driver=bridge --subnet 172.16.0.0/16 galera-net

2. Create the Volumes

for n in 1 2 3; do docker volume create galera-node$n-data; done
galera-node1-data
galera-node2-data
galera-node3-data

3. Configuration

Create the config directories

mkdir -p ~/containers/galera_cluster/galera-node{1,2,3}-conf

galera-node1 — initial bootstrap config

Node 1 starts with an empty wsrep_cluster_address — this is what tells Galera to found a new cluster rather than join one:

cat <<EOF > galera-node1-conf/60-galera.cnf
[galera]

wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="galera_cluster"
wsrep_cluster_address="gcomm://"
wsrep_sst_method=rsync
wsrep_node_name="galera-node1"
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
EOF

galera-node2 and galera-node3 config

Both point at the full peer list from the start, since they're joining rather than bootstrapping:

cat <<EOF > galera-node2-conf/60-galera.cnf
[galera]

wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="galera_cluster"
wsrep_cluster_address="gcomm://172.16.0.101,172.16.0.102,172.16.0.103"
wsrep_sst_method=rsync
wsrep_node_name="galera-node2"
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
EOF
cat <<EOF > galera-node3-conf/60-galera.cnf
[galera]

wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="galera_cluster"
wsrep_cluster_address="gcomm://172.16.0.101,172.16.0.102,172.16.0.103"
wsrep_sst_method=rsync
wsrep_node_name="galera-node3"
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
EOF

4. Bootstrap the First Node

docker run -it -d --net galera-net --name galera-node1 -h galera-node1 --ip 172.16.0.101 \
  -e MYSQL_ROOT_PASSWORD=rootpass -e GALERA_NEW_CLUSTER=1 -v galera-node1-data:/var/lib/mysql \
  -v ~/containers/databases/galera_cluster/galera-node1-conf/60-galera.cnf:/etc/mysql/mariadb.conf.d/60-galera.cnf:Z \
  docker.io/library/mariadb:11.4

Verify the cluster came up with itself as the only member:

docker exec -it galera-node1 mariadb -h 172.16.0.101 -uroot -prootpass -e "SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size';"
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| wsrep_cluster_size | 1     |
+--------------------+-------+

5. Reconfigure Node 1 Out of Bootstrap Mode

Stop and remove the bootstrap container:

docker stop galera-node1
docker rm galera-node1

Edit galera-node1-conf/60-galera.cnf so it no longer self-bootstraps on every future start — comment out the empty gcomm:// and switch to the full peer list, matching nodes 2 and 3:

[galera]

wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="galera_cluster"
# wsrep_cluster_address="gcomm://"                                    # <- comment this line out
wsrep_cluster_address="gcomm://172.16.0.101,172.16.0.102,172.16.0.103" # <- add all node IPs
wsrep_sst_method=rsync
wsrep_node_name="galera-node1"
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2

This step matters: if node 1 keeps an empty gcomm://, restarting it later will silently found a second, divergent cluster instead of rejoining the existing one.

6. Start All Three Nodes

docker run -it -d --net galera-net --name galera-node1 -h galera-node1 --ip 172.16.0.101 \
  -e MYSQL_ROOT_PASSWORD=rootpass -e GALERA_NEW_CLUSTER=1 -v galera-node1-data:/var/lib/mysql \
  -v ~/containers/databases/galera_cluster/galera-node1-conf/60-galera.cnf:/etc/mysql/mariadb.conf.d/60-galera.cnf:Z \
  docker.io/library/mariadb:11.4
docker run -it -d --net galera-net --name galera-node2 -h galera-node2 --ip 172.16.0.102 \
  -e MYSQL_ROOT_PASSWORD=rootpass -v galera-node2-data:/var/lib/mysql \
  -v ~/containers/databases/galera_cluster/galera-node2-conf/60-galera.cnf:/etc/mysql/mariadb.conf.d/60-galera.cnf:Z \
  docker.io/library/mariadb:11.4
docker run -it -d --net galera-net --name galera-node3 -h galera-node3 --ip 172.16.0.103 \
  -e MYSQL_ROOT_PASSWORD=rootpass -v galera-node3-data:/var/lib/mysql \
  -v ~/containers/databases/galera_cluster/galera-node3-conf/60-galera.cnf:/etc/mysql/mariadb.conf.d/60-galera.cnf:Z \
  docker.io/library/mariadb:11.4

Note node 2 and node 3 don't pass GALERA_NEW_CLUSTER=1 — only the founding node does.

7. Check Cluster Status

docker exec -it galera-node1 mariadb -h 172.16.0.101 -uroot -prootpass -e "SHOW GLOBAL STATUS LIKE 'wsrep_%';"
docker exec -it galera-node1 mariadb -h 172.16.0.101 -uroot -prootpass -e "SHOW GLOBAL STATUS LIKE 'wsrep_cluster_size';"

Reference: Galera cluster monitoring docs. Full variable-by-variable detail lives on the Monitoring page.

Personal status script

A pre-built status checker that surfaces the important cluster variables in one call:

docker pull registry.gitlab.com/myteam/galera_status:latest
docker run -it --rm registry.gitlab.com/andrewmercer/galera_status:latest python galera_status.py -c all
docker run -it --rm registry.gitlab.com/andrewmercer/galera_status:latest python galera_status.py --check [ option ]

Cleanup

for container in galera-node1 galera-node2 galera-node3; do docker stop $container && docker rm $container; done
docker network rm galera-net
for volume in galera-node1-data galera-node2-data galera-node3-data; do docker volume rm $volume; done

Troubleshooting Cross-Reference

Docker-specific issues (e.g. It may not be safe to bootstrap the cluster from this node) are covered in Troubleshooting.

Further Reading