env-municipio-docker-vm

MariaDB and Galera

Goal

Give every data VM its own MariaDB server and local application connection while optionally keeping two servers synchronized — without installing a database server on the host.

Containerized solution

MariaDB runs from MARIADB_IMAGE, pinned to an exact digest like every other service. No mariadb-server, mariadb-backup or mariadb-client package is installed on the VM.

Three host paths are bind-mounted into the container:

Host path Container path Purpose
DB_DATA_ROOT /var/lib/mysql The data directory. Always local disk.
DB_SOCKET_DIR /run/mysqld The Unix socket, shared with the application.
CONFIG_ROOT/mariadb /etc/mysql/conf.d (read-only) The generated 60-municipio.cnf.

The container shares the host network namespace. MariaDB binds client traffic to 127.0.0.1, while Galera uses the VM’s real address for replication. The database container is not network-isolated from its own VM, but its client port is not reachable from other VMs.

DB_SOCKET_DIR is not under /run. That is a tmpfs, so the directory would be missing after a reboot and the container would start without a place to create its socket.

How the application connects

The application uses DB_HOST=localhost:/run/mysqld/mysqld.sock. It mounts DB_SOCKET_DIR read-only at the same /run/mysqld path, so both containers see the socket at that address.

Because every application connection arrives over the socket, it authenticates as 'user'@'localhost'. The application account is never granted to a network host, and MariaDB’s client port is not published from the container.

Administrative access

DB_ROOT_PASSWORD is generated by the installer and stored in the root-only configuration file. Maintenance scripts reach the server with db_root, which runs the client inside the database container and passes the password through the exec environment rather than the command line, so it never appears in a process list.

The image is started with MARIADB_ROOT_HOST=localhost. Without it the image would also create a root@'%' account.

Standalone

The installer starts the container, waits for it to accept connections, verifies that the socket exists and that DB_SOCKET_UID/DB_SOCKET_GID match the mysql user inside the image, then creates the database and application user.

The ownership check is not ceremony: the socket directory is created on the host before the container exists, so its ownership is an assumption until the image can be asked. A mismatch would otherwise surface much later as a site that cannot reach its database.

Cluster

Both data VMs run the same image with wsrep_on=ON. The official image ships the Galera provider at /usr/lib/galera/libgalera_smm.so, rsync and mariabackup; state transfer uses wsrep_sst_method=rsync.

Digest-pinning gives both data VMs byte-identical database builds regardless of their OS release. Host OS releases still matter for GlusterFS, so keep the cluster hosts on the same release.

The image’s entrypoint disables the wsrep provider while it initializes a fresh data directory and loads Galera on the real start, so a joiner initializes cleanly before its state transfer.

Bootstrap flag

A container started with --wsrep-new-cluster keeps that argument across restarts, so an unattended reboot would form a second primary component — split brain.

cluster.municipio.sh bootstrap and failover.municipio.sh promote therefore start MariaDB through compose.galera-bootstrap.yaml and record CONFIG_ROOT/galera-bootstrap-active. While that marker exists, status.municipio.sh reports it on every run. cluster.municipio.sh clear-bootstrap-flag recreates the container without the argument once the peer has joined; it refuses while wsrep_cluster_size is below 2 and verifies that the node returns to the Primary component afterwards.

Treat a node with the marker set as not yet fully recovered.

Shared credentials

A state transfer overwrites the joiner’s privilege tables with the donor’s copy. Both data VMs must therefore be installed with the same DB_PASSWORD and the same DB_ROOT_PASSWORD; for this reason the wizard derives both from one shared cluster password instead of generating random values. cluster.municipio.sh join verifies root access after the transfer completes and fails loudly on a mismatch instead of leaving it to be discovered at the next maintenance command.

The arbitrator is the documented exception

garbd is not part of the MariaDB image, and an arbitrator is a quorum-only witness host that stores no data and runs no container. It keeps the galera-arbitrator-4 package and /etc/default/garb, and the installer still gives that host no Docker at all. Adding a container runtime there to run one small daemon would enlarge the host it is supposed to keep minimal.

Forbidden operations