DEV Community

Cheng Shao
Cheng Shao

Posted on

My GHC dev environment with vscode remote & docker

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:



  curl -SsL | 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


RUN fixuid

USER ghc

  sudo rm -r \
    /etc/fixuid \
    /usr/local/bin/fixuid \

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

  sudo apt update && \
  sudo apt install -y \
    bash-completion && \
  cd /tmp && \
  git clone && \
  cd haskell-language-server && \
  cabal --project-file=cabal-ghc92.project update && \
  cabal --project-file=cabal-ghc92.project install && \
  rm -rf /tmp/*
Enter fullscreen mode Exit fullscreen mode
  • Which base image to use? Check out .gitlab-ci.yml and 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 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.

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.

Top comments (0)