Infrastructure guide
MariaDB Docker Compose: Private Networking and a Verified Restore
Run MariaDB with private networking, file-based credentials, application accounts, health checks, and an isolated logical-backup restore drill.
Published and reviewed by OpenAlt · October 7, 2026

Start MariaDB with persistent storage, a separate application account and no published database port. Then prove that you can restore an application backup into an isolated database. A container that starts successfully has passed a startup check, not a data-protection check.
This guide covers a single-server database for a self-hosted application. It does not create replication, automatic failover or a managed database service. If nobody can own database updates and recovery, choose an operating model with that responsibility explicitly assigned before migrating important data.
Table of contents
- What must you decide before creating the database?
- How do you create a private Compose service?
- Why do changed passwords sometimes do nothing?
- How should the application connect?
- How do you make a useful backup?
- How do you prove restoration works?
- How should upgrades and rollback work?
- FAQ
What must you decide before creating the database?
Check the application's supported database versions, expected storage growth and acceptable recovery delay. Do not select a database version just because a search result contains a short Compose file.
The MariaDB Foundation identifies the Docker Official Image as the official container distribution. Use that provenance as the starting point, then choose a supported version that your application accepts. The example below uses the 11.8 series; it is not a claim that every application supports that series.
Write down what must survive: the application database, required accounts, configuration and backups. Decide how much recent work could be lost after a failure and who performs recovery. A personal test service and a team's daily system can use similar YAML while needing very different operating arrangements.
The OpenAlt infrastructure guides place the database alongside the proxy, authentication and monitoring it supports. Keep the whole service in view: a healthy database does not prove that the application can use it correctly.
How do you create a private Compose service?
Give the official image a persistent data volume and unique credentials, supplied through files. Omit ports when only application containers on the same network need access.
In a new project directory, generate two different local secrets:
mkdir -p secrets
chmod 700 secrets
openssl rand -hex 32 > secrets/db-root.txt
openssl rand -hex 32 > secrets/db-app.txt
chmod 600 secrets/db-root.txt secrets/db-app.txt
Keep these files outside version control. Save the following as compose.yaml:
services:
db:
image: mariadb:11.8
environment:
MARIADB_ROOT_PASSWORD_FILE: /run/secrets/db_root
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD_FILE: /run/secrets/db_app
secrets:
- db_root
- db_app
volumes:
- db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
secrets:
db_root:
file: ./secrets/db-root.txt
db_app:
file: ./secrets/db-app.txt
volumes:
db-data:
Docker's Compose secrets guide explains service-scoped file access. Local secret files still need host protection and a recovery plan; this mechanism does not make the underlying disk encrypted.
Run docker compose up -d db, inspect docker compose ps, and read startup errors with docker compose logs --tail=100 db. The MariaDB healthcheck reference documents the connection and InnoDB initialization tests. The timing values above are starting settings, not measured capacity guarantees.
Why do changed passwords sometimes do nothing?
Initialization variables create the initial database state; they do not continually rewrite accounts inside an existing database. Changing a secret file after initialization is not a complete password-rotation procedure.
The official environment-variable reference states that initialization settings generally have no effect once the data directory contains a database. This protects existing data from being recreated at every startup.
For an existing installation, rotate the account through database administration and coordinate the application's stored credential. Keep a working administrative route until the new application login succeeds. Record which secret now corresponds to the live account.
Do not delete the volume to make a new environment value take effect. That changes the problem from credential management to data loss. First establish whether this is an empty trial installation or a database containing work you must retain.
A useful check is to connect as the application user and perform an authorized operation. Being able to connect as root says nothing about whether the account used by the application has the right password and permissions.


How should the application connect?
An application on the same Compose network should connect to db on port 3306. Its own localhost address does not refer to the database container.
Docker's Compose networking guide documents service-name discovery and the difference between internal and published ports. Add the application service to the same project network, or explicitly share an appropriate network between projects. Avoid fixed container IP addresses that can change after recreation.
Use the app account for normal work. Keep administrative credentials out of the application configuration. Check the application's schema creation and upgrade requirements before tightening permissions, then grant only what that workflow needs.
For occasional administration from the host, prefer a controlled local route. Publishing 3306 on every network interface is unnecessary for the deployment shown here. If remote database access is a real requirement, design its authentication, encryption and network restrictions separately rather than adding an open port during troubleshooting.
How do you make a useful backup?
Make a consistent database export, check its completion and copy it away from the host. A persistent volume helps with container replacement but does not provide an independent recovery copy.
The MariaDB container backup guide documents mariadb-dump and physical backup options. For the small application database in this example, a logical export is an understandable starting point:
mkdir -p backups
chmod 700 backups
umask 077
docker compose exec -T db sh -c \
'mariadb-dump -uroot -p"$(cat /run/secrets/db_root)" --single-transaction --routines --events --triggers app' \
> backups/app.sql
Treat the output as sensitive. The command's credential handling is convenient for a local administrative shell; do not copy its execution into shared logs or expose the host to untrusted administrators.
--single-transaction is suitable for transactional tables such as InnoDB; avoid schema changes during the export, and review consistency requirements for other engines. Check the process exit status and backup size. A nonempty file alone is not proof that the entire export succeeded.
Plan retention and off-host space using the backup storage calculator. Preserve required account grants and deployment secrets separately, since exporting one application database is not a complete server inventory.
How do you prove restoration works?
Import the backup into a different Compose project with a fresh volume, then verify the application's behavior against it. Keep production untouched throughout the exercise.
Using the same reviewed definition and credentials for a temporary private drill:
docker compose -p mariadb-restore up -d db
Wait until its health check passes. Import the application export:
docker compose -p mariadb-restore exec -T db sh -c \
'mariadb -uroot -p"$(cat /run/secrets/db_root)" app' \
< backups/app.sql
Compose's project separation gives this definition a different named volume. Confirm that separation before importing. Do not add a fixed external volume name that accidentally reconnects the drill to production.
Check representative records, attachments referenced by the application, permissions and a normal read/write workflow. A database-only backup cannot restore application files that lived elsewhere. Record the recovery duration you actually observe, then stop the test project when finished. The PostgreSQL guide discusses the same recovery discipline for that separate database family.
How should upgrades and rollback work?
Upgrade only after checking compatibility and retaining a matching pre-upgrade backup. Replacing the image with an older version is not a universal way to reverse a changed data directory.
The MariaDB environment reference describes automatic upgrade behavior and a system-database backup. Do not mistake that limited backup for a complete copy of the application's data.
Rehearse the upgrade on restored data first. Test the application's queries and migrations, then schedule the production change with an explicit recovery path. Retain the old image identity and old data backup together until the new version passes normal usage checks.
FAQ
Why did changing an environment password not reset the account?
The data directory was already initialized. Rotate the existing database account and update its consumers together.
Must port 3306 be public?
No. Containers sharing the intended network can communicate without publishing the database port.
Is the data volume enough protection?
No. Keep independent backups and demonstrate that they can be restored.
Can I change the major image version directly?
Only after checking the supported upgrade path and testing it on a restored copy. Preserve a matching rollback backup first.