MariaDB in Docker for production is a lot like building a race car: you can’t just plop the engine in the chassis and expect it to win.
Let’s see MariaDB chugging along. Imagine this is a busy API server:
# Start a basic, non-production-ready MariaDB container
docker run -d \
--name my-mariadb-dev \
-e MARIADB_ROOT_PASSWORD=mysecretpassword \
mariadb:latest
# Connect and run a quick query
docker exec -it my-mariadb-dev mariadb -p \
-e "CREATE DATABASE testdb; USE testdb; CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255)); INSERT INTO users (name) VALUES ('Alice'); SELECT * FROM users;"
# Output will show:
# +----+-------+
# | id | name |
# +----+-------+
# | 1 | Alice |
# +----+-------+
This works for a quick test, but production needs more. The core problem with running MariaDB in Docker for production is that the default container settings are optimized for ease of use, not for performance, security, or data durability. It’s a "run-anywhere" setup, not a "run-fast-and-safe-everywhere" setup.
Persistent Data is Non-Negotiable
The most immediate problem with the above is that if the container is removed, all your data is gone. That’s a non-starter.
- Diagnosis: You’ll notice data disappears if the container is stopped and removed (
docker rm my-mariadb-dev). - Cause: Container filesystems are ephemeral by default.
- Fix: Use Docker volumes to persist data outside the container’s lifecycle.
# Create a named volume for data docker volume create mariadb_data # Start the container with the volume mounted docker run -d \ --name my-mariadb-prod \ -e MARIADB_ROOT_PASSWORD=mysecretpassword \ -v mariadb_data:/var/lib/mysql \ mariadb:latest - Why it works: The
-v mariadb_data:/var/lib/mysqldirective tells Docker to map themariadb_datavolume (managed by Docker) to the directory where MariaDB stores its data inside the container. Data written to/var/lib/mysqlwithin the container is now written tomariadb_dataon the host, persisting even if the container is deleted.
Tuning for Performance: The my.cnf File
MariaDB’s performance is heavily influenced by its configuration file, my.cnf (or my.ini). The default settings are conservative and not tuned for heavy loads.
- Diagnosis: Slow queries, high CPU/memory usage under load, poor transaction throughput.
- Cause: Default
innodb_buffer_pool_size,innodb_log_file_size, and other InnoDB settings are too small. - Fix: Mount a custom
my.cnffile into the container.- Create a
my.cnffile on your host machine (e.g., in/etc/mysql/conf.d/custom.cnf).[mysqld] # Example tuning for a container with 4GB RAM innodb_buffer_pool_size = 2G innodb_log_file_size = 512M max_connections = 200 innodb_flush_log_at_trx_commit = 2 innodb_flush_method = O_DIRECT - Mount this file into the container:
docker run -d \ --name my-mariadb-prod \ -e MARIADB_ROOT_PASSWORD=mysecretpassword \ -v mariadb_data:/var/lib/mysql \ -v /etc/mysql/conf.d/custom.cnf:/etc/mysql/conf.d/custom.cnf \ mariadb:latest
- Create a
- Why it works: MariaDB loads configuration from files in
/etc/mysql/my.cnfand/etc/mysql/conf.d/. By mounting your custom file, you override the defaults with settings appropriate for your workload and available resources.innodb_buffer_pool_sizeis the most critical; setting it to 50-70% of available RAM on the host (if the container has dedicated RAM) is a common starting point.innodb_flush_log_at_trx_commit = 2provides a good balance between durability and performance for many web applications, sacrificing absolute ACID compliance for speed.
Memory Management within the Container
Docker containers have resource limits. MariaDB needs to be aware of these limits, especially regarding memory.
- Diagnosis: MariaDB might crash with OOM (Out Of Memory) errors, or the Docker host might become unstable.
- Cause: MariaDB’s memory allocation (especially the InnoDB buffer pool) exceeds the container’s allocated memory.
- Fix: Set memory limits on the Docker container and adjust
innodb_buffer_pool_sizeaccordingly.
(Note: Ensuredocker run -d \ --name my-mariadb-prod \ -e MARIADB_ROOT_PASSWORD=mysecretpassword \ -v mariadb_data:/var/lib/mysql \ -v /etc/mysql/conf.d/custom.cnf:/etc/mysql/conf.d/custom.cnf \ --memory="2g" \ --memory-swap="2g" \ mariadb:latestinnodb_buffer_pool_sizeincustom.cnfis less than the--memorylimit, e.g.,2Gon a2glimit might be too close; adjustcustom.cnfto1.5Gor1.8G) - Why it works: The
--memoryflag limits the RAM the container can use. By setting this, you ensure MariaDB doesn’t consume more memory than allocated, preventing the host or container from being killed by the OOM killer. You then tuneinnodb_buffer_pool_sizewithin themy.cnfto be a significant portion of this limit, but not exceeding it.
User Management and Permissions
Running everything as root is a security risk. Production environments require dedicated users with specific privileges.
- Diagnosis: Applications can’t connect, or they have too many permissions.
- Cause: Default
rootuser is used, or necessary user creation/granting steps are missed. - Fix: Create users and grant privileges using SQL commands, ideally executed via an initialization script.
- Create a
init.sqlfile:CREATE DATABASE IF NOT EXISTS appdb; CREATE USER IF NOT EXISTS 'appuser'@'%' IDENTIFIED BY 'apppassword'; GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'%'; FLUSH PRIVILEGES; - Mount this script to be run on initialization:
docker run -d \ --name my-mariadb-prod \ -e MARIADB_ROOT_PASSWORD=mysecretpassword \ -v mariadb_data:/var/lib/mysql \ -v /path/to/your/init.sql:/docker-entrypoint-initdb.d/init.sql \ mariadb:latest
- Create a
- Why it works: The MariaDB Docker image automatically executes any
.shor.sqlfiles placed in/docker-entrypoint-initdb.d/when the database is first initialized. This ensures your database is set up with the correct schema and users before any application tries to connect. Using%as the host forappuserallows connections from any host, which is common in Docker environments where the application container might be on a different network. For stricter security, you’d replace%with the specific IP address or network of your application container.
Network Exposure and Security
By default, the container’s MariaDB port (3306) is not exposed to the host or external networks. You need to explicitly expose it if your application container is not on the same Docker network.
- Diagnosis: Application containers cannot connect to the database.
- Cause: The database port is not published to the host or accessible on the Docker network.
- Fix: Publish the port.
Or, better, connect via Docker networks:docker run -d \ --name my-mariadb-prod \ -e MARIADB_ROOT_PASSWORD=mysecretpassword \ -v mariadb_data:/var/lib/mysql \ -p 3306:3306 \ mariadb:latest
Your application would then connect to# Create a network docker network create app_network # Run MariaDB on this network docker run -d \ --name my-mariadb-prod \ --network app_network \ -e MARIADB_ROOT_PASSWORD=mysecretpassword \ -v mariadb_data:/var/lib/mysql \ mariadb:latest # Run your app container on the same network docker run -d \ --name my-app \ --network app_network \ your-app-imagemy-mariadb-prod(the container name) on port3306. - Why it works:
-p 3306:3306maps port 3306 on the Docker host to port 3306 inside the container. When using Docker networks, containers on the same network can resolve each other by their container names, making direct IP mapping unnecessary and more robust. This is the preferred method for inter-container communication.
Logging
You need to be able to see what MariaDB is doing, especially when things go wrong.
- Diagnosis: Can’t diagnose issues because there are no logs.
- Cause: Default logging might be off, or logs are not accessible.
- Fix: Ensure logging is enabled and directed to standard output (which Docker captures).
In your
custom.cnf:
And ensure the log directory is writable by the[mysqld] log_error = /var/log/mysql/error.log general_log = 1 general_log_file = /var/log/mysql/general.log slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log log_queries_not_using_indexes = 1mysqluser.
Then restart the container.# Example: Create log directory and set permissions before starting docker exec -it my-mariadb-prod bash -c "mkdir -p /var/log/mysql && chown mysql:mysql /var/log/mysql" - Why it works: MariaDB writes logs to the specified files. By default, Docker captures
stdoutandstderr. If you configure MariaDB to log to files within the container, you can then usedocker logs my-mariadb-prodfor general errors, ordocker exec my-mariadb-prod cat /var/log/mysql/error.logto view specific log files. The key is ensuring the directory exists and has the correct permissions for themysqluser inside the container.
After all these steps, the next hurdle you’ll likely face is managing database schema migrations across different environments.