Docker image for MariaDB + Galera Cluster. Designed for Compose and Swarm, supports arbitrary node startup order, and exposes an HTTP health check for HAProxy.
- Docker Hub:
galeriadb/12.1 - Tag:
latest(image name encodes MariaDB version, e.g.galeriadb/12.1:latest)
Branch main tracks MariaDB 12.1. Older versions are available in versioned branches (e.g. 11.8 for MariaDB 11.8).
- Cluster discovery from
GALERIA_PEERSwith bootstrap candidate logic. - HAProxy-friendly health check on port 9200 (Synced + wsrep_ready=ON).
- Hot backups to S3 with optional retention and cron scheduling.
- Restore (clone) from S3 when the data directory is empty.
From the example Compose stack:
cd examples/docker-compose
docker compose up -dConnect via HAProxy on port 3306:
mariadb -h 127.0.0.1 -P 3306 -u root -psecretThe container exits immediately if any required variable is missing or empty.
| Variable | Description | Example |
|---|---|---|
GALERIA_PEERS |
Comma-separated peer list. Use Compose service names, Swarm task names (tasks.galera), or IPs/hostnames. |
galera1,galera2,galera3 / tasks.galera / 10.0.0.1,10.0.0.2 |
GALERIA_ROOT_PASSWORD |
Password for MariaDB root user (no default). |
secret |
GALERIA_BOOTSTRAP_CANDIDATE |
Hostname of the node that may bootstrap a new cluster when none is found. Must match one of your node hostnames. | galera1 |
| Variable | Description | Default |
|---|---|---|
GALERIA_CLUSTER_NAME |
Galera cluster name (wsrep_cluster_name). |
galera_cluster |
GALERIA_DISCOVERY_TIMEOUT |
Total seconds to resolve peers before continuing. | 5 |
GALERIA_DISCOVERY_INTERVAL |
Seconds between resolution attempts. | 1 |
GALERIA_JOIN_PRIMARY_TIMEOUT |
For non-candidates without an initial Synced peer: seconds to pre-check for a reachable Synced peer before exit (for orchestrator restart). | 30 |
GALERIA_READY_TIMEOUT |
Seconds to wait for MariaDB to accept connections before the container gives up. Time spent in a state transfer (SST) does not count against it. | 60 |
GALERIA_SST_TIMEOUT |
Maximum seconds to wait while a state transfer (SST) is running. 0 waits without a limit. |
3600 |
GALERIA_MARIADB_LOGS |
MariaDB/Galera server logs to container stdout/stderr. Set to on to enable; any other value keeps only entrypoint/script logs in docker logs. |
off |
GALERIA_NODE_ADDRESS |
Override this node's IP address if auto-detection is wrong. | auto-detected |
Discovery behavior:
- The image resolves
GALERIA_PEERSand looks for a Synced node. - If a Synced node is found, this node joins it.
- If none is found within the discovery window, only the bootstrap candidate starts a new cluster.
- Non-candidates run a pre-check for up to
GALERIA_JOIN_PRIMARY_TIMEOUT; if no Synced peer appears, they exit so the orchestrator can restart later. - In Swarm or Kubernetes, peer DNS may be empty during the first task start. The discovery window avoids long waits while still allowing late joiners.
State transfer (SST):
- A joining node refuses client connections for as long as its state transfer takes, so the
GALERIA_READY_TIMEOUTcountdown is paused while a transfer is running and restarts once it finishes. A joiner is never killed mid-SST because its dataset is large or the link is slow (which would also abort the transfer on the donor). - The transfer itself is bounded by
GALERIA_SST_TIMEOUT. Raise it (or set0) for datasets that need more than an hour to transfer.
The image starts an HTTP listener on port 9200 with two endpoints:
| Endpoint | Meaning | Use for |
|---|---|---|
GET / |
Readiness: 200 only when the node is Synced and wsrep_ready=ON. |
Load balancers (HAProxy http-check send meth GET uri /). |
GET /liveness |
Liveness: 200 while the node is Synced or taking part in a state transfer (receiving it as a joiner, serving it as a donor, applying the backlog afterwards). | Container health checks, orchestrator probes. |
The image HEALTHCHECK uses /liveness, because a node that is merely "not ready" must not be restarted: a joiner restarted mid-SST loses the transfer and also breaks it on the donor. Do not override the health check with a plain server ping (mariadb -e "SHOW STATUS ...") — a joiner has no server to answer it while its state transfer runs, so Swarm would restart the task in the middle of the transfer.
In Kubernetes, map the endpoints directly: /liveness for livenessProbe, / for readinessProbe.
The check keeps the default start period of 60s, which covers cluster connect and data directory initialization before a transfer begins. A node that restarts with a very large gcache backlog to replay (IST, with no transfer helper running and the server not yet accepting connections) can need longer; raise the health check start_period for that deployment if so.
Optional:
| Variable | Description | Default |
|---|---|---|
GALERIA_HEALTHCHECK_USER |
MySQL user for health check queries. | root |
GALERIA_HEALTHCHECK_PASSWORD |
Password for health check user. | GALERIA_ROOT_PASSWORD |
If both GALERIA_HEALTHCHECK_USER and GALERIA_HEALTHCHECK_PASSWORD are set and the user is not root, the entrypoint will create/update that user and grant the minimal permissions required for the check.
Hot backups run only on nodes in Synced state.
| Variable | Description | Default |
|---|---|---|
GALERIA_BACKUP_SCHEDULE |
Cron schedule for backups. Empty disables cron. | empty |
GALERIA_BACKUP_S3_URI |
Full S3 URI for backups (overrides bucket+path). | — |
GALERIA_BACKUP_S3_BUCKET |
S3 bucket name. | — |
GALERIA_BACKUP_S3_PATH |
Path under bucket if using GALERIA_BACKUP_S3_BUCKET. |
mariadb |
GALERIA_BACKUP_TMPDIR |
Temporary directory for backups. | /tmp |
GALERIA_BACKUP_RETENTION_DAYS |
Delete backups older than this many days (by S3 LastModified). | — |
GALERIA_BACKUP_RETENTION_CUTOFF_OVERRIDE |
Override retention cutoff date (YYYY-MM-DD). Useful for tests. | — |
GALERIA_CRONTAB |
Additional cron lines to install alongside backups. | — |
AWS_ACCESS_KEY_ID |
AWS access key (optional if using IAM role). | — |
AWS_SECRET_ACCESS_KEY |
AWS secret key. | — |
AWS_REGION / AWS_DEFAULT_REGION |
AWS region for S3. | — |
AWS_ENDPOINT_URL |
Custom S3 endpoint (e.g. MinIO). | — |
Manual backup:
docker exec <container> /usr/local/bin/galera-backup.shBackup logs are written to /var/log/galera-backup.log inside the container.
Restore runs only when /var/lib/mysql is empty on container startup and clone variables are set.
| Variable | Description | Default |
|---|---|---|
GALERIA_CLONE_BACKUP_S3_URI |
Full S3 URI for restore (overrides bucket+path). | — |
GALERIA_CLONE_BACKUP_S3_BUCKET |
S3 bucket name. | — |
GALERIA_CLONE_BACKUP_S3_PATH |
Path under bucket if using GALERIA_CLONE_BACKUP_S3_BUCKET. |
mariadb |
GALERIA_CLONE_FROM |
Object key relative to base path or full s3:// URI. If unset, the latest backup under {hostname}/ is used. |
— |
GALERIA_CLONE_TMPDIR |
Temporary directory for restore files. | /tmp |
CLONE_AWS_ACCESS_KEY_ID |
AWS access key for restore. | — |
CLONE_AWS_SECRET_ACCESS_KEY |
AWS secret key for restore. | — |
CLONE_AWS_REGION / CLONE_AWS_DEFAULT_REGION |
AWS region for restore. | — |
CLONE_AWS_ENDPOINT_URL |
Custom S3 endpoint (e.g. MinIO). | — |
root@%is created on startup and granted full privileges.- If the node is the bootstrap candidate,
safe_to_bootstrapis set to 1 ingrastate.datwhen needed. - If the data directory was created by an older MariaDB version, the container runs
mariadb-upgradeon startup.
When a version upgrade is detected, the entrypoint runs mariadb-upgrade automatically. If S3 backup settings are configured, a hot backup is taken and uploaded before the migration runs. If backup is configured but the pre-upgrade backup fails or the node does not reach Synced within the timeout, the container exits and migration is not performed.
Optional:
| Variable | Description | Default |
|---|---|---|
GALERIA_AUTO_MIGRATE_SYNC_TIMEOUT |
Seconds to wait for Synced + wsrep_ready=ON before taking the pre-upgrade backup. |
120 |
- 3306 - MariaDB clients
- 4567 - Galera replication
- 4568 - IST
- 4444 - SST
- 9200 - HTTP health check
docker build -t galeriadb/12.1:latest -f docker/Dockerfile docker/Single entry point:
make ciRun inside the dev container (no local toolchain required):
make ci-dockermake ci-docker runs make ci from sources baked into the dev image at /workspace and uses Docker socket passthrough (/var/run/docker.sock or path from DOCKER_HOST=unix://...).
Diagnostics from /workspace/artifacts are copied back to local ./artifacts/ after the run.
Targets:
| Command | Description |
|---|---|
make ci |
lint, build, CST, security, smoke, deploy, backup-s3 |
make ci-docker |
same as make ci inside dev container |
make lint |
hadolint, shellcheck, shfmt |
make build |
build image (galeriadb/12.1:local) |
make cst |
Container Structure Tests |
make security |
Trivy rootfs scan (CRITICAL) + Dockle image scan |
make smoke |
required env validation, single-node startup, graceful shutdown, Docker HEALTHCHECK |
make deploy |
3-node Galera + HAProxy scenarios |
make backup-s3 |
S3 backup tests (MinIO) |
make swarm |
Swarm sanity (run after make ci) |
Tests live in tests/00.* through tests/05.*. Each test directory has its own entrypoint.sh. Diagnostics are written to ./artifacts/ on failure.
GitHub Actions runs make ci in a dev container on pull requests to the main branch and on manual runs. The Swarm job runs on the same triggers after CI passes (unless [skip-ci] is set).
Images are built and published via .github/workflows/docker-publish.yml on push to main and on manual runs (skips when [skip-ci] is in the head commit message). Required secrets: DOCKER_USERNAME, DOCKER_PASSWORD.
docker/- Dockerfile, entrypoint, entrypoint stages, backup/clone scripts, templates.tests/- CI and local test suite.examples/docker-compose/- local Compose stack (3 nodes + HAProxy).examples/docker-swarm/- Swarm stack and docs.
This repository is licensed under the MIT License. The image includes third-party software (MariaDB, Galera, socat, etc.) under their own licenses; see NOTICE for details.