cloud
Keep Chef out of your Docker containers
Mar 14, 2015

Keep Chef out of your Docker containers

Keep Chef out of your Docker containers


Configuration Management tools can be of good use to provision your Docker containers. But you don’t want these tools to end up in your Docker images. Using Data Volume Containers and Docker Compose, we can do this and still have a slim image. Here is how you can do it with Chef.

TL;DR

Prepare a docker-compose.yml file with 3 services: chef, chefdata and app.

  • chef exposes /opt/chef as a volume
  • chefdata contains the Chef setup (client.rb, json, cookbooks) exposed on /tmp/chef
  • app uses the volumes from the former and runs Chef to provision itself
  • commit app container as an image.
  • Use your application image! :-)

Get an image with Chef installed

I went over to the Docker Hub to search for an image with Chef preinstalled, but exposed as a volume. The last part of my request was usually not fulfilled so I created an image myself: releasequeue/chef-client Since using floating versions like latest is not proper release management, I also tag my  images with the Chef version installed.

Prepare your application Chef setup

Create a folder containing your Berksfile for your application or service:

source "https://supermarket.getchef.com"

cookbook 'rq-web-api', git: 'git@github.com:releasequeue/rq-web-api-cookbook.git'

Retrieve the cookbook and its dependencies:

$ berks update
$ berks vendor chef/cookbooks

Cooking your cookbooks container

To run Chef succesfully, we need three parts:

  • a config file
  • a JSON data file
  • the set of cookbooks

We retrieved the cookbook in the previous step. In the chef folder, add a Chef configuration file and a JSON data file to complete the setup:

cookbook_path               ["/tmp/chef/cookbooks"]
ssl_verify_mode             :verify_peer
{
  "run_list": [
    "recipe[rq-web-api]"
  ]
}

With all these parts in place, we can create another data volume container containing all of the above. Put a Dockerfile in the chef folder with these contents:

FROM tianon/true

# Add the Chef cookbooks and runtime info to a volume container
COPY zero.rb first-boot.json /tmp/chef/
COPY cookbooks /tmp/chef/cookbooks/

# Create volumes
VOLUME /tmp/chef

Provisioning the application container

Now we are ready to create our application container. Add your application Dockerfile to the top-level project folder:

FROM ubuntu:14.04.2

# Configure your application here

# Startup commands
ENTRYPOINT /usr/bin/start-server

To get the 3 containers running together, we use docker compose with this input file:

# Build a data volume container with the chef cookbooks on it.
chefdata:
  build: chef

# Attach the image containing the Chef installation
chef:
  name: chef
  image: releasequeue/chef-client:12.0.3-1
  # Override default command to use this container as a volume container
  command: /bin/true

# Build the application container using Chef and the cookbooks on the attached volumes
rqwebapi:
  name: rqwebapi
  build: .
  volumes_from:
   - chefdata
   - chef
  environment:
    PATH: "/opt/chef/bin:$PATH"
  entrypoint: "/opt/chef/bin/chef-client"
  command: "-c /tmp/chef/zero.rb -z -j /tmp/chef/first-boot.json"

and run docker-compose up. You should see all three containers be created and Chef running from the last one. Once it has done, the containers all stop gracefully.

Baking the application image

We have to find the container id of our application container after running docker compose:

$ docker-compose up
Creating rqwebapidocker_chef_1...
Creating rqwebapidocker_chefdata_1...
Creating rqwebapidocker_rqwebapi_1...
Attaching to rqwebapidocker_rqwebapi_1
rqwebapi_1 | [2015-03-14T17:47:08+00:00] INFO: Started chef-zero at http://localhost:8889 with repository at /tmp/chef
rqwebapi_1 |   One version per cookbook
rqwebapi_1 |
...

$ docker ps -a
CONTAINER ID        IMAGE                               COMMAND                CREATED             STATUS                          PORTS               NAMES
74315e335f2b        rqwebapidocker_rqwebapi:latest      "/opt/chef/bin/chef-   3 minutes ago       Exited (0) About a minute ago                       rqwebapidocker_rqwebapi_1
e7eb9f4f206b        rqwebapidocker_chefdata:latest      "/true"                3 minutes ago       Exited (0) 3 minutes ago                            rqwebapidocker_chefdata_1
dbff11d1294b        releasequeue/chef-client:12.0.3-1   "/bin/true"            3 minutes ago       Exited (0) 3 minutes ago                            rqwebapidocker_chef_1

Let’s commit our application container to an image and tag it:

$ docker commit 74315e335f2b releasequeue/rq-web-api
18f53343b0cbe8f36005fee6878ad7fede7d202dd33e787b1713c039e9c3c516
$ docker push releasequeue/rq-web-api
...

Run your application

We have our provisioned image, so let’s start our application:

$ docker run -i -t releasequeue/rq-web-api
[2015-03-14 18:02:11] INFO  WEBrick 1.3.1
[2015-03-14 18:02:11] INFO  ruby 2.1.5 (2014-11-13) [x86_64-linux]
[2015-03-14 18:02:11] INFO  WEBrick::HTTPServer#start: pid=98 port=3000
...

Since the volumes of Chef or the cookbooks are no longer there, this image is free of any provisioning tools and much smaller as a result.

Happy container cooking!