VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
Quick Start
Environment Variables
| Variable | Required | Description |
|---|---|---|
MYSQL_ROOT_PASSWORD | Yes (or use ALLOW_EMPTY_PASSWORD) | Root user password |
MYSQL_DATABASE | No | Database to create on startup |
MYSQL_USER | No | Non-root user to create |
MYSQL_PASSWORD | No | Password for MYSQL_USER |
MYSQL_ALLOW_EMPTY_PASSWORD | No | Set to yes to allow empty root password (dev only) |
MYSQL_RANDOM_ROOT_PASSWORD | No | Set to yes to generate a random root password (logged to stdout) |
MYSQL_ONETIME_PASSWORD | No | Set to yes to require password change on first login |
MYSQL_USER and MYSQL_PASSWORD must be set together. The created user receives full access to MYSQL_DATABASE.
Persistent Data with Volumes
Without a volume, all data is lost when the container is removed. Mount a volume to persist the data directory:mysql-data) persists across container restarts and removals. To use a host directory instead:
Choosing an Image and Volume Strategy
Official mysql:8.4 | villagesql/villagesql:latest | |
|---|---|---|
| Base | MySQL 8.4 | MySQL 8.4 tracking fork |
| Extensions | None built-in | VEF framework; install .veb bundles |
| Drop-in replacement | — | Yes — same wire protocol, same client tools |
| Image availability | Docker Hub | Docker Hub (villagesql/villagesql) |
| Use when | Standard MySQL workload | 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 |
Docker Compose
For development environments with multiple services, Docker Compose is the standard approach:healthcheck with depends_on: condition: service_healthy ensures the application container doesn’t start until MySQL is ready to accept connections — not just started.
Custom Configuration
Pass a custommy.cnf by mounting it:
Initialization Scripts
The container runs any.sql or .sh files placed in /docker-entrypoint-initdb.d/ on first startup (when the data directory is empty):
Connecting from Applications
Inside a Docker Compose network, containers connect to MySQL by service name:127.0.0.1 (not localhost — MySQL’s socket isn’t available on the host):
When to Run MySQL in Docker
| Scenario | Docker | Native install | Managed cloud (RDS, Cloud SQL) |
|---|---|---|---|
| Local development | Best choice — isolated, reproducible, easy teardown | Works, but harder to reset | Overkill; adds cost and network latency |
| CI/CD pipelines | Best choice — spin up fresh instance per run | Possible but slower to provision | Possible; adds cost per run |
| Staging environment | Good — mirrors prod topology in Compose | Fine if the host is dedicated | Good if prod is also managed |
| Production (single server) | Fine with named volumes + proper backups | Fine; slightly simpler ops | Adds managed backups, HA, failover |
| Production (high availability) | Needs orchestration (Kubernetes, Swarm) | Manual replication setup | Best choice — HA built in |
| VillageSQL extensions needed | Use villagesql/villagesql image | Build from source | Not available on managed services |
| Compliance / data residency | Check your policy — data stays on-host | Clear — data stays on-host | Depends on provider region config |
Production Considerations
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
--memoryin Docker to prevent MySQL from exhausting host memory, and tuneinnodb_buffer_pool_sizeto match. - Backups:
mysqldumpworks 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-devor 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.
Frequently Asked Questions
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.
How do I make MySQL data persist in Docker?
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 nameddb, 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?
Troubleshooting
| Problem | Solution |
|---|---|
| Container exits immediately | Check docker logs mysql-dev — missing MYSQL_ROOT_PASSWORD or volume permission issue |
ERROR 2002: Can't connect to local MySQL server through socket | Connect via TCP (-h 127.0.0.1) not socket — the Unix socket isn’t on the host |
| Application starts before MySQL is ready | Add a healthcheck and use depends_on: condition: service_healthy in Compose |
Data lost after docker rm | Volume wasn’t mounted — recreate the container with -v mysql-data:/var/lib/mysql |
| Init scripts didn’t run | They only run when the data directory is empty (first-time setup) — remove the volume and restart to reinitialize |

