Using Docker inside Porter Bundles

Sometimes you need a hammer, and that hammer happens to be a whale 🐳. We all use containers as part of our pipeline: building images, running a one-off command in a utility container, spinning up a test environment to verify your application, or even more creative tasks that you have already containerized. Well now you can reuse all that hard work and logic from within your bundles!

Let’s walk through using my favorite container, docker/whalesay, in a bundle.

 _____________________
< Challenge Accepted! >
 ---------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/

Author the bundle

Writing a bundle that uses Docker has a few steps:

Here’s the full working example whalesay bundle for you to follow along with.

Require Docker

The user running the bundle, and Porter, needs to know that this bundle requires the local Docker daemon connected to the bundle. We have added a new section to porter.yaml for required extensions, and defined a new prototype extension that says that the bundle requires access to a Docker daemon:

required:
- docker

Optionally you can include that you also want the bundle to be run with --privileged which is intended for docker-in-docker scenarios:

required:
- docker:
    privileged: true

Install Docker

We can install the Docker CLI, or whatever is needed, into the bundle using a custom Dockerfile.

In our Dockerfile.tmpl below, I am installing the Docker CLI and copying my files into the bundle:

FROM debian:stretch

ARG BUNDLE_DIR
RUN apt-get update && apt-get install -y curl ca-certificates

ARG DOCKER_VERSION=19.03.8
RUN curl -o docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz && \
    tar -xvf docker.tgz && \
    mv docker/docker /usr/bin/docker && \
    chmod +x /usr/bin/docker && \
    rm docker.tgz

# Use the BUNDLE_DIR build argument to copy files into the bundle
COPY . $BUNDLE_DIR

The docker-compose mixin

Alternatively, we have also created a new mixin, docker-compose, that helps you quickly use Docker Compose from inside your bundle. It handles installing the Docker Compose CLI and gives you the following syntax:

docker-compose:
  description: "Start Test Services"
  arguments:
  - up
  - -d

We are going to focus on just using Docker for this blog post, but here is a full working example for how to use Docker Compose in a bundle.

Use Docker

Since we don’t have a docker mixin, I am using the exec mixin to call the Docker CLI. For ease of testing, I put my commands into a helper script, helpers.sh. This lets me test out my commands locally without running the entire bundle.

helpers.sh

#!/usr/bin/env bash
set -euo pipefail

whalesay() {
  docker run --rm docker/whalesay:latest cowsay $1
}

install() {
  whalesay "Hello World"
}

upgrade() {
  whalesay "World 2.0"
}

uninstall() {
  whalesay "Goodbye World"
}

# Call the requested function and pass the arguments as-is
"$@"

Now in my porter.yaml I have very straightforward calls to the functions defined in my helpers.sh script for each action:

porter.yaml

name: whalesay
version: 0.1.1
description: "An example bundle that uses docker through the magic of whalespeak"
tag: getporter/whalesay:v0.1.1

dockerfile: Dockerfile.tmpl

required:
- docker

parameters:
- name: msg
  description: a message for the whales to speak
  type: string
  default: "whale hello there!"
  applyTo:
  - say

mixins:
- exec

install:
- exec:
    description: "Install Hello World"
    command: ./helpers.sh
    arguments:
    - install

upgrade:
- exec:
    description: "World 2.0"
    command: ./helpers.sh
    arguments:
    - upgrade

say:
- exec:
    description: "Say Something"
    command: ./helpers.sh
    arguments:
    - whalesay
    - '"{{ bundle.parameters.msg }}"'

uninstall:
- exec:
    description: "Uninstall Hello World"
    command: ./helpers.sh
    arguments:
    - uninstall

After I have tested the bundle, I used porter publish to push it up to getporter/whalesay:v0.1.1.

Run that bundle

Now that the bundle is ready to use, the user running the bundle needs to give the bundle elevated permission with the Allow Docker Host Access setting. This is because giving a container access to the host’s Docker socket, or running a container with --privileged, has security implications for the underlying host, and should only be given to trusted containers, or in this case trusted bundles.

Let the whales speak!

$ porter install --tag getporter/whalesay:v0.1.1 --allow-docker-host-access
installing whalesay...
executing install action from whalesay (bundle instance: whalesay)
Install Hello World
 _____________
< Hello World >
 -------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/
execution completed successfully!

I can set the flag --allow-docker-host-access with the PORTER_ALLOW_DOCKER_HOST_ACCESS environment variable so that I don’t have to specify it for every command.

export PORTER_ALLOW_DOCKER_HOST_ACCESS=true

Now let’s see what else we can do with whalesay:

$ porter invoke whalesay --action=say --param 'msg=try it yourself!'
invoking custom action say on whalesay...
executing say action from whalesay (bundle instance: whalesay)
Say Something
 __________________
< try it yourself! >
 ------------------
    \
     \
      \
                    ##        .
              ## ## ##       ==
           ## ## ## ##      ===
       /""""""""""""""""___/ ===
  ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ /  ===- ~~~
       \______ o          __/
        \    \        __/
          \____\______/
execution completed successfully!

This is hopefully just the first step towards first class support for Docker and Docker Compose within Porter bundles, especially now that Docker Compose has an open specification. If you are interested in collaborating with us to take this further, please reach out on the porter or porter-docker-compose repositories!