Using a Language Server in a Docker Container

Projects that use docker for local development run into a problem when trying to use language servers with their text editor—they don’t handle multiple environments.

There are a few ways around it, but they all have trade-offs.

Running a remote language server

You can connect to a remote language server (it is a server after all) by wrapping the command in a script and configuring your text editor to use it.

For example (assuming you have docker container with pyls):

#!/bin/bash

# Runs a python-language-server in a running container for use with
# eglot in emacs.

docker compose -f /path/to/my/docker-compose.yml \
               exec \
               -T \
               myservice pyls

Everything works with the exception of jump-to-definition (at least when using Emacs eglot). That’s because the file path returned by the language server is the file path in the container, not your host machine.

In Emacs, you can hack around this in eglot (albeit in an inelegant way) by redefining xref-make-file-location like so:

;; HACK: If the xref file doesn't exist, it probably came from a
;; remote LSP server using eglot. Try mapping it to the local
;; file system. Maybe someday it will be supported in eglot.
;; See: https://github.com/joaotavora/eglot/issues/350
(eval-after-load "xref"
  '(defun xref-make-file-location (file line column)
     (if (not (file-exists-p file))
         (make-instance 'xref-file-location :file (format "~/your/host/file/path" file) :line line :column column)
       (make-instance 'xref-file-location :file file :line line :column column))))

Edit files in the remote language server container

To get around the compatibility issues that comes from running in two different environments (the host machine and the container), you can edit the files remotely in the container that is running the language server (in Emacs you can use docker-tramp). This keeps the environment consistent, but it’s a bit clunky when you need to remember where the files are and flipping back and forth (e.g. when doing a `git commit` from the host machine).

  • Setting up Typescript and Eslint With Eglot

    Using TypeScript with eglot in Emacs is fiddly to set up. I also prefer using eslint as a plugin to typescript-language-server so no other setup is required. Together, this makes for an extremely portable setup with minimal fuss which is the whole point of the language server protocol to begin with.