My GHC dev environment with vscode remote & docker
Tags: haskell, ghc
Published: 2022-04-28 15:10:51

For my daily work of hacking GHC, I’ve recently transitioned to a workflow of using VSCode remote + Docker containers. The main advantage is being able to reuse the same Docker images used by GHC CI, so you can have more confidence with local test results before pushing. This post briefly walks through the process to set up such a dev environment.

## Building & starting the dev container

The first step is picking an image specified in the GHC CI config, using that as a base image to build our dev image. The original images aren’t suitable to be used directly, because:

- The default `ghc` user’s uid/gid likely doesn’t match your host user’s uid/gid. This will result in file permission issues if you mount a host directory into the container and use that as the working directory.
- The default `PATH` doesn’t contain `ghc`-controlled directories like `~/.cabal/bin` or `~/.local/bin`. This can be a minor annoyance if you build and install HLS(haskell-language-server) in those directories, the VSCode Haskell extension wouldn’t be able to find the executable automatically.

Here’s the `Dockerfile` recipe:

```docker
FROM registry.gitlab.haskell.org/ghc/ci-images/x86_64-linux-deb10:0849567cd9780cc8e9652118b949cb050c632ef4

ARG UID
ARG GID

RUN \
  curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.5.1/fixuid-0.5.1-linux-amd64.tar.gz | sudo tar -C /usr/local/bin -xzf - && \
  sudo chown root:root /usr/local/bin/fixuid && \
  sudo chmod 4755 /usr/local/bin/fixuid && \
  sudo mkdir -p /etc/fixuid && \
  printf "user: ghc\ngroup: ghc\n" | sudo tee /etc/fixuid/config.yml

USER ${UID}:${GID}

RUN fixuid

USER ghc

RUN \
  sudo rm -r \
    /etc/fixuid \
    /usr/local/bin/fixuid \
    /var/run/fixuid.ran

ENV PATH=${PATH}:/home/ghc/.cabal/bin:/opt/ghc/9.2.2/bin

RUN \
  sudo apt update && \
  sudo apt install -y \
    bash-completion && \
  cd /tmp && \
  git clone https://github.com/haskell/haskell-language-server.git && \
  cd haskell-language-server && \
  cabal --project-file=cabal-ghc92.project update && \
  cabal --project-file=cabal-ghc92.project install && \
  rm -rf /tmp/*
```

- Which base image to use? Check out [`.gitlab-ci.yml`](https://gitlab.haskell.org/ghc/ghc/-/blob/master/.gitlab-ci.yml) and [`jobs.yaml`](https://gitlab.haskell.org/ghc/ghc/-/blob/master/.gitlab/jobs.yaml) to figure out which Docker images are used by GHC CI. Here we use `deb10` , although a bit outdated, it’s the most widely used one, especially for edge cases like `unreg` or `tsan` CI jobs.
- We use the [`fixuid`](https://github.com/boxboat/fixuid) project for correcting the default `ghc` user’s uid/gid to the numbers provided. Typically, `fixuid` is invoked once upon first run of the container, but it’s actually possible to do this at image build time. The `UID` and `GID` variables must be specified to `docker build` via `--build-arg UID=$(id -u) --build-arg GID=$(id -g)`.
- We build and install HLS during image build time. There are prebuilt binaries available, but my past experience tells me HLS works properly only if built by the exact same GHC used to build your projects.

Start the container after the dev image is built. Don’t forget to mount your working directory.

The dev container is supposed to be a long-running container, and the simplest way to keep it running is using a `screen` or `tmux` session.

## Optional: setting up Docker context

Skip this part if you’re using Docker on your local machine. If you’re using a remote machine, the previous steps are done within an SSH session, then you also need to:

- Install the Docker CLI locally. It doesn’t need to be a full Docker installation, we don’t need to connect to the Docker daemon.
- Set up the Docker context using the instructions [here](https://code.visualstudio.com/docs/containers/ssh).

This way, the local `docker` commands will transparently talk to the remote Docker daemon using SSH.

## Connecting to the dev container

The rest is simple: enable the VSCode remote extension pack, click on the green button on VSCode window’s bottom left corner to open the remote menu, then select “Attach to Running Container”, voila. Set up extensions you use, open your working directory, and happy hacking GHC.