Easy Haskell Development Setup with Docker

Tony DiPasquale

Haskell, a statically typed, purely-functional programming language, is not only fun to develop with, but can make you a better programmer just by learning it. If you’ve never tried it, I highly suggest you try your next side project in Haskell. We can even help you get started.

If you have used it before, you’ll know that getting started can be a long process. Compiling GHC, cabal, or a long list of dependencies can take minutes to hours and all you really want to do is code! You might also run into dependency issues if you’re not sandboxing correctly which can cause errors that will frustrate and confuse even the most patient programmers. I want your first and every experience with Haskell to be enjoyable! Using Docker, we abstract the setup and allow you to focus on the fun parts of Haskell: Haskell itself.

In order to make Haskell development smoother, we’ve created a few Docker images that you can use. They start containers that come installed with the base dependencies needed to develop with Haskell and with Yesod, a popular web development framework. We’re going to walk you through the process of installing and setting up Docker for you machine. Then we’ll get you right into a working Haskell development environment. Finally, we’ll look at creating a Yesod project and setup your development environment.

Installing Docker

We first need to install Docker. The installation instructions are a little different depending on your system. We’ve outlined a couple below but if you are looking for more detail or for the instructions of your specific system you can follow Docker’s install instructions.

Installation on OS X

If you’re on a Mac, you’ll also need to install boot2docker which is a set of tools wrapped around a minimal Linux Virtual Machine for Docker to live inside. boot2docker requires VirtualBox installed first. So the complete install instructions for using Homebrew on a Mac are:

  1. Install VirtualBox
  2. run brew update && brew install boot2docker docker docker-compose

Great! Now start boot2docker by running boot2docker init then boot2docker up. It should print three export commands. Copy those and run them in your terminal. Now you’re ready to use docker! If you switch terminal contexts and need those export statements again you can run boot2docker shellinit and copy-paste them.

You can add boot2docker to your launch control so you don’t have to remember to start it after every restart. In your terminal run the following:

ln -sfv /usr/local/opt/boot2docker/*.plist ~/Library/LaunchAgents
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.boot2docker.plist

Then add $(boot2docker shellinit 2>/dev/null) to your shell startup file, like .bashrc or .zshrc.

Installation on Debian

apt-get update && apt-get install docker.io

Diving into Haskell

If you just want to mess around with Haskell, you can easily boot into our latest GHC image and run ghci to open a REPL.

docker run --rm --interactive --tty thoughtbot/ghc

--rm disposes of the container when we’re done while --interactive and --tty allow us to interact with the process running inside the container.

You could also make your own Dockerfile that inherits from this one to give you the base GHC install and dependencies. For example, if you wanted to have an image that starts with Yesod installed you could create this Dockerfile:

FROM thoughtbot/ghc

RUN cabal update
RUN cabal install happy yesod-bin

And we’ve done just that to provide you with a default Yesod setup for your web projects.

Setting Up a Yesod Project

To quickly setup a new Yesod project, we provide a yesod-bin Docker image which gives you easy access to the yesod command. Make and navigate into a new project directory.

mkdir myproj && cd myproj

Then run this command:

docker run --rm --interactive --volume $PWD:/src thoughtbot/yesod-bin init --bare

This command mounts your working directory to the /src directory in the container and then runs yesod init --bare. Follow the prompts to create a new project and voilà, that’s it!

Yesod Development with Docker

OK, your project is setup, what next? To run the base project you’ll have to compile your dependencies and link a database. We have your back here, too. We’ve created a base Yesod image to get you started. You’ll have to make a Dockerfile and a docker-compose.yml file for your project, but no fear, we’re going to walk you through it.

First, let’s create the Dockerfile using the Yesod image as a base:

FROM thoughtbot/yesod

RUN mkdir -p /app
WORKDIR /app

COPY *.cabal ./
RUN cabal install --dependencies-only -j4 --enable-tests

The keyword FROM specifies our base image, then we create our app directory and set it as our working directory. Next, we copy the .cabal file from the newly created Yesod project into the container. Finally, we install the dependencies making sure to enable tests.

Now, on to our docker-compose.yml file:

db:
  image: postgres:9.4
  ports:
   - "5432"
web:
  build: .
  command: yesod devel
  environment:
    - HOST=0.0.0.0
    - PGUSER=postgres
    - PGPASS
    - PGHOST=db
  stdin_open: true
  volumes:
   - .:/app
  ports:
   - "3000:3000"
  links:
   - db

There is a bit more going on in this file, but it’s not bad, I promise! First, we create our database container calling it db. It uses the base Postgres version 9.4 image hosted on Docker Hub and opens up the port 5432 which is the default port used by Postgres. Then we setup our Yesod app naming it web. We set it to build the image from the Dockerfile in the current directory and then run the command yesod devel. Next, we setup our environment with the database configuration, keep stdin open (required for the yesod devel command), mount our current directory to the container’s /app folder, map the port 3000, and finally link the db container to the web container.

Almost there! All that’s left now is to create your database. If you look at the config/settings.yml file that yesod init created, you’ll see the name of the database that Yesod is expecting to find. We can create this database in our db container by running this command:

docker-compose run web createdb -h db -U postgres DATABASE_NAME

You can also create your test database the same way. The name of the test database that Yesod will look for is in your config/test-settings.yml.

docker-compose run web createdb -h db -U postgres DATABASENAMEtest

That’s all! Now let’s run our app!

docker-compose up

View your app, the default Yesod landing page, in your browser:

Launch on OS X

open http://$(boot2docker ip):3000

Launch on Linux

$BROWSER http://localhost:3000

Now go forth and Haskell!