At Spantree we periodically do hackathons, sometimes for internal projects, at other times for non-profit organizations. Every year on Martin Luther King, Jr. Day we held our annual hackathon for social good, which is always exciting.

This past year, we decided that we did not want to spend too much time upfront implementing a continuous deployment solution because we wanted to focus on more fun and productive aspects of the hackathon. So, I came up with the following super dirty solution to deploy an application that runs as a docker container in an AWS host.

The application is a small demo application written in clojure. We used CircleCI as the CI server and the application directory structure was somewhat like this:

$ tree -L 2
├── circle.yml
└── server
    ├── Dockerfile
    ├── dev
    ├── profiles_sample.clj
    ├── project.clj
    ├── resources
    ├── src
    ├── supervisord.conf
    └── test

The first step is create an AWS instance and install docker, the installation of docker and creation of an AWS instance are extremely well documented everywhere in the internet so I will not bore the audience with that.

The second step is to create a circleci.yml file with a configuration similar to this:

  build_dir: server
    - docker

  # Yes quick and dirty
  - echo "Skipping tests"

    branch: develop
      - lein uberjar
      - cp target/my-artifact.jar .
      - docker build -t spantree/myapp .
      - docker login -u $DOCKER_LOGIN -p $DOCKER_PASSWORD -e $DOCKER_EMAIL
      - docker push spantree/myapp
      - ../ $AWSHOST

The variables $DOCKER_LOGIN, $DOCKER_PASSWORD, $DOCKER_EMAIL and $AWSHOST are defined in the Web UI

The third step is to add a script with a content similar to the followint

#!/usr/bin/env bash

set -o xtrace  # trace what gets executed
set -o errexit # exit when a command fails.
set -o nounset # exit when your script tries to use undeclared variables

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

pushd "$__dir"


ssh -t ubuntu@"$server" '( docker kill $(docker ps -aq --filter "label=net.spantree.myapp=true" --filter status=running ) ; docker rmi -f spantree/myapp:latest ) ; docker run -d --label=net.spantree.myapp=true -p 3000:3000 spantree/myapp:latest'


Being that this simple project is not business critical we can afford to have a small downtime, we are not using any type of versioning. Instead, we want to have a deployment that just overwrites the previous one for fast iteration.

The script connects through ssh to the server and the proceeds to:

  1. docker kill $(docker ps -aq --filter "label=net.spantree.myapp=true" --filter status=running, kill all the running containers that are running and have the label "net.spantree.myapp=true"
  2. docker rmi -f spantree/myapp:latest remove the current image because we do not want to fill the filesystem with unused images.
  3. docker run -d --label=net.spantree.myapp=true -p 3000:3000 spantree/myapp:latest start a new version of the container.

It is probably obvious that this type of deployment is terrible for anything serious, but for something super quick it just works!