..

Dockerizing Lubot

Lubot is the Lansing Codes chat bot responsible for bringing updates about all local events to chatters everywhere…or just in Lansing. Previously, it resided on a Heroku Free instance and was required to sleep for six hours out of every twenty-four hours. After some issues with him waking up, we began looking for alternatives. Since I already had a server hosting several Docker containers, it seemed like the best choice.

Docker is basically a way to create a container that can be easily distributed across many operating systems. Using it, we can take the code, runtime, and libraries that Lubot requires and put them in a package. You can read more about it in the Official “What is Docker” article.

To begin, we need to determine the necessary dependencies for our application. Lubot is built using NodeJS and uses npm to manage dependencies. Specifically, we are using Node v5.0.0 and npm v3.3.9. There’s an official Node Dockerfile to use to begin with so it is pretty easy to start.

FROM node

ENV NODE_VERSION 5.0.0
ENV NPM_VERSION 3.3.9

After that, we want to take care of the dependencies for our application. Because of the way Docker works, we want to cache this step so when our packages.json file does not change, we do not have to rebuild our dependencies.

ADD package.json /tmp/
RUN cd /tmp && npm install
RUN mkdir -p /opt/hubot && cp -a /tmp/node_modules /opt/hubot

Then, we need to add the application code to the container.

ADD . /opt/hubot
WORKDIR /opt/hubot

Finally, we can start the service.

CMD ["/opt/hubot/bin/hubot", "--adapter", "slack"]

Combine these steps and we end up with a Dockerfile. This gets added to the repisitory so that we can build the application. Building an image is easy.

docker build -t lansingcodes/lubot .

This will download and build the necessary filesystems, caching where necessary and giving us a runable container image. Starting the container is also simple.

docker run lansingcodes/lubot

Lubot expects some environment variables to be there. But since we are in a container, no environment variables exist on the system and we need to pass them in. Our new run command accounts for this.

docker run -d --restart=always --name lubot \
  -e HUBOT_SLACK_TOKEN=$HUBOT_SLACK_TOKEN \
  -e TWITTER_LANSINGCODES_CONSUMER_KEY=$TWITTER_LANSINGCODES_CONSUMER_KEY \
  -e TWITTER_LANSINGCODES_CONSUMER_SECRET=$TWITTER_LANSINGCODES_CONSUMER_SECRET \
  -e TWITTER_LANSINGCODES_ACCESS_TOKEN=$TWITTER_LANSINGCODES_ACCESS_TOKEN \
  -e TWITTER_LANSINGCODES_ACCESS_TOKEN_SECRET=$TWITTER_LANSINGCODES_ACCESS_TOKEN_SECRET \
  -e GOOGLE_API_KEY=$GOOGLE_API_KEY \
  -e LUBOT_MEETUP_API_KEY=$LUBOT_MEETUP_API_KEY \
  -e TZ=$TZ \
  -e REDIS_URL=$REDIS_URL \
  lansingcodes/lubot

Lubot is now running in a container. However, Heroku also provided easy continuous deployment when combined with Circle CI. Being able to have changes deployed when the master branch changes is handy. Circle CI allows us to specify post-build commands to run. Typically, we’d want to build the container on our CI server and then push to a Docker registry, butI didn’t have one of those available. We can still use Circle CI to execute commands on a remote server with SSH. This makes our deploy process simple.

  • clone the repository on our remote server
  • build the docker image from that repositry
  • run the docker image that was build

Our CI build file will trigger these actions via a script.

scp deploy/deploy.sh lubot@app.atomaka.com:/home/lubot
ssh lubot@app.atomaka.com "bash /home/lubot/deploy.sh"

And then, deploy.sh will take care of the parts we already discussed.

#!/bin/bash

cd $HOME
source lubotrc

git clone https://github.com/lansingcodes/lubot.git

cd $HOME/lubot
sudo docker build -t lansingcodes/lubot .
cd $HOME
rm -rf $HOME/lubot

sudo docker rm -f lubot
sudo docker run -d --restart=always --name lubot \
  -e HUBOT_SLACK_TOKEN=$HUBOT_SLACK_TOKEN \
  -e TWITTER_LANSINGCODES_CONSUMER_KEY=$TWITTER_LANSINGCODES_CONSUMER_KEY \
  -e TWITTER_LANSINGCODES_CONSUMER_SECRET=$TWITTER_LANSINGCODES_CONSUMER_SECRET \
  -e TWITTER_LANSINGCODES_ACCESS_TOKEN=$TWITTER_LANSINGCODES_ACCESS_TOKEN \
  -e TWITTER_LANSINGCODES_ACCESS_TOKEN_SECRET=$TWITTER_LANSINGCODES_ACCESS_TOKEN_SECRET \
  -e GOOGLE_API_KEY=$GOOGLE_API_KEY \
  -e LUBOT_MEETUP_API_KEY=$LUBOT_MEETUP_API_KEY \
  -e TZ=$TZ \
  -e REDIS_URL=$REDIS_URL \
  lansingcodes/lubot

Deploying Lubot is now just as easy as it was with Heroku and he never has to sleep again.