Presenting With Docker

A few people have asked me about my presentation environment recently. I present about 30-40 times a year and I’ve got my process (still not funny apparently) and tooling pretty much down pat.

For my content, I’ve long been a fan of Hakim El Hattab’s Reveal.JS. Reveal.JS is a presentation framework that allows you to create awesome slideshows that can be displayed via your browser via a builtin web server.

You can specify slides in HTML or Markdown.

  <div class="slides">
      <h1>Your title</h2>

        <li>Your insightful argument</li>
        <li>More wisdom...</li>

I’ve recently combined this with Wetty, a terminal in the browser that allows me to have in slide SSH. No longer do I need to switch between my terminal and my slides whilst demo’ing. I find that’s pretty bloody cool (and people love it). I sometimes have as many people ask me how I do the “secret in browser terminal magic” than I do about the topic of my presentation.

{} wetty

One of the annoying aspects of using both tools is that they do get updated fairly regularly and installation is still basic and via git clone. This means I need to keep them up to date to fix minor issues. It also means I end up with a checkout of both tools inside each repository that I store my presentations in, like my Introduction to Docker talk.

So I decided that I’d make my life easier by creating a Docker image for my presentation stack. I designed it so that the data (my content) would be separated from the code (the presentation stack). Each presentation repository contains the following files:

The slides/slides.html file holds my slides and the images directory holds any images I am using in my slides.

I then have a Docker image that holds my presentation stack. Here’s the Dockerfile for my image.

FROM ubuntu:14.04
MAINTAINER James Turnbull <>

RUN apt-get -qqy update
RUN apt-get -qqy install git nodejs npm
RUN ln -s /usr/bin/nodejs /usr/bin/node

# Install reveal.js
RUN git clone presentation
WORKDIR /opt/presentation
RUN npm install -g grunt-cli
RUN npm install
RUN sed -i "s/port: port/port: port,\n\t\t\t\t\thostname: \'\'/g" Gruntfile.js

# Install wetty
RUN git clone
WORKDIR /opt/presentation/wetty
RUN npm install

# Add content
ADD docker.css /opt/presentation/css/theme/docker.css
ADD index.html /opt/presentation/index.html
ADD images /opt/presentation/images/
ADD slides /opt/presentation/slides/

WORKDIR /opt/presentation


CMD [ "grunt", "serve" ]

The Dockerfile is pretty self-explanatory. I base the image on Ubuntu 14.04 (to get the latest NodeJS). I install NodeJS, NPM, and Git. I then install Reveal.JS, its dependencies and Grunt to power it.

By default Grunt only binds the Reveal.JS server to localhost so I edit the Gruntfile.js to update the server. This will bind Grunt to all interfaces so I can expose my presentation on the container’s external network interface.

I also add Wetty and its dependencies.

I then add some template content and images I use and a Docker CSS theme to render my slides using Docker’s colors and styles.

Finally I set my working directory to the presentation directory, expose port 8000 and specify a command to run, grunt serve, when a container is launched from this image. So when a container is launched Grunt will serve out my presentation on port 8000, which I can then directly map 1:1 or on a port in Docker’s default port range.

I can then build my image (I actually use a Trusted Build but I could also build from the command line):

$ sudo docker build -t="jamtur01/docker-presentation" .

Then launch a container from my image!

$ sudo docker run -p 8000:8000 --name docker_presentation \
-v /Users/james/src/intro-to-docker/images:/opt/presentation/images \
-v /Users/james/src/intro-to-docker/slides:/opt/presentation/slides \
-d jamtur01/docker-presentation

This creates a container called docker_presentation and mounts /Users/james/src/intro-to-docker/images and /Users/james/src/intro-to-docker/slides into the container. I would update these paths for the specific presentation I am loading. The container is launched daemonized and port 8000 inside the container is mapped to port 8000 outside on the host.

If I now browse to http://localhost:8000 I will see my Introduction to Docker presentation.

If I wanted to launch another presentation I could create a new container like so:

$ sudo docker run -P --name ops_mythology \
-v /Users/james/src/ops-mythology/images:/opt/presentation/images \
-v /Users/james/src/ops-mythology/slides:/opt/presentation/slides \
-d jamtur01/docker-presentation

I’ve updated my volume mounts to the new presentation and I am now mapping the port to one of Docker’s ports. This allows me to run more than one presentation locally rather being restricted to port 8000. Let’s see which port got assigned with the docker port command.

$ sudo docker port 0261a0d6cb4b 8000

If I now browse to http://localhost:49159 I will see my Ops Mythology presentation.

Now I don’t need to worry about anything in my presentation stack or worse duplicate its code in every presentation. Now all I need to do is maintain my content and my images and I am done!

You can find the code for this here and the Docker image is available on here.

comments powered by Disqus