The Players

Jupyter is the latest incarnation of an interactive notebook project that grew up out IPython, and describing the all the goals and history of that project is waaay outside of the scope of this write-up. Suffice to say that from the humble beginnings of an interactive shell, Jupyter has become an amazing tool for interactive presentations, education, and data-driven scientific computing amongst other things. It has support for console and web-based interaction. Most relevant to the topic presented here: Jupyter is no longer limited to python and has support for multiple language backends as well.1.

IElixir is a neat piece of work that brings an Elixir language kernel to jupyter.

The Goal

Jupyter, IElixir, and Elixir itself all require significant installation and configuration, some of it at the system level. This can be annoying to someone who just wants to try it all out. These circumstances (or just the fact that I wanted to experiment) make IElixir a decent candidate for wrapping up inside docker.

Apart from doing things for the experiment's sake this is at least marginally practical. The standard approach to interactive elixir is useful but also pretty wonky. Here's a few brutal aspects of the UX (may vary somewhat depending on your platform):

  • The up/down keys for history don't work for out of the box, but the standard workarounds have stability issues
  • For better history, it seems there is some resistance to fixing the underlying problems anywhere but inside erlang core.
  • The situation is similar for control-L, the least-surprising shortcut for clearing the screen.

A work-around at the Jupyter level instead fixes the history annoyance at the same time as adding all the many niceties the Jupyter team has spent a lot of time setting up.2

Prior Art

The Jupyter project actually has official docker stacks in a few different flavors, and those might be more performant and minimal than what I'll be building here. I'm not using those as a docker base image, partly because I wanted to see what it would look like to build everything from scratch with ansible.

Actually, the procedure I'm going to describe here is basically just a more specific use-case for what I've described in the Provisioning Docker with Ansible writeup. That article takes things slower, but here I'll get straight to the point. You might like to read it if you want more explanation of some of these steps.

Setup files

Create a folder for this project, and create files for your ansible playbook.yml and Dockerfile with the content you see below.

# Dockerfile
FROM phusion/baseimage:0.9.19
CMD ["/sbin/my_init"]
RUN apt-get update
RUN apt-get install -y python2.7
RUN rm -f /etc/service/sshd/down
RUN /usr/sbin/enable_insecure_key
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

[gist 4b46c138f2787a8674aedf0feb3f4879]

Setup docker

Bootstrap images

Create the initial image and container with the commands below.

$ docker build -t "heredoc/ielixir" .
$ docker run --detach --name ielixir -t -i heredoc/ielixir /sbin/my_init --enable-insecure-key

Download SSH Key

In another terminal, change into your Dockerfile directory and download the insecure private key from phusion:

    :::bash
    $ curl -o insecure_key -fSL https://github.com/phusion/baseimage-docker/raw/master/image/services/sshd/keys/insecure_key
    $ chmod 600 insecure_key

Provision with Ansible

If you don't already have ansible installed, run pip install ansible. Next we need to save the container IP and provision the container with Ansible.

$ CIP="`docker inspect -f \"{{.NetworkSettings.IPAddress }}\" ielixir`"
$ echo $CIP
$ echo "docker ansible_host=$CIP ansible_python_interpreter=/usr/bin/python2.7" > ansible.hosts
$ ANSIBLE_HOST_KEY_CHECKING=False \
  ansible-playbook playbook.yml \
    --inventory ansible.hosts -v \
    --user=root --private-key=./insecure_key

Hopefully everything went smoothly. If so, now you'll want to save your container changes to the main image, and remove the container.

$ docker commit ielixir heredoc/ielixir
$ docker rm -f ielixir

Now to run the ielixir console and notebook, you'll need to run commands like what you see below. Note that due to the --rm argument, no changes are persistent.

# The console command!
$ docker run \
  --rm \
  --name ielixir-console \
  -it heredoc/ielixir \
  /root/venv/bin/jupyter-console \
    --kernel='ielixir'

After you've run the console command above, you might like to confirm for yourself this is actually Elixir since it looks like the oldschool IPython console. Try concatenating a string: "This is"<>" Elixir code!"

# The notebook command! (runs webserver on port 8888 in foreground)
# this is a demo and is NOT secure by default, jupyter's terminal
# functionality can give you a rootshell inside the container.
$ docker run \
  --rm \
  --name ielixir-nb \
  --publish 8888:8888 \
  -it heredoc/ielixir \
  /root/venv/bin/jupyter-notebook \
    --ip='*' --no-browser \
    --notebook-dir='/root/IElixir/resources'

After you've run the notebook command above, try taking a look at http://localhost:8888/notebooks/example.ipynb which ought to show the interactive version of this example notebook

  1. Many language backends are implemented, but few are official. See https://github.com/ipython/ipython/wiki/IPython-kernels-for-other-languages
  2. Sane editing for multiliners without an IDE? Yes please.