How to run MySQL 8.4 or VillageSQL in Docker — official image setup, persistent named volumes, Docker Compose multi-service configuration, networking between containers, and when Docker is the right deployment choice over native install or managed cloud.
Use this file to discover all available pages before exploring further.
VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
The official MySQL Docker image lets you run a fully configured MySQL server in a container. It handles initialization, user creation, and data directory setup automatically through environment variables.
Need custom types, VDF functions, or VEF extensions
Volume type
Persistence
Performance
Portability
Named volume (-v mysql-data:/var/lib/mysql)
Survives docker rm
Managed by Docker; good on all platforms
Easy to back up with docker volume commands
Bind mount (-v /host/path:/var/lib/mysql)
Survives container removal
Slower on macOS/Windows (filesystem translation)
Direct host access; easier to inspect files
No volume (container layer)
Lost on docker rm
Fastest writes (ephemeral)
Dev/test only — never for data you want to keep
For most workloads, named volumes are the right default. Bind mounts are useful when you need to inspect or edit the data directory directly from the host, but the performance cost matters on macOS and Windows.
The healthcheck with depends_on: condition: service_healthy ensures the application container doesn’t start until MySQL is ready to accept connections — not just started.
SQL files are executed as root. Shell scripts run in the container environment. These scripts only run on the very first initialization — not on subsequent container starts.
Docker works well for MySQL in production with a few caveats:
Data persistence: Use named volumes or bind mounts. Never let MySQL data live in the container layer.
Memory limits: Set --memory in Docker to prevent MySQL from exhausting host memory, and tune innodb_buffer_pool_size to match.
Backups: mysqldump works inside the container: docker exec mysql-dev mysqldump -u root -p myapp > backup.sql
Logging: MySQL logs go to stdout/stderr in Docker. Use docker logs mysql-dev or configure your logging driver.
Upgrades: Pull the new image tag, stop the old container, and start the new one with the same volume. MySQL’s upgrade process runs automatically on first start with a newer version.
Why can’t I connect with mysql -h localhost from the host?
On Linux and macOS, localhost causes the MySQL client to try connecting via Unix socket, which isn’t available on the host. Use 127.0.0.1 explicitly: mysql -h 127.0.0.1 -P 3306.
Mount a named volume to /var/lib/mysql when you start the container: -v mysql-data:/var/lib/mysql. Without this, all data is stored in the container’s writable layer and lost when you run docker rm. Named volumes survive container removal and are managed by Docker — use docker volume ls to list them and docker volume inspect mysql-data to find the storage location. In Docker Compose, declare the volume under the top-level volumes: key and reference it in the service’s volumes: list (the example in the Docker Compose section above shows this pattern).
How do containers in Docker Compose connect to MySQL?
Containers in the same Compose project share a default network. Your application container connects to MySQL using the service name as the hostname — if your database service is named db, the connection string is host=db port=3306. You don’t need to expose ports between containers; port mapping (-p 3306:3306) is only needed for connections from the host machine. Use depends_on: condition: service_healthy with a healthcheck on the MySQL service to prevent the application from starting before MySQL is ready.
How do I run a SQL file against a running container?
docker exec -i mysql-dev mysql -u root -p myapp < /path/to/script.sql# or from within the container:docker exec -it mysql-dev mysql -u root -p myapp -e "SOURCE /tmp/script.sql"