Running multiple emacs daemons

Srijan Choudhary Srijan Choudhary
- 2 min read

I have been using Emacs for several years, and these days I'm using it both for writing code and for working with my email (another post on that soon).

As commonly suggested, I run Emacs in daemon-mode to keep things fast and snappy, with an alias to auto-start the daemon if it's not started, and connect to it if started:

alias e='emacsclient -a "" -c'
Config for single daemon

But, this has some problems:

  1. The buffers for email and code projects get mixed together
  2. Restarting the emacs server for code (for example) kills the open mail buffers as well
  3. Emacs themes are global – they cannot be set per frame. For code, I prefer a dark theme (most of the time), but for email, a light theme works better for me (specially for HTML email).

To solve this, I searched for a way to run multiple emacs daemons, selecting which one to connect to using shell aliases, and automatically setting the theme based on the daemon name. Here's my setup to achieve this:

Custom run_emacs function in zshrc:

run_emacs() {
  if [ "$1" != "" ];
  then
    server_name="${1}"
    args="${@:2}"
  else
    server_name="default"
    args=""
  fi

  if ! emacsclient -s ${server_name} "${@:2}";
  then
    emacs --daemon=${server_name}
    echo ">> Server should have started. Trying to connect..."
    emacsclient -s ${server_name} "${@:2}"
  fi
}

This function takes an optional argument – the name to be used for the daemon. If not provided, it uses default as the name. Then, it tries to connect to a running daemon with the name. And if it's not running, it starts the daemon and then connects to it. It also passes any additional arguments to emacsclient.

Custom aliases in zshrc:

# Create a new frame in the default daemon
alias e='run_emacs default -n -c'

# Create a new terminal (TTY) frame in the default daemon
alias en='run_emacs default -t'

# Open a file to edit using sudo
es() {
    e "/sudo:root@localhost:$@"
}

# Open a new frame in the `mail` daemon, and start notmuch in the frame
alias em="run_emacs mail -n -c -e '(notmuch-hello)'"

The first 3 aliases use the default daemon. The last one creates a new frame in the mail daemon and also uses emacsclient's -e flag to start notmuch (the email package I use in Emacs).

Emacs config:

(cond
 ((string= "mail" (daemonp))
  (setq doom-theme 'modus-operandi)
 )
 (t
  (setq doom-theme 'modus-vivendi)
 )
)

This checks the name of the daemon passed during startup, and sets the doom theme accordingly. The same pattern can be used to set any config based on the daemon name.

Note that I'm using doom emacs, but the above method should work with or without any framework for Emacs. Tested with Emacs 27 and 28.

Interactions

  • B2Pi
    B2Pi

    I've just found that if ! emacsclient -s ${server_name} "${@:2}"; doesn't always 'do what I want it to' (i.e. return true on exit), especially when I've entered with no arguments. I changed to using pgrep, based partially on something in emacswiki, with a zsh alias using pgrep containing

    if ! pgrep -f -U $(id -u_ "emacs.*--daemon=${server_name}" >/dev/null
    then
    emacs --daemon=${server_name}
    echo ">> Server ${server_name} should have started. Trying to connect..."
    fi
    emacsclient -t -s ${server_name} "${@:2}"
    Reply
  • rollc_at

    I used to start runit from my .xsession, which would then run the WM, gpg/ssh agents, etc as services, auto-restart them on crash (very useful for WM development), and so on. I remember trying to get either the emacs server or the gpg agent (one or both was misbehaving, can't recall now), to work in this scenario, but at the time there was no way to start a headless server without forking into the background.

    runit makes the kinds of scenarios OP describes trivial, given the software in question doesn't go the extra mile to make it harder. The ssh agent will for example allow you to use a named pipe in your home directory rather than a random one in /tmp, which would make it trivial to run two (or twenty) instances. Service instances are trivial to create with symlinks: have a look at how Void Linux spawns getty to get the idea.

    I no longer use this setup because while robust, it was difficult to set up on a new machine (too much shell glue), and I switched from Linux to Mac & OpenBSD at some point. Perhaps it'd make sense to package and distribute it somehow, because ultimately it was really good, and I wish X11 desktops handled sessions this way.

    Reply