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
ghcuser’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
PATHdoesn’t containghc-controlled directories like~/.cabal/binor~/.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:
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.ymlandjobs.yamlto figure out which Docker images are used by GHC CI. Here we usedeb10, although a bit outdated, it’s the most widely used one, especially for edge cases likeunregortsanCI jobs. - We use the
fixuidproject for correcting the defaultghcuser’s uid/gid to the numbers provided. Typically,fixuidis invoked once upon first run of the container, but it’s actually possible to do this at image build time. TheUIDandGIDvariables must be specified todocker buildvia--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)