Skip to content

Instantly share code, notes, and snippets.

@stephanGarland
Last active August 21, 2026 15:29
Show Gist options
  • Select an option

  • Save stephanGarland/fd76ff4af9194583b324e77893b6b0aa to your computer and use it in GitHub Desktop.

Select an option

Save stephanGarland/fd76ff4af9194583b324e77893b6b0aa to your computer and use it in GitHub Desktop.
Run local MySQL 5.7 and 8 simultaneously on MacOS via Homebrew

Prerequisites

Mac

This has been tested only on ARM (Apple Silicon) Macs. YMMV on x86. I don't see why it wouldn't work, but you may need to change some directory paths.

MySQL

You'll need to have MySQL 5.7 and 8 installed. The former is somewhat tricky, because the formula is disabled. To install it, you can edit the formula (brew edit mysql@5.7), remove that line, and then install it with

HOMEBREW_NO_INSTALL_FROM_API=1 brew install mysql@5.7.

Brew may set a root password – either note it for use, or change it by logging in and using ALTER USER.

Instructions

  1. Stop both versions if running with brew stop mysql@$VERSION
  2. Create individual configuration files for each version:
export base_dir="/opt/homebrew/etc/my.cnf.d"
mkdir -p "$base_dir"

# NOTE: you can change the buffer pool settings as desired
# this would reserve a total of 8 GiB of RAM for the two instances
# if you don't need that, consider lowering it – use this script for exact values
# https://gist.github.com/stephanGarland/cd6a5c667e8406cf9cfb661a12399ddd

cat << EOF > "${base_dir}/mysql"{57,80}.cnf
[mysqld]
datadir = /opt/homebrew/var/mysqlXY
bind-address = 127.0.0.1
port = 330XX
socket = /tmp/mysqlYY.sock
innodb_adaptive_hash_index = 0
innodb_monitor_enable = all
innodb_buffer_pool_chunk_size = 134217728  # 128 MiB
innodb_buffer_pool_instances = 4
innodb_buffer_pool_size = 4294967296  # 4.0 GiB
innodb_print_all_deadlocks = on
innodb_status_output = on
innodb_status_output_locks = on
innodb_sync_array_size = 16
secure_file_priv = ""
EOF

# edit the files however you'd like to edit XX and YY
# suggested ports are 3306 and 3307
# suggested sockets are /tmp/mysql{57,80}.sock
  1. Create a service-only formula per version in a local tap.

    Since Homebrew PR #23312 (2026-08-01), brew services generates the plist from the formula's service block instead of reading the one in the keg, so editing the keg's .plist/.service has no effect. The core mysql@5.7 and mysql@8.0 formulae also both hardcode --datadir=/opt/homebrew/var/mysql — the same path for both — and mysqld_safe parses argv after every config file, so that flag always wins over my.cnf. A wrapper formula passes --defaults-file instead, putting the datadir back under config-file control, and survives brew upgrade.

    • Create the tap and a payload archive (a formula requires a url):
    brew tap-new $USER/local
    tap="$(brew --repo $USER/local)"
    mkdir -p "$tap"/{payload,src/mysql-service-1.0}
    echo "MySQL service wrapper" > "$tap/src/mysql-service-1.0/README.md"
    tar -C "$tap/src" -czf "$tap/payload/mysql-service-1.0.tar.gz" mysql-service-1.0
    shasum -a 256 "$tap/payload/mysql-service-1.0.tar.gz"
    • Create $tap/Formula/mysqlXY.rb for each version:
    class MysqlXY < Formula
      desc "Service wrapper: mysql@X.Y on port 330X (datadir var/mysqlXY)"
      homepage "https://dev.mysql.com/"
      url "file://#{HOMEBREW_PREFIX}/Library/Taps/<user>/homebrew-local/payload/mysql-service-1.0.tar.gz"
      sha256 "<output of shasum above>"
      version "1.0"
    
      depends_on "mysql@X.Y"
    
      def install
        doc.install "README.md"
      end
    
      service do
        run [HOMEBREW_PREFIX/"opt/mysql@X.Y/bin/mysqld_safe",
             "--defaults-file=#{HOMEBREW_PREFIX}/etc/my.cnf.d/mysqlXY.cnf"]
        keep_alive true
        throttle_interval 60
        working_dir HOMEBREW_PREFIX/"var/mysqlXY"
      end
    end

    Install into doc rather than the keg root — a bare README at the root is treated as an empty installation and the install fails.

  2. Initalize the new data directories

/opt/homebrew/opt/mysql@X.Y/bin/mysqld --datadir=/opt/homebrew/var/mysqlXY --initialize
# NOTE: if you get an error that the directory isn't empty, it probably means
# you attempted to create the directory on your own – delete it, and try again
  1. Manually start each MySQL instance one at a time, and verify you can login to each one. This will run in the foreground, so have a second terminal window open.
/opt/homebrew/opt/mysql@X.Y/bin/mysql.server start --datadir=/opt/homebrew/var/mysqlXY
# NOTE: this should default to 3306, since you haven't specified a config file
# but in case of failure, try -P XXXX for the specified port
  1. Manually stop MySQL (you may have to issue kill against its PID)
  2. After ensuring that MySQL isn't running (ps -ef | grep mysqld), install the wrappers and start them.
brew install $USER/local/mysqlXY
brew services start mysqlXY

Usage

  • To run an instance on-demand, use brew services run mysqlXY
  • To stop a running instance, use brew services stop mysqlXY
  • To have an instance automatically start on boot, use brew services start mysqlXY
  • Never use brew services start mysql@X.Y — it targets /opt/homebrew/var/mysql
  • Optionally alias the two versions, e.g. alias mysql80="mysql -S /tmp/mysql80.sock -uroot -pLOL"
    • Or, for a more secure installation (other than running as root – it is assumed you can fix that), use mysql_config_editor:
    mysql_config_editor set --login-path=mysql80 -S /tmp/mysql80.sock -uroot -p
    # enter your password when prompted
    # then, to use this, you'd run:
    mysql --login-path=mysql80
    # which you could _also_ wrap into a shell alias like 'mysql80' if you'd like
  • Also, it's recommended that you include --default-character-set=utf8mb4 in your mysql alias
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment