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.
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.
- Stop both versions if running with
brew stop mysql@$VERSION - 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-
Create a service-only formula per version in a local tap.
Since Homebrew PR #23312 (2026-08-01),
brew servicesgenerates the plist from the formula'sserviceblock instead of reading the one in the keg, so editing the keg's.plist/.servicehas no effect. The coremysql@5.7andmysql@8.0formulae also both hardcode--datadir=/opt/homebrew/var/mysql— the same path for both — andmysqld_safeparses argv after every config file, so that flag always wins overmy.cnf. A wrapper formula passes--defaults-fileinstead, putting the datadir back under config-file control, and survivesbrew 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.rbfor 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
docrather than the keg root — a bareREADMEat the root is treated as an empty installation and the install fails. - Create the tap and a payload archive (a formula requires a
-
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- 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- Manually stop MySQL (you may have to issue
killagainst its PID) - 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- 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
- Or, for a more secure installation (other than running as root – it is assumed you can fix that), use
- Also, it's recommended that you include
--default-character-set=utf8mb4in yourmysqlalias