4 min read

Setting up an Elm Project using Docker

I didn’t get into containers when they first came out. I didn’t get the hype. Now I’m finally learning about uses for them, and one use case I really like is for isolating development environments.

For Elm, I have a number of small projects. I want to be able to use a particular version of Elm with each project. If I install Elm system wide on my computer, which is the default way of doing it, I have to upgrade each project if I come back to work on it.

By using Docker, I fully automate the process of getting Elm installed, each project is isolated and can have it’s own version, and if I want to do some development on a different machine, it’s very quick to get a reasonably working dev environment (editor excluded).

For larger projects with multiple developers, this is super nice. Instead of a long list of instructions a new dev needs to follow to get set up, they just clone the repo, run a script, and have the software running in seconds. Big props to a colleague of mine who advocated for this and made it happen, it changed the way I think about dev environments.

The first thing you’ll need is Docker itself. Head over to https://docs.docker.com/install/ and follow the instructions for installing Docker on your OS.

Once you’ve got Docker, the next thing you need is a Dockerfile. Here’s my Dockerfile that I start with for a new Elm project:

FROM ubuntu:18.04

WORKDIR /workdir

RUN DEBIAN_FRONTEND=noninteractive apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y \
    git \
    nodejs \
    npm

RUN npm install create-elm-app -g

# If you don't want all the stuff create-elm-app provides, use this instead:
# RUN npm install elm -g

# Without this Elm writes stuff to /root/.elm within the container.
ENV ELM_HOME=/workdir/.elm-home
RUN mkdir -p /workdir/.elm-home

CMD bash

Ubuntu is a somewhat heavy container. If you want something that takes less space, use Alpine instead:

FROM alpine:3.10.2

WORKDIR /workdir

RUN apk add git nodejs npm

RUN npm install -g --unsafe-perm=true create-elm-app

# If you don't want all the stuff create-elm-app provides, use this instead:
#RUN npm install -g --unsafe-perm=true elm

# Without this Elm writes stuff to /root/.elm within the container.
ENV ELM_HOME=/workdir/.elm-home
RUN mkdir -p /workdir/.elm-home

CMD sh

I use create-elm-app to start Elm projects that might actually go somewhere. If you want a slimmer docker image with less stuff, you can comment out that line in the Dockerfile and uncomment the line that only installs Elm.

Great! You’ve got a Dockerfile, now turn it into an image:

docker build -t my-elm-project .

The -t flag “tags” the image for easy reference later. You can call it whatever you want, but it probably should be something that makes sense in the context of your project. The . at the end is the current working directory, the contents of which are sent to the Docker daemon as the “context” when you build the image. By default, docker build looks for your Dockerfile here.

Once you have an image, you can create a container from it and start it up:

docker run -ti --rm -v $(pwd):/workdir -p 3000:3000 my-elm-project

Docker’s main use case is for running applications on servers. By default, starting a container puts it in the background so you can’t see its output or type input into it. The -ti flags connect it to your terminal so that you can interact with it. The --rm flag tells docker to remove the container when it exits. The -v flag tells Docker to mount the current directory to /workdir inside the container. This way your source code is accessible both inside and outside the container. The -p flag tells Docker to forward requests to port 3000 from outside the container to port 3000 inside the container. This allows you to hit the web server you will run to serve your Elm code from your web browser. Finally, the last argument to the run command is the tag you gave the image when you built it.

After that docker run command, you should be inside the container, seeing a prompt like this:

/workdir #

Ok cool! Let’s create an Elm project and start writing some code! Run this command:

create-elm-app my-app
cd my-app
elm-app start

You can point your browser at http://localhost:3000/ and see that its working!

The rest is on you. Open my-app/src/Main.elm and create create create. Changes to Main.elm will be detected and the compiler will run.

When you are ready to shut the container down, type ctrl+c to exit elm-app start, then type exit or type ctrl+d to exit the container.

It might be good to add the docker commands to build and run the container to a README, or to make scripts for them. They way if you take a break and come back to the project later, it’ll be easier to get going again. Your future self will thank you.