---
title: Easy Haskell Development Setup with Docker
teaser: Using Docker with Haskell removes the mess of setting up your environment
  and allows you to focus on the fun of programming in Haskell.
tags: web,haskell,docker
author: Tony DiPasquale
published_on: 2015-07-02
---

[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].

[get started]: https://thoughtbot.com/blog/we-want-to-teach-you-haskell

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.

[Haskell]: https://www.haskell.org/
[GHC]: https://www.haskell.org/ghc/
[cabal]: https://www.haskell.org/cabal/

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`

[install instructions]: https://docs.docker.com/
[Homebrew]: http://brew.sh/
[boot2docker]: http://boot2docker.io/
[VirtualBox]: https://www.virtualbox.org/wiki/Downloads

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:

```bash
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

<kbd>apt-get update && apt-get install docker.io</kbd>

## 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.

[latest GHC image]: https://registry.hub.docker.com/u/thoughtbot/ghc/
[`ghci`]: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html#ghci-introduction

<kbd>docker run --rm --interactive --tty thoughtbot/ghc</kbd>

`--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`:

```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.

[Yesod]: http://www.yesodweb.com/

## 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.

[yesod-bin]: https://registry.hub.docker.com/u/thoughtbot/yesod-bin/

<kbd>mkdir myproj && cd myproj</kbd>

Then run this command:

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

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.

[Yesod image]: https://registry.hub.docker.com/u/thoughtbot/yesod/

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

```dockerfile
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:

```yaml
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.

[Docker Hub]: https://registry.hub.docker.com/

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:

<kbd>docker-compose run web createdb -h db -U postgres DATABASE_NAME</kbd>

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`.

<kbd>docker-compose run web createdb -h db -U postgres DATABASE_NAME_test</kbd>

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

<kbd>docker-compose up</kbd>

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

### Launch on OS X

<kbd>open http://$(boot2docker ip):3000</kbd>

### Launch on Linux

<kbd>$BROWSER http://localhost:3000</kbd>

Now go forth and Haskell!
