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 containghc
-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:
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
andjobs.yaml
to 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 likeunreg
ortsan
CI jobs. - We use the
fixuid
project for correcting the defaultghc
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. TheUID
andGID
variables must be specified todocker 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)