February 1

MariaDB Docker setup β€” Running MariaDB in Docker containers complete guide

Running MariaDB in Docker simplifies deployment, makes environments reproducible, and allows you to spin up databases in seconds. Whether you need a quick dev environment or a production-ready setup, Docker handles the complexity of installation and configuration. This guide covers everything from basic container setup to production configurations with persistence, custom settings and proper backup strategies.

Why run MariaDB in Docker

Traditional MariaDB installation means dealing with package managers, version conflicts, and cleanup when things go wrong. Docker containers provide isolation and consistency that bare-metal installations can't match.

Benefits of containerized databases

Docker containers package MariaDB with all its dependencies. You get the same behavior on your laptop, CI server, and production environment. No more "works on my machine" problems.

Containers start in seconds. Spinning up a fresh MariaDB instance takes about 5-10 seconds compared to minutes for traditional installation. This matters when running integration tests or switching between projects.

Cleanup is trivial. Delete the container and it's gone. No leftover configuration files, no orphaned data directories, no package conflicts with other software.

When Docker makes sense

Docker works well for development environments where you need quick setup and teardown. It's also suitable for microservices architectures where each service gets its own database. Testing and CI pipelines benefit from reproducible database instances.

For production, Docker adds complexity but provides consistency across environments. You trade some raw performance for operational benefits. The overhead is typically 1-3% for database workloads, which is acceptable for most applications.

Quick start with Docker run

The fastest way to get MariaDB running is a single Docker command. This works for testing and development.

Basic container setup

Start MariaDB with minimal configuration:

docker run -d \
  --name mariadb \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mariadb:11

This starts MariaDB 11 in the background with root password set. The container runs until you stop it.

Check if it's running:

docker ps

Connect to the database:

docker exec -it mariadb mariadb -u root -p

Environment variables for initial setup

MariaDB's Docker image supports several environment variables for first-run configuration:

  • MYSQL_ROOT_PASSWORD β€” Root user password (required, or use one of the alternatives below)
  • MYSQL_ALLOW_EMPTY_PASSWORD β€” Allow empty root password (optional)
  • MYSQL_RANDOM_ROOT_PASSWORD β€” Generate random root password (optional)
  • MYSQL_DATABASE β€” Create database on startup (optional)
  • MYSQL_USER β€” Create non-root user (optional)
  • MYSQL_PASSWORD β€” Password for non-root user (optional)

Create a database and user on startup:

docker run -d \
  --name mariadb \
  -e MYSQL_ROOT_PASSWORD=rootpassword \
  -e MYSQL_DATABASE=myapp \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppassword \
  mariadb:11

Exposing ports

By default, MariaDB runs on port 3306 inside the container. Map it to your host:

docker run -d \
  --name mariadb \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mariadb:11

Now you can connect from your host machine:

mariadb -h 127.0.0.1 -u root -p

Use a different host port if 3306 is already in use:

docker run -d \
  --name mariadb \
  -p 3307:3306 \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mariadb:11

Data persistence with volumes

Without volumes, your data disappears when the container stops. That's fine for throwaway test databases, but production needs persistence.

Named volumes

Docker named volumes are the simplest approach:

docker run -d \
  --name mariadb \
  -v mariadb-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mariadb:11

The volume mariadb-data persists even after container deletion. List your volumes:

docker volume ls

Inspect volume details:

docker volume inspect mariadb-data

Bind mounts

Bind mounts map a host directory into the container. Useful when you need direct access to data files:

docker run -d \
  --name mariadb \
  -v /path/to/data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mariadb:11

Make sure the directory exists and has proper permissions. On Linux, the MySQL user inside the container needs write access:

mkdir -p /path/to/data
chown -R 999:999 /path/to/data

The UID 999 is the MySQL user inside the MariaDB container.

Volume backup

Back up a named volume by running a temporary container:

docker run --rm \
  -v mariadb-data:/source:ro \
  -v $(pwd):/backup \
  alpine tar czf /backup/mariadb-backup.tar.gz -C /source .

This creates a compressed archive of the entire data directory. For proper database backups, use mysqldump or mariabackup instead, which we'll cover later.

Docker Compose for MariaDB

Docker Compose makes multi-container setups manageable and configurations version-controlled.

Basic compose file

Create a docker-compose.yml:

services:
  mariadb:
    image: mariadb:11
    container_name: mariadb
    environment:
      MYSQL_ROOT_PASSWORD: my-secret-pw
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppassword
    ports:
      - "3306:3306"
    volumes:
      - mariadb-data:/var/lib/mysql
    restart: unless-stopped

volumes:
  mariadb-data:

Start the service:

docker compose up -d

Stop and remove:

docker compose down

Remove including volumes:

docker compose down -v

Application with MariaDB

A typical setup includes your application and MariaDB together:

services:
  app:
    build: .
    environment:
      DATABASE_URL: mysql://appuser:apppassword@mariadb:3306/myapp
    depends_on:
      mariadb:
        condition: service_healthy
    ports:
      - "8080:8080"

  mariadb:
    image: mariadb:11
    container_name: mariadb
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppassword
    volumes:
      - mariadb-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  mariadb-data:

The depends_on with condition: service_healthy ensures your app waits for MariaDB to be ready before starting.

Custom configuration

Default settings work for development but production often needs tuning.

Configuration file mount

Create a custom configuration file my.cnf:

[mysqld]
# InnoDB settings
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# Connection settings
max_connections = 200
wait_timeout = 600

# Query cache (disabled in MariaDB 10.1.7+)
query_cache_type = 0

# Logging
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2

# Character set
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

Mount it into the container:

docker run -d \
  --name mariadb \
  -v ./my.cnf:/etc/mysql/conf.d/custom.cnf:ro \
  -v mariadb-data:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=my-secret-pw \
  mariadb:11

Files in /etc/mysql/conf.d/ are read automatically.

Docker Compose with custom config

services:
  mariadb:
    image: mariadb:11
    container_name: mariadb
    environment:
      MYSQL_ROOT_PASSWORD: my-secret-pw
    volumes:
      - mariadb-data:/var/lib/mysql
      - ./my.cnf:/etc/mysql/conf.d/custom.cnf:ro
    ports:
      - "3306:3306"
    restart: unless-stopped

volumes:
  mariadb-data:

Common configuration options

Key settings to consider for production:

  • innodb_buffer_pool_size β€” Default: 128M, Production: 50-70% of available RAM
  • innodb_log_file_size β€” Default: 48M, Production: 256M-1G depending on write load
  • max_connections β€” Default: 151, Production: based on expected concurrent connections
  • innodb_flush_log_at_trx_commit β€” Default: 1, Production: 1 for durability, 2 for performance

Verify your configuration is applied:

docker exec mariadb mariadb -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

Initialization scripts

The MariaDB Docker image can run SQL scripts on first startup. This is useful for schema creation and seed data.

SQL initialization

Place .sql, .sql.gz, or .sql.xz files in /docker-entrypoint-initdb.d/:

Create init/01-schema.sql:

CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS posts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(255) NOT NULL,
    content TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Create init/02-seed.sql:

INSERT INTO users (email) VALUES ('admin@example.com');
INSERT INTO users (email) VALUES ('user@example.com');

Mount the init directory:

services:
  mariadb:
    image: mariadb:11
    environment:
      MYSQL_ROOT_PASSWORD: my-secret-pw
      MYSQL_DATABASE: myapp
    volumes:
      - mariadb-data:/var/lib/mysql
      - ./init:/docker-entrypoint-initdb.d:ro
    restart: unless-stopped

volumes:
  mariadb-data:

Scripts run in alphabetical order, only on first container start (when data directory is empty).

Shell script initialization

For more complex setup, use shell scripts:

Create init/00-setup.sh:

#!/bin/bash
set -e

mariadb -u root -p"$MYSQL_ROOT_PASSWORD" <<-EOSQL
    CREATE USER IF NOT EXISTS 'readonly'@'%' IDENTIFIED BY 'readonlypassword';
    GRANT SELECT ON myapp.* TO 'readonly'@'%';
    FLUSH PRIVILEGES;
EOSQL

Make it executable:

chmod +x init/00-setup.sh

Networking

Docker networking controls how containers communicate with each other and the outside world.

Default bridge network

Containers on the default bridge network can communicate via IP address but not hostname. For development this usually works fine:

docker run -d --name mariadb -e MYSQL_ROOT_PASSWORD=pw mariadb:11
docker run -it --rm mariadb:11 mariadb -h $(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mariadb) -u root -p

Custom networks

Custom networks allow hostname-based communication:

docker network create myapp-network

docker run -d \
  --name mariadb \
  --network myapp-network \
  -e MYSQL_ROOT_PASSWORD=pw \
  mariadb:11

docker run -it --rm \
  --network myapp-network \
  mariadb:11 \
  mariadb -h mariadb -u root -p

The second container can reach MariaDB using hostname mariadb.

Compose networking

Docker Compose creates a network automatically. Services communicate by service name:

services:
  app:
    image: myapp
    environment:
      DB_HOST: mariadb # Use service name as hostname
      DB_PORT: 3306

  mariadb:
    image: mariadb:11
    environment:
      MYSQL_ROOT_PASSWORD: pw

Health checks and monitoring

Proper health checks ensure containers are actually ready to serve traffic, not just running.

Built-in health check

MariaDB's Docker image includes a health check script:

services:
  mariadb:
    image: mariadb:11
    environment:
      MYSQL_ROOT_PASSWORD: my-secret-pw
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

Check health status:

docker inspect --format='{{.State.Health.Status}}' mariadb

Custom health check

For specific requirements, write custom checks:

healthcheck:
  test:
    [
      "CMD",
      "mariadb",
      "-u",
      "root",
      "-p$MYSQL_ROOT_PASSWORD",
      "-e",
      "SELECT 1",
    ]
  interval: 10s
  timeout: 5s
  retries: 5

Monitoring with logs

View container logs:

docker logs mariadb

Follow logs in real-time:

docker logs -f mariadb

Limit output to recent entries:

docker logs --tail 100 mariadb

Enable slow query logging in your configuration to catch performance issues:

[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1

Mount a volume for logs:

volumes:
  - mariadb-data:/var/lib/mysql
  - mariadb-logs:/var/log/mysql

Backup strategies for Docker MariaDB

Data in containers needs the same backup discipline as traditional installations. Docker adds some nuances but the fundamentals remain.

Using mysqldump in Docker

Run mysqldump inside the container:

docker exec mariadb mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" myapp > backup.sql

For all databases:

docker exec mariadb mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases > all_databases.sql

Compressed backup:

docker exec mariadb mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" myapp | gzip > backup.sql.gz

Scheduled backups with cron

Create a backup script:

#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"
CONTAINER="mariadb"

docker exec $CONTAINER mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases | \
  gzip > "$BACKUP_DIR/mariadb_${TIMESTAMP}.sql.gz"

# Keep only last 7 days
find $BACKUP_DIR -name "mariadb_*.sql.gz" -mtime +7 -delete

Add to crontab for daily 3 AM backups:

0 3 * * * /usr/local/bin/mariadb-backup.sh

Using Databasus for automated backups

Manual backup scripts work but require maintenance and lack monitoring. Databasus (an industry standard for MariaDB backup) provides automated backups with a web interface, scheduling, and notifications.

Install Databasus on a separate server using Docker:

docker run -d \
  --name databasus \
  -p 4005:4005 \
  -v ./databasus-data:/databasus-data \
  --restart unless-stopped \
  databasus/databasus:latest

Or with Docker Compose:

services:
  databasus:
    image: databasus/databasus:latest
    container_name: databasus
    ports:
      - "4005:4005"
    volumes:
      - databasus-data:/databasus-data
    restart: unless-stopped

volumes:
  databasus-data:

Access the web interface at http://localhost:4005, then:

  1. Add your database β€” Click "New Database", select MariaDB, enter your MariaDB server's connection details (host, port, credentials)
  2. Select storage β€” Choose local storage, S3, Google Cloud Storage, or other supported destinations
  3. Select schedule β€” Set backup frequency: hourly, daily, weekly, or custom cron expression
  4. Click "Create backup" β€” Databasus handles backup execution, compression, retention and notifications

Databasus supports multiple notification channels including Slack, Discord, Telegram and email, so you know immediately when backups succeed or fail.

Security considerations

Running databases in containers doesn't change security requirements. If anything, you need to be more careful about configuration.

Secure root password

Never use default or weak passwords. Use environment variables from secrets management:

services:
  mariadb:
    image: mariadb:11
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mariadb_root_password
    secrets:
      - mariadb_root_password

secrets:
  mariadb_root_password:
    file: ./secrets/mariadb_root_password.txt

For Docker Swarm, use proper Docker secrets:

echo "my-secret-pw" | docker secret create mariadb_root_password -

Network isolation

Don't expose database ports to the public internet. Use internal Docker networks:

services:
  app:
    networks:
      - frontend
      - backend

  mariadb:
    networks:
      - backend # Only accessible from backend network

networks:
  frontend:
  backend:
    internal: true # No external access

Read-only root filesystem

For extra security, run with read-only root filesystem:

services:
  mariadb:
    image: mariadb:11
    read_only: true
    tmpfs:
      - /tmp
      - /run/mysqld
    volumes:
      - mariadb-data:/var/lib/mysql

Resource limits

Prevent runaway containers from consuming all resources:

services:
  mariadb:
    image: mariadb:11
    deploy:
      resources:
        limits:
          cpus: "2"
          memory: 4G
        reservations:
          cpus: "1"
          memory: 2G

Production checklist

Before running MariaDB Docker containers in production, verify these items:

  • Data persistence configured with volumes
  • Custom configuration tuned for workload
  • Health checks enabled
  • Automated backup strategy in place
  • Secrets managed securely (not hardcoded)
  • Network properly isolated
  • Resource limits set
  • Monitoring and alerting configured
  • Restart policy set to unless-stopped or always
  • Container image version pinned (not using latest)

Troubleshooting common issues

Container exits immediately

Check logs for errors:

docker logs mariadb

Common causes: missing MYSQL_ROOT_PASSWORD, corrupt data directory, or permission issues on mounted volumes.

Permission denied on bind mount

Ensure the host directory has correct ownership:

sudo chown -R 999:999 /path/to/data

Or run MariaDB with your user ID:

docker run -d --user $(id -u):$(id -g) ...

Can't connect from host

Verify port mapping:

docker port mariadb

Check if MariaDB is listening on all interfaces. By default it should, but verify with:

docker exec mariadb mariadb -u root -p -e "SHOW VARIABLES LIKE 'bind_address';"

Slow performance

Check if InnoDB buffer pool is sized correctly:

docker exec mariadb mariadb -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"

For Docker Desktop on macOS/Windows, file system performance through volumes can be slow. Use named volumes instead of bind mounts for better performance.

Conclusion

Running MariaDB in Docker provides consistent environments across development, testing and production. Start with simple docker run commands for quick setups, then move to Docker Compose for more complex configurations. Always configure data persistence with volumes, set up proper health checks, and implement automated backups. The overhead of containerization is minimal compared to the operational benefits of reproducibility and isolation. Pin your image versions, tune your configuration for your workload, and treat container security with the same rigor as traditional deployments.