Setting up Multiple Redis Instances on Mac OS X

2020-06-07

The recommended way to use Redis in a dev environment is to install and run multiple instances. Redis does ship with some rudimentary namespacing support--a connection can switch "databases'', but the utility of this approach is limited, mainly by the fact that Redis databases cannot be named, only numbered 0 through 15. In a production deployment, you will have one Redis instance per application. In the interest of having your development environment match production as closely as possible, it makes sense to run multiple redises locally, and have the development configuration connect directly via a localhost port. Note that this is not how I recommend to work with a local RDBMS such as Postgres, where in production you will specify a named database. Therefore, passing a database name involves no extra code complexity with Postgres, whereas with Redis it does.

First, install Redis using brew, if you don't have it already. The brew installation will also install a .plist file at the appropriate location for Redis to be started on system startup. This file can be found by running brew services list--it should be in the ~/Library/LaunchAgents/ directory. This directory contains service specifier files for processes to run on system launch. We're going to copy it and create a duplicate Redis instance to run alongside the one managed by Homebrew.

Copy the Homebrew plist file to the same directory, and give it a different name. I did something like

cp ~/Library/LaunchAgents/homebrew.mxcl.redis.plist ~/Library/LaunchAgents/$USER.$APPNAME.redis.plist

where $APPNAME is the name of the application I want to run an extra Redis instance for. Now we need to edit some key/value pairs in the .plist file:

vim ~/Library/LaunchAgents/$(whoami).${APPNAME}.redis.plist
    <key>Label</key>
-   <string>homebrew.mxcl.redis</string>
+   <string>USER.APPNAME.redis</string>
      <string>/usr/local/opt/redis/bin/redis-server</string>
-     <string>/usr/local/etc/redis.conf</string>
+     <string>/usr/local/etc/redis-APPNAME.conf</string>
      <string>--daemonize no</string>
    <key>StandardErrorPath</key>
-   <string>/usr/local/var/log/redis.log</string>
+   <string>/usr/local/var/log/redis-APPNAME.log</string>
    <key>StandardOutPath</key>
-   <string>/usr/local/var/log/redis.log</string>
+   <string>/usr/local/var/log/redis-APPNAME.log</string>

Now that we have a service specifier ready to go, we need to make sure that the configuration file points to something.

cp /usr/local/etc/redis.conf /usr/local/etc/redis-$APPNAME.conf
vim /usr/local/etc/redis-$APPNAME.conf

You'll need to edit the port. Hopefully this is obvious, but please enter a real port, not the literal string "$PORT". You can just use the 6380-6390 block to make it easy to remember which block of ports your Redis instances are listening on:

# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
-port 6379
+port $PORT

You may also want to require a password for connections. This is in line with trying to make our development environment as similar as possible to production:

-requirepass foobared
+requirepass $MYSTRONGPASS

Store that password in a password manager, not in a version control system. The development environment password leaking is not such a big deal, but it's good to build the muscles of devops practice.

Save the modified configuration file, and start the Redis instance:

launchctl load ~/Library/LaunchAgents/$USER.$APPNAME.redis.plist
launchctl load $USER.$APPNAME.redis
launchctl start $USER.$APPNAME.redis

You should be able to tail the log file and see that everything is okay, and Redis is accepting connections.

tail -f /usr/local/var/log/redis-$APPNAME.log