Skip to main content

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.

Quick Start

docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -e MYSQL_DATABASE=myapp \
  -e MYSQL_USER=appuser \
  -e MYSQL_PASSWORD=apppass \
  -p 3306:3306 \
  -d mysql:8.4
Connect from the host machine:
mysql -h 127.0.0.1 -P 3306 -u appuser -p myapp
Or connect inside the container:
docker exec -it mysql-dev mysql -u root -p

Environment Variables

VariableRequiredDescription
MYSQL_ROOT_PASSWORDYes (or use ALLOW_EMPTY_PASSWORD)Root user password
MYSQL_DATABASENoDatabase to create on startup
MYSQL_USERNoNon-root user to create
MYSQL_PASSWORDNoPassword for MYSQL_USER
MYSQL_ALLOW_EMPTY_PASSWORDNoSet to yes to allow empty root password (dev only)
MYSQL_RANDOM_ROOT_PASSWORDNoSet to yes to generate a random root password (logged to stdout)
MYSQL_ONETIME_PASSWORDNoSet 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:
docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -v mysql-data:/var/lib/mysql \
  -p 3306:3306 \
  -d mysql:8.4
Named volume (mysql-data) persists across container restarts and removals. To use a host directory instead:
-v /path/on/host:/var/lib/mysql
The data directory owns MySQL’s data and logs. Never share a volume between two running MySQL containers.

Choosing an Image and Volume Strategy

Official mysql:8.4villagesql/villagesql:latest
BaseMySQL 8.4MySQL 8.4 tracking fork
ExtensionsNone built-inVEF framework; install .veb bundles
Drop-in replacementYes — same wire protocol, same client tools
Image availabilityDocker HubDocker Hub (villagesql/villagesql)
Use whenStandard MySQL workloadNeed custom types, VDF functions, or VEF extensions
Volume typePersistencePerformancePortability
Named volume (-v mysql-data:/var/lib/mysql)Survives docker rmManaged by Docker; good on all platformsEasy to back up with docker volume commands
Bind mount (-v /host/path:/var/lib/mysql)Survives container removalSlower on macOS/Windows (filesystem translation)Direct host access; easier to inspect files
No volume (container layer)Lost on docker rmFastest 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.

Docker Compose

For development environments with multiple services, Docker Compose is the standard approach:
services:
  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppass
    volumes:
      - mysql-data:/var/lib/mysql
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    build: .
    environment:
      DB_HOST: db
      DB_PORT: 3306
      DB_NAME: myapp
      DB_USER: appuser
      DB_PASSWORD: apppass
    depends_on:
      db:
        condition: service_healthy

volumes:
  mysql-data:
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.

Custom Configuration

Pass a custom my.cnf by mounting it:
docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -v /path/to/my.cnf:/etc/mysql/conf.d/custom.cnf:ro \
  -d mysql:8.4
Or set options directly via command arguments:
docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -d mysql:8.4 \
  --innodb-buffer-pool-size=512M \
  --max-connections=200

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):
docker run --name mysql-dev \
  -e MYSQL_ROOT_PASSWORD=rootpass \
  -v /path/to/init-scripts:/docker-entrypoint-initdb.d:ro \
  -d mysql:8.4
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.

Connecting from Applications

Inside a Docker Compose network, containers connect to MySQL by service name:
DB_HOST=db   # service name in docker-compose.yml
DB_PORT=3306
From the host machine, connect via 127.0.0.1 (not localhost — MySQL’s socket isn’t available on the host):
DB_HOST=127.0.0.1
DB_PORT=3306

When to Run MySQL in Docker

ScenarioDockerNative installManaged cloud (RDS, Cloud SQL)
Local developmentBest choice — isolated, reproducible, easy teardownWorks, but harder to resetOverkill; adds cost and network latency
CI/CD pipelinesBest choice — spin up fresh instance per runPossible but slower to provisionPossible; adds cost per run
Staging environmentGood — mirrors prod topology in ComposeFine if the host is dedicatedGood if prod is also managed
Production (single server)Fine with named volumes + proper backupsFine; slightly simpler opsAdds managed backups, HA, failover
Production (high availability)Needs orchestration (Kubernetes, Swarm)Manual replication setupBest choice — HA built in
VillageSQL extensions neededUse villagesql/villagesql imageBuild from sourceNot available on managed services
Compliance / data residencyCheck your policy — data stays on-hostClear — data stays on-hostDepends 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 --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.

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 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"

Troubleshooting

ProblemSolution
Container exits immediatelyCheck docker logs mysql-dev — missing MYSQL_ROOT_PASSWORD or volume permission issue
ERROR 2002: Can't connect to local MySQL server through socketConnect via TCP (-h 127.0.0.1) not socket — the Unix socket isn’t on the host
Application starts before MySQL is readyAdd a healthcheck and use depends_on: condition: service_healthy in Compose
Data lost after docker rmVolume wasn’t mounted — recreate the container with -v mysql-data:/var/lib/mysql
Init scripts didn’t runThey only run when the data directory is empty (first-time setup) — remove the volume and restart to reinitialize