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/mysql directive tells Docker to map the mariadb_data volume (managed by Docker) to the directory where MariaDB stores its data inside the container. Data written to /var/lib/mysql within the container is now written to mariadb_data on 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.cnf file into the container.
    1. Create a my.cnf file 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
      
    2. 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
      
  • Why it works: MariaDB loads configuration from files in /etc/mysql/my.cnf and /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_size is 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 = 2 provides 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_size accordingly.
    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 \
      --memory="2g" \
      --memory-swap="2g" \
      mariadb:latest
    
    (Note: Ensure innodb_buffer_pool_size in custom.cnf is less than the --memory limit, e.g., 2G on a 2g limit might be too close; adjust custom.cnf to 1.5G or 1.8G)
  • Why it works: The --memory flag 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 tune innodb_buffer_pool_size within the my.cnf to 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 root user 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.
    1. Create a init.sql file:
      CREATE DATABASE IF NOT EXISTS appdb;
      CREATE USER IF NOT EXISTS 'appuser'@'%' IDENTIFIED BY 'apppassword';
      GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'%';
      FLUSH PRIVILEGES;
      
    2. 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
      
  • Why it works: The MariaDB Docker image automatically executes any .sh or .sql files 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 for appuser allows 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.
    docker run -d \
      --name my-mariadb-prod \
      -e MARIADB_ROOT_PASSWORD=mysecretpassword \
      -v mariadb_data:/var/lib/mysql \
      -p 3306:3306 \
      mariadb:latest
    
    Or, better, connect via Docker networks:
    # 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-image
    
    Your application would then connect to my-mariadb-prod (the container name) on port 3306.
  • Why it works: -p 3306:3306 maps 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:
    [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 = 1
    
    And ensure the log directory is writable by the mysql user.
    # 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"
    
    Then restart the container.
  • Why it works: MariaDB writes logs to the specified files. By default, Docker captures stdout and stderr. If you configure MariaDB to log to files within the container, you can then use docker logs my-mariadb-prod for general errors, or docker exec my-mariadb-prod cat /var/log/mysql/error.log to view specific log files. The key is ensuring the directory exists and has the correct permissions for the mysql user inside the container.

After all these steps, the next hurdle you’ll likely face is managing database schema migrations across different environments.

Want structured learning?

Take the full Mariadb course →