4 Ways to Launch Docker Containers

Introduction

Amazon just announced general availability of their Elastic Container Service providing a platform for launching Docker images in the cloud.  Let’s say your team is developing software on Windows and Mac OSX, but Docker requires the Linux kernel’s virtualization features to work. By now, you have likely discovered that Vagrant and/or boot2docker provide nice ways to run Linux on your local PC or Mac and provide a docker deployment platform.

But with so many different options available to configure how your Docker containers talk to each other, how do you get started?  In this article, we will take a look at a basic set of containers needed to stand up your own Docker registry (a must if you want to share your images in a place other than the public docker.io or paid private quay.io) and look at four different ways to launch your containers: 

Manual Approach

Let’s say you want to start up the official Docker registry, a docker-registry-ui and nginx to act as the front-end for these two containers.  Here’s how you could run these containers inside of your Linux host:

docker run --name registry -e SETTINGS_FLAVOR=local -e STORAGE_PATH=/registry -e SQLALCHEMY_INDEX_DATABASE=sqlite:///db/docker-registry.db -e SEARCH_BACKEND=sqlalchemy -p 5000:5000 registry
docker run -p 8080:8080 -e REG1=http://registry:5000/v1/ -e APP_CONTEXT=ui atcol/docker-registry-ui
docker run --link registry:registry --link registryui:registryui -v /nginx.conf:/etc/nginx/nginx.conf:ro -d -p 7000:80 nginx

For your reference, the nginx.conf forwards all traffic to  registry, except for request to /ui/ which get proxied to the docker-registry-ui.  The nginx.conf file looks like this:

worker_processes 1;
events { worker_connections 1024; }
http {
 upstream registry {
   server registry:5000;
 }
 upstream registryui {
   server registryui:8080;
 }
 server {
   listen 80;

   location / {
     proxy_pass http://registry;
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection 'upgrade';
     proxy_set_header Host $host;
     proxy_cache_bypass $http_upgrade;
   }

   location ~ ^/ui/(.*)$ {
     proxy_pass http://registryui;
     proxy_http_version 1.1;
     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection 'upgrade';
     proxy_set_header Host $host;
     proxy_cache_bypass $http_upgrade;
   }
 }
}

Multi-machine Docker Vagrant [ Vagrantfile ]

Vagrant has supported the ability to manage multiple machines for a while now and now also supports running Docker containers.  The Vagrant way of accomplishing the same thing as above is shown in the code snippet below.  This approach allows you to simply type:  vagrant up nginx –provider=docker in order to run your containers.  (Assuming you have VirtualBox and Vagrant installed).

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

config.vm.define "registry", autostart: true do |v|
  v.vm.provider "docker" do |d|
    d.name="registry"
    d.image = "registry"
    d.ports = [ "5000:5000" ]
    d.env = {
      SETTINGS_FLAVOR: "local",
      SEARCH_BACKEND: "sqlalchemy",
      STORAGE_PATH: "/registry",
      SQLALCHEMY_INDEX_DATABASE: "sqlite:///db/docker-registry.db"
    }
    d.volumes = ["/vagrant/db:/db/", "/vagrant/registry/"]
    d.vagrant_vagrantfile = 'host/Vagrantfile'
  end
end
config.vm.define "registryui", autostart: true do |v|
  v.vm.provider "docker" do |d|
    d.name="registryui"
    d.ports = [ "8080:8080" ]
    d.image = "atcol/docker-registry-ui"
    d.volumes = ["/vagrant/h2:/var/lib/h2"]
    d.env = {
      REG1: "http://172.17.42.1:7000/v1/",
      APP_CONTEXT: ui
    }
    d.link "registry:registry"
    d.vagrant_vagrantfile = 'host/Vagrantfile'
  end
end

config.vm.define "nginx" do |v|
  v.vm.provider "docker" do |d|
    d.build_dir= "./docker/nginx/"
    d.name="nginx"
    d.ports = [ "7000:7000" ]
    d.link "registry:registry"
    d.link "registryui:registryui"
    d.vagrant_vagrantfile = 'host/Vagrantfile'
  end
end

end

Docker Compose [ docker-compose.yml ] (fig’s successor)

Docker has now incorporated Fig into what it calls Docker Compose, which represents a third, and notably more industry standard way of launching your containers.

Before you run docker-compose on your Linux host, you need to install it with commands like this:

curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

Then, you can run ‘docker-compose run nginx‘ to start your containers while inside of the same directory that contains a docker-compose.yml like this:

registry:
  image: registry
  ports:
    - 5000:5000
  environment:
  SETTINGS_FLAVOR: local
  SEARCH_BACKEND: sqlalchemy
  volumes:
    - /vagrant/db:/tmp/registry-db/
    - /vagrant/registry:/tmp/registry/
registryui:
  image: atcol/docker-registry-ui
  ports:
    - 8080:8080
  environment:
    REG1: http://172.17.42.1:5000/v1/
    APP_CONTEXT: ui
  volumes:
    - /vagrant/db:/var/lib/h2
  links:
    - registry:registry
nginx:
  build: ./docker/nginx
  links:
    - registry
    - registryui
  ports:
    - "7000:80"

Multi-container Elastic Beanstalk [ Dockerrun.aws.json ]

If you plan to deploy your Docker images to AWS using its Elastic Beanstalk offering, the following JSON file is accepted by Elastic Beanstalk as a valid deployment descriptor.  When you use multi-container Elastic Beanstalk deployments, your applications get launched using the AWS Elastic Container Service. The volumes and containerDefinitions of this file contain the same JSON format that is accepted by ECS.  (For more information on the task definition format, see Amazon ECS Task Definitions in the Amazon ECS Developer Guide.)

{
  "AWSEBDockerrunVersion": 2,
  "volumes": [
    {
      "name": "nginx-conf",
      "host": {
        "sourcePath": "/var/app/current/nginx.conf"
      }
    },
    {
      "name": "registry-storage",
      "host": {
        "sourcePath": "/var/app/current/registry-storage"
      }
    },
    {
      "name": "registry-db",
      "host": {
        "sourcePath": "/var/app/current/registry-db"
      }
    },
    {
      "name": "registryui-db",
      "host": {
        "sourcePath": "/var/app/current/registryui-db"
      }
    }
  ],
  "containerDefinitions": [
    {
      "name": "registry",
      "image": "registry",
      "essential": true,
      "memory": 512,
      "portMappings": [
        {
          "hostPort": 5000,
          "containerPort": 5000
        }
      ],
      "environment": [
        {
          "name": "SETTINGS_FLAVOR",
          "value": "local"
        },
        {
          "name": "SQLALCHEMY_INDEX_DATABASE",
          "value": "sqlite:///db/docker-registry.db"
        },
        {
          "name": "STORAGE_PATH",
          "value": "/registry"
        },
        {
          "name": "SEARCH_BACKEND",
          "value": "sqlalchemy"
        }
      ],
      "mountPoints": [
        {
          "sourceVolume": "registry-db",
          "containerPath": "/db"
        },
        {
          "sourceVolume": "registry-storage",
          "containerPath": "/registry",
          "readOnly": false
        }
      ]
    },
    {
      "name": "nginx",
      "image": "nginx",
      "essential": true,
      "memory": 512,
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        }
      ],
      "links": [
        "registry",
        "registryui"
      ],
      "mountPoints": [
        {
          "sourceVolume": "nginx-conf",
          "containerPath": "/etc/nginx/nginx.conf",
          "readOnly": true
        }
      ]
    },
    {
      "name": "registryui",
      "image": "atcol/docker-registry-ui",
      "essential": true,
      "memory": 512,
      "portMappings": [
        {
          "hostPort": 8080,
          "containerPort": 8080
        }
      ],
        "links": [
          "registry"
        ],
      "mountPoints": [
        {
          "sourceVolume": "registryui-db",
          "containerPath": "/var/lib/h2"
        }
      ],
      "environment": [
        {
          "name": "REG1",
          "value": "http://a-cool-registry.elasticbeanstalk.com:5000/v1/"
        },
        {
          "name": "APP_CONTEXT",
          "value": "ui"
        }
      ]
    }
  ]
}

Fortunately, the engineers at Amazon have provided us with the ability to run this container configuration locally, without deploying to AWS Elastic Beanstalk.  These articles (1 and 2) provide details on how to install the awsebcli and use your Dockerrun.aws.json file to run Docker containers on your local Linux host.  A Vagrantfile like the one shown below will allow you to vagrant ssh into your Docker host, cd into the app/ folder, and run ‘eb local run’ to start the your containers!

Vagrant.configure("2") do |config|
  config.vm.hostname = "eb-registry"
  config.vm.network :forwarded_port, guest: 7000, host: 7000

  config.vm.box = "ubuntu/trusty64"
  config.vm.provision "docker"
  config.vm.provision "shell", inline:
    "ps aux | grep 'sshd:' | awk '{print $2}' | xargs kill"

  config.vm.provider "virtualbox" do |v|
    v.memory = 4096
    v.cpus = 2
    v.name = "eb-registry"
  end

  config.vm.provision :shell do |s|
    s.inline = <<-EOT
      apt-get -y install build-essential python-dev libevent-dev liblzma-dev python-setuptools w3m
      easy_install -U pip
      chmod a+w /usr/local
      usermod -a -G docker vagrant
    EOT
  end

  config.vm.provision :shell, privileged: false do |s|
    s.inline = <<-EOT
      if [ ! -x ~/.ebvenv/bin/eb ]; then
        curl -s https://s3.amazonaws.com/elasticbeanstalk-cli-resources/install-ebcli.py | python
      fi
      if [ ! -L /usr/local/bin/eb ]; then
        sudo ln -s ~/.ebvenv/bin/eb /usr/local/bin/eb
      else
    fi
   EOT
 end
 config.vm.synced_folder "./", "/home/vagrant/app"
end

As an added benefit, the ‘eb local run’ command also results in the more industry standard docker-compose.yml being created inside of a .elasticbeanstalk/ folder in the same directory as your Dockerrun.aws.json file.  This makes it very easy to share your container configs to other non-AWS platforms!

Clearly, the Dockerrun.aws.json format is significantly more verbose than the other options presented here, but if your goal is to deploy to Elastic Beanstalk in AWS, then developing and testing locally with ‘eb local run’ is a nice choice that eliminates the potential for deltas between local and your various AWS deployment environments.

By looking at four methods of composing the same four Docker containers, you can see how the various file formats relate to each other and decide for yourself which method works best for your team.