Use this pattern for local development, staging, or controlled lab environments. Updated for current best practices with MySQL 8.4 and Docker Compose V2.
Ensure Docker and Docker Compose are installed:
# Check Docker version
docker --version
# Check Docker Compose version
docker compose version
sudo mkdir -p /opt/mysql
cd /opt/mysql
# Set appropriate ownership
sudo chown -R $(whoami):$(whoami) /opt/mysql
compose.yaml with MySQL 8.4services:
mysql:
image: mysql:8.4
container_name: mysql-primary
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}" # Use environment variable
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: "${MYSQL_APP_PASSWORD}" # Use environment variable
volumes:
- ./data:/var/lib/mysql
- ./conf:/etc/mysql/conf.d
- ./logs:/var/log/mysql
networks:
- mysql-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
start_period: 30s
interval: 10s
networks:
mysql-net:
driver: bridge
Create a .env file to store sensitive information:
# .env
MYSQL_ROOT_PASSWORD=your_secure_root_password_here
MYSQL_APP_PASSWORD=your_secure_app_password_here
Create a custom configuration file for performance and security settings:
# Create conf directory
mkdir -p conf
# Create custom MySQL configuration
cat > conf/custom.cnf << EOF
[mysqld]
# Connection settings
max_connections = 200
connect_timeout = 60
wait_timeout = 28800
interactive_timeout = 28800
# Network settings
bind_address = 0.0.0.0
skip_name_resolve = ON
# Character set
character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci
# InnoDB settings
innodb_buffer_pool_size = 512M
innodb_log_file_size = 128M
innodb_flush_log_at_trx_commit = 1
innodb_flush_method = O_DIRECT
# Binary logging (for replication and point-in-time recovery)
server_id = 1
log_bin = mysql-bin
binlog_expire_logs_seconds = 604800
# Slow query log
slow_query_log = ON
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 0.5
# Security
local_infile = OFF
# Performance schema (for monitoring)
performance_schema = ON
EOF
# Start MySQL container in detached mode
docker compose up -d
# Check container status
docker compose ps
# View logs
docker compose logs mysql
# Follow logs
docker compose logs -f mysql
You can initialize your database with custom SQL scripts by placing them in a initdb directory:
# Create initdb directory
mkdir -p initdb
# Create initialization script
cat > initdb/init.sql << EOF
-- Create additional databases
CREATE DATABASE IF NOT EXISTS analytics;
-- Create additional users with specific permissions
CREATE USER 'analytics_user'@'%' IDENTIFIED BY '${MYSQL_APP_PASSWORD}';
GRANT SELECT, INSERT, UPDATE, DELETE ON analytics.* TO 'analytics_user'@'%';
-- Flush privileges
FLUSH PRIVILEGES;
EOF
Then update your compose.yaml to include the initdb volume:
services:
mysql:
image: mysql:8.4
container_name: mysql-primary
restart: unless-stopped
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: "${MYSQL_APP_PASSWORD}"
volumes:
- ./data:/var/lib/mysql
- ./conf:/etc/mysql/conf.d
- ./logs:/var/log/mysql
- ./initdb:/docker-entrypoint-initdb.d # Add this line
networks:
- mysql-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
start_period: 30s
interval: 10s
networks:
mysql-net:
driver: bridge
# Connect to MySQL container
docker exec -it mysql-primary mysql -u root -p
# Execute SQL command directly
docker exec mysql-primary mysql -u root -p -e "SHOW DATABASES;"
# Backup database
docker exec mysql-primary mysqldump -u root -p appdb > backup.sql
# Restore database
docker exec -i mysql-primary mysql -u root -p appdb < backup.sql
# Check container resource usage
docker stats mysql-primary
# Stop and remove containers
docker compose down
# Stop, remove containers and volumes (WARNING: Data loss!)
docker compose down -v
For production environments, consider these additional configurations:
services:
mysql:
image: mysql:8.4
container_name: mysql-primary
restart: unless-stopped
ports:
- "127.0.0.1:3306:3306" # Bind to localhost only
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password # Use Docker secrets
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD_FILE: /run/secrets/mysql_app_password # Use Docker secrets
volumes:
- mysql-data:/var/lib/mysql # Named volume instead of bind mount
- ./conf:/etc/mysql/conf.d
- ./logs:/var/log/mysql
secrets:
- mysql_root_password
- mysql_app_password
networks:
- mysql-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
start_period: 30s
interval: 10s
deploy:
resources:
limits:
memory: 2G
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.25'
volumes:
mysql-data:
secrets:
mysql_root_password:
file: ./secrets/mysql_root_password.txt
mysql_app_password:
file: ./secrets/mysql_app_password.txt
networks:
mysql-net:
driver: bridge
internal: false # Set to true if you don't need external connectivity