Using MySQL in Docker for local testing in Go

Using MySQL in Docker for local testing in Go

part of Go Cookbook
When developing a Go application that uses MySQL, I like running the database in Docker. It keeps database files and server versions separate from the host’s package manager, and lets different projects use different MySQL versions.
On Docker Desktop, publish a container port to loopback and connect to 127.0.0.1; there is no need to discover a Docker VM’s IP address. This assumes the Docker engine runs on your development machine, not on a remote Docker host.
The original version of this article parsed human-readable docker ps output in Go. Docker Compose can manage that lifecycle for us, and a SQL health check can distinguish “container running” from “database ready.”

Configure the database

Save this as compose.yaml in your project:
services:
  db:
    image: mysql:8.4
    ports:
      - "127.0.0.1:7200:3306"
    environment:
      MYSQL_ROOT_PASSWORD: local-root-password
      MYSQL_DATABASE: cookbook
      MYSQL_USER: cookbook
      MYSQL_PASSWORD: local-app-password
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "MYSQL_PWD=\"$$MYSQL_PASSWORD\" mysql --protocol=TCP -h 127.0.0.1 -u\"$$MYSQL_USER\" \"$$MYSQL_DATABASE\" -e 'SELECT 1'"]
      interval: 2s
      timeout: 5s
      retries: 60
      start_period: 20s
volumes:
  mysql-data:
These are disposable local-development credentials. The application gets its own user instead of connecting as root. The published port is restricted to loopback.
The example uses the MySQL 8.4 series; choose the official image that matches production. Pin an exact patch tag or digest when reproducibility matters. Changing the tag is not a database migration plan.
The named volume preserves data across container replacement. Initialization variables create users and databases only when the data directory is empty; changing a password in this file does not change an existing database user’s password.
Compose expands $ expressions, so $$ passes a literal dollar sign to the shell inside the container. The health check connects over TCP and runs a query as the application user.

Start it manually or from Go

With a recent Docker Compose plugin:
docker compose up -d --wait --wait-timeout 180 db
--wait waits for the service to become healthy and reports a failure if the deadline expires. First-time initialization can take a while.
To automate this from your Go application’s development mode:
func startLocalMySQL(ctx context.Context, projectDir string) error {
	ctx, cancel := context.WithTimeout(ctx, 4*time.Minute)
	defer cancel()
	cmd := exec.CommandContext(ctx, "docker", "compose", "up",
		"-d", "--wait", "--wait-timeout", "180", "db")
	cmd.Dir = projectDir
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.WaitDelay = 5 * time.Second
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("start local MySQL: %w", err)
	}
	return nil
}
This function uses context, fmt, os, os/exec, and time. Pass the directory containing compose.yaml. Only call it in development. In production, read the database address and credentials from deployment configuration.
Cancelling the command stops the Compose client, not necessarily the containers it already started. Inspect them with docker compose ps and docker compose logs db if startup fails.

Connect with database/sql

Add a MySQL driver to your Go module:
go get github.com/go-sql-driver/mysql
This complete program checks that the local database accepts a connection:
package main

import (
	"context"
	"database/sql"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql", "cookbook:local-app-password@tcp(127.0.0.1:7200)/cookbook?parseTime=true&timeout=5s&readTimeout=5s&writeTimeout=5s")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		log.Fatal(err)
	}
	log.Println("MySQL is ready")
}
sql.Open creates a connection pool and may not connect immediately. PingContext verifies connectivity. Reuse the pool throughout the application, and pass contexts to queries and transactions. The driver documentation describes DSN options and mysql.Config for constructing a DSN from configuration values.
If the Go application is also a Compose service, connect to db:3306 on the Compose network instead of the host’s loopback port.

Stop or reset the database

docker compose stop db stops the service. docker compose down removes its containers and network while keeping the named volume. Adding --volumes deletes the stored database, so use that only when you intend to reset local data.
For automated integration tests, isolate data between tests and register cleanup with t.Cleanup. The Testcontainers for Go MySQL module is another option when tests should own a temporary container and dynamically assigned port.
The original helper documents the earlier Docker CLI approach.
#go
Sep 5 2026

Related articles

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you: