How I Manage My Time


I see a lot of posts like this or this or this on HackerNews asking about time management

I was disorganised until my 30s.

Then I got organised and changed my life with:

  • JIRA
  • Notes in Git
  • Automating environment setup

What I’ve ‘got done’ since is listed below.

The Phone Call

About 6 years ago I missed something important at work. While working in ops, a customer had asked me to do something while I was busy, I’d moved onto another fire and clean forgot to do it.

Monday I got a phone call from her: “Ian, the payments went through over the weekend. Did you remember to switch off the cron job?”


She had to go and sort it out. I could only give my apologies to help her with the grief she was going to get.

I had the excuse that I was busy, that I’d been distracted, that I’ve got two kids and lots on.

But deep down I know something was wrong, that it was my fault. That I’d made a contract with someone and not honoured it.

It hurt.

A Chance

A few weeks later I was on a rare day off, and happened to be in a bookshop, and in this bookshop I saw Getting Things Done. I guess my unconscious led me to it, and though I’d always mocked books like this, I picked it up and scanned it. To my surprise, the advice was flexible, human, and practicable. I inhaled it, and my life changed from there.


What Happened?

It’s fair to say a lot in my life changed since that phone call 6 years ago. Since then, I’ve:

Also (and no less importantly), I’ve still got a job, and am a happily married father of two (I overlook the fact that my wife refuses to use JIRA).

I also generally feel less stressed, and more productive. I don’t know if all of it can be attributed to getting organised, but it certainly feels that way. Here’s what I did.


What I Did – JIRA

The first thing I did was set up a home JIRA instance. I’m a dinosaur, a control freak, and a cheapskate, so I buy the license and run it from home.

It doesn’t matter that it’s JIRA, the point is that all the things that impinge on my consciousness get put in here, and get out of my mind, giving me a clear head.

When the board gets too full, I start ditching things. Most of these are articles I intended to read, or little ideas my ardour has cooled on. That stops me getting overwhelmed.

Over time, I made a few tweaks that helped me be a little more efficient:

  • I created my own workflow that matched the way I thought about tasks:
    • Open/New
    • To-Do
    • Waiting for Something
    • Reminder Set
    • Closed
  • I set up a gmail account and linked it to JIRA so I could create tickets
  • I use mail this link to send links I’m interested in to my JIRA
  • I use send to kindle to mail articles directly to my kindle, so I can batch-read them asynchronously

There’s no separation between work and home tasks. Tax returns and birthday reminders sit right next to work tasks I want to stay on top of.

If it takes up space in your head, it goes in one place.

What I Did – Notes

That’s what I did for tasks – I had another frustration that I wanted to address. I would work on something, then either:

  • forget it
  • make notes and forget where they were
  • make notes, remember where they were, but couldn’t find them

I did something really simple to solve this: I created a git repo for all my notes.

imiell@Ians-Air-2:/space/git/work/notes ⑂ master +  ls | head

Then, I wrote some helper scripts. For example, creates a folder in this repo with some file pre-created:

if [[ $1 = '' ]] 
 echo folder name needed 
 exit 1 
mkdir -p ${NOTES}/$1 
mkdir -p ${LEARNING}/$1 
touch ${NOTES}/$1/cheat_sheet.asciidoc 
touch ${NOTES}/$1/links 
touch ${NOTES}/$1/git_repos 
touch ${NOTES}/$1/$1.asciidoc 
touch ${LEARNING}/$1/$1.asciidoc 
git add ${NOTES}/$1 
git add ${LEARNING}/$1

This creates a folder and adds it to git with a file for related links, a cheat_sheet, any related git_repos and a file that has the subject name in it.

Now if I pick up a new skill and then pick up my learning later, I can track my notes up to where I left off. I’ve used these notes to compiled blog posts like these.

I create asciidocs because I like the format, and it works well with vim.

I did try other methods (google docs, email, JIRA tickets), but this works best for me because:

  • It is available offline (git being a distributed note-taking tool)
  • It is text only
  • The current content is easily searched (grep)
  • A history is maintained that I can also search if needed
  • I can control/extend this system the way that makes sense to me

These things are important to me. They might be more or less important to you, so choose a tool accordingly. The vital thing is that it’s all in one place.

For example, here’s a link I literally just saw on Twitter while writing this: Organizing your life using GitHub

Work Environment Setup

Another constant niggle was setting up work environments. Like many people, I work on Linux servers, Mac laptops, and occasionally a Windows machine.

Mostly I dial into my home servers, but not infrequently I have to work on other servers.

To save time I wrote a ShutitFile to set up a server the way I like it. Here’s an abbreviated version of the full version:

# We assert here that we are running as root
SEND whoami

SEND lsb_release -d -s | awk '{print $1}'

# We assert here the user imiell was set up by the OS installation process
SEND cut -d: -f1 /etc/passwd | grep imiell | wc -l

# Install required packages
INSTALL openssh-server
INSTALL run-one


# Install docker
IF_NOT RUN docker version
 INSTALL apt-transport-https
 INSTALL ca-certificates
 INSTALL software-properties-common
 RUN curl -fsSL | sudo apt-key add -
 RUN add-apt-repository "deb [arch=amd64] $(lsb_release -cs) stable"
 RUN apt-get update
 RUN apt install -y docker-ce
# Add imiell to the docker user group
RUN usermod -G docker -a imiell

# Create space folder and chown it to imiell
RUN mkdir -p /space && chown imiell: /space
RUN mkdir -p /space/git

# Generate an ssh key
IF_NOT FILE_EXISTS /home/imiell/.ssh/
 RUN ssh-keygen
 # Note that the response to 'already exists' below prevents overwrite here.
 EXPECT_MULTI ['file in which=','empty for no passphrase=','Enter same passphrase again=','already exists=n']

# Log me in as imiell
USER imiell
# If it's not been done before, check out my dotfiles and set it up
IF_NOT FILE_EXISTS /home/imiell/.dotfiles
 RUN cd /home/imiell
 RUN git clone --depth=1 ~imiell/.dotfiles
 RUN cd .dotfiles
 RUN ./script/bootstrap
 EXPECT_MULTI ['What is your github author name=Ian Miell','What is your github author','verwrite=O']


ShutIt is a tool I wrote for simple automation of interactive sessions. Like traditional CM tools, but simpler.

Exceptions/Difficulties/Lessons Learned

This method works, for me, but there are limitations. I can’t keep all my work Confluence notes and JIRAs on my home JIRA or Git repo (not least for security reasons), so there is some separation between work and home notes and information.

That can’t be helped, but what’s more interesting are the downsides of this approach.

Is It Productive?

Sometimes it feels like managing this is a tax on my attention. I do wonder whether sometimes I’m just shuffling tickets around rather than tackling the hard stuff that happens over much longer time periods than individual tasks.

I Have to Remember to Let Go

Managing your workload more formally like this can make it hard to let go. There’s always something to do, but sometimes you need to take time out and smell the flowers. That’s when other good things can happen. Being productive is not everything, by a long chalk.

Or, as Lennon didn’t put it: Life is what happens when you are busy grooming your backlog.

Any Suggestions?

I’m always open to improving my workflow, so please let me know below if you have any suggestions.

Author is currently working on the second edition of Docker in Practice 

Get 39% off with the code: 39miell2


Ten Things I Wish I’d Known About Chef

1) Understand How Chef Works

This sounds obvious, but is important to call out.

Chef’s structure can be bewildering to newcomers. There are so many concepts that may be new to you to get to grips with all at once. Server, chef-client, knife, chefdk, recipe, role, environment, run list, node, cookbook… the list goes on and on.

I don’t have great advice here, but I would avoid doing too many theoretical tutorials, and just focus on getting an environment that you can experiment on to embed the concepts in your mind. I automated an environment in Vagrant for this purpose for myself here. Maybe you’ve got a test env at work you can use. Either way, unless you’re particularly gifted you’re not going to get conversant with these things overnight.

Then keep the chef docs close to hand, and occasionally browse them to pick up things you might need to know about.


2) A Powerful Debugger in Two Lines

This is less well known than it should be, and has saved me a ton of time. Adding these two lines to your recipes will give you a breakpoint when you run chef-client.

require 'pry'

You’re presented with a ruby shell you can interact with mid-run. Here’s a typical session:

root@chefnode1:~# chef-client
Starting Chef Client, version 12.16.42
resolving cookbooks for run list: ["chef-repo"]
Synchronizing Cookbooks:
 - chef-repo (0.1.0)
Installing Cookbook Gems:
Compiling Cookbooks...

Frame number: 0/22

From: /opt/chef/embedded/lib/ruby/gems/2.3.0/gems/chef-12.16.42/lib/chef/cookbook_version.rb @ line 234 Chef::CookbookVersion#load_recipe:

220: def load_recipe(recipe_name, run_context)
 221: unless recipe_filenames_by_name.has_key?(recipe_name)
 222: raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
 223: end
 225: Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
 226: recipe =, recipe_name, run_context)
 227: recipe_filename = recipe_filenames_by_name[recipe_name]
 229: unless recipe_filename
 230: raise Chef::Exceptions::RecipeNotFound, "could not find #{recipe_name} files for cookbook #{name}"
 231: end
 233: recipe.from_file(recipe_filename)
 => 234: recipe
 235: end
[1] pry(#<Chef::CookbookVersion>)> 

The last line above is a prompt from which you can inspect the local state, similar to other breakpoint debuggers.

CTRL-D continues the run.

See here for more.

3) Run Locally-Modified Cookbooks

I spent a long time being frustrated by my inability to re-run chef-client with a slightly modified set of cookbooks in the local cache (in /var/chef/cache...).

Then the chef client we were using was upgraded, and the
--skip-cookbook-sync option was available. This did exactly what I wanted: use the cache, but run the recipes in exactly the same way, run list and all.

The -z flag can do similar, but you need to specify the run-list by hand.
--skip-cookbook-sync ‘just works’ if you want to keep everything exactly the same and add a log line or something.

4) Learn Ruby

Ruby is the language Chef uses, so learning it is very useful.

I used Learn Ruby the Hard Way to quickly get a feel for the language.

5) Libraries

It isn’t immediately obvious how you avoid re-using the same code recipe after recipe.

Here’s a sample of a ‘ruby library’ embedded in a Chef recipe. It handles the figuring out of the roles of the nodes.

One thing to note is that because you are outside the Chef recipe, to access the standard Chef functions, you need to explicitly refer to its namespace. For example, this line calls the standard search​:, "role:rolename")

The library is used eg here. The library object is created:

server_info =

and then the object is referenced as items are needed, eg:

first_master = server_info.first_master
master_servers = server_info.master_servers

Note that the node object is passed in, so it’s visible within the library.

6) Logging and .to_s

If you want to ‘quickly’ log something, it’s easy:

log 'my log message do
  level :debug

and then run at debug level with:

chef-client -l debug

To turn a value into a string, try the .to_s function, eg:

log 'This is a string: ' + node.to_s do
  level :debug


7) Search and Introspection Functions

The ‘search’ function in Chef is a very powerful tool that allows you to write code that switches based on queries to the Chef server.

Some examples are here, and look like this:

graphite_servers = search(:node, 'role:graphite-server')

Similarly, you can introspect the client’s node using its attributes and standard Ruby functions.

For example, to introspect a node’s run list to determine whether it has the webserver role assigned to it, you can run:


This technique is also used in the example code mentioned above.

8) Attribute precedence and force_override

Attribute precedence becomes important pretty quickly.

Quite often I have had to refer to this section of the docs to remind myself of the order that attributes are set.

Also, force_override is something you should never have to use as it’s a filthy hack, but occasionally it can get you out of a spot. But it can’t override everything (see 10 below)!

9) Chef’s Two-Pass model

This can be the cause of great confusion. If the order of events in Chef seems counter-intuitive in a run, it’s likely that you’ve not understood the way Chef processes its code.

The best explanation of this I’ve found is here. For me, this is the key sentence:

This also means that any Ruby code in the file not explicitly delayed (ruby_blocklazynot_if/only_if) is run when the file is run, during the compile phase.

Don’t feel you need to understand this from day one, just keep it in mind when you’re scratching your head about why things are happening in the wrong order, and come back to that page.

10) Ohai and IP Addresses

This one caused me quite a lot of grief. I needed to override the IP address that ohai (the tool that gathers information about each Chef node and places in the node object) gets from the node.

It takes the default route’s interface’s IP address by default, but this caused me lots of grief when using Vagrant. force_override​ (see 8) above) doesn’t work because it’s an automatic ohai variable.

I am not the only one with this problem, but I never found a ‘correct’ solution.

In the end I used this hack.

Find the ruby file that sets the ip and mac address. Depending on the version this may differ for you:


Then get the ip address and mac address of the interface you want to use (in this case the eth1 interface:

IPADDR=$(ip addr show eth1 | grep -w inet | awk '{print $2}' | sed 's/\(.*\).24/\1/'""")
MACADDRESS=$(ip addr show eth1 | grep -w link.ether | awk '{print $2}'""")

Finally, use sed (or gsed if you are on a mac) to hard-code the ruby file that gets the details to return the information you want:

sed -i "s/\(.*${IPADDR} \).*/\1 \"\"/" $RUBBYFILE
sed -i "s/\(.*macaddress \)m.*/\1 \"${MACADDRESS}\"/" $RUBYFILE


Author is currently working on the second edition of Docker in Practice 

Get 39% off with the code: 39miell2



Vagrant and Ohai / Chef IP Address Hack



In vagrant, ohai returns the eth0 ip.

This is a PITA, since if you run clusters of Vagrant that use Chef (as I do), then Chef and ohai thinks the IP address of the node is always:

or whatever Vagrant attaches to the default interface (usually eth0).


It’s Not Just Me

Plenty of people appear to have this problem:

How to change ip address of node after added to chef server?

Chef and ohai retrieving a droplets private ip address


How to have chef use a different-ip?


I haven’t found a ‘proper’ solution for this. The most elegant (or least inelegant) one I could find involves:

  • Finding the ruby file involved in determining network information
  • Getting the IP address associated with the interface that you want Chef to ‘see’
  • (Optional) get the mac address associated with the interface that you want Chef to ‘see’
  • Hard-code the IP address and macaddress with these values directly in the network.rb file

Here’s an example:


IPADDR=$(ip addr show eth1 | grep -w inet | awk '{print $2}' | sed 's/\(.*\).24/\1/'""")

MACADDRESS=$(ip addr show eth1 | grep -w link.ether | awk '{print $2}'""")

sed -i "s/\(.*${IPADDR} \).*/\1 \"\"/;s/\(.*macaddress \)m.*/\1 \"${MACADDRESS}\"/" $RUBYFILE


Got a Better Way?

Please let me know!

Author is currently working on the second edition of Docker in Practice 

Get 39% off with the code: 39miell2

‘Towards a National Computer Grid’ – Electronic Computers, 1965

Recently I picked up this book on my travels:


This is the second edition (1970) of a book originally published in 1965.

It’s a fascinating insight into the state of computing 50 years ago, and remarkably prescient.

Here’s a few highlights that piqued my interest.

Getting programs right

This is a fascinating insight into how testing and debugging worked when hardware was a bigger part of the equation. Ever used a cathode ray tube to figure out what’s wrong with your program?

In the early years the usual procedure was to run the program in slow motion, one instruction at a time, and observe what happened. Computers were provided with a number of visual indicators – rows of lights or cathode ray tubes – which enabled the contents of some of the arithmetic, control or storage registers to be inspected. This practice – sometimes known as ‘peeping’ – was soon found to be intolerably slow. It can be speeded up by inserting stop instructions at suitable points, thus enabling the operator to restrict the slow motion to selected parts of the program. Those parts that are above suspicion can be run at full speed. Even with those improvements this procedure is far too prodigal of valuable machine time …

…or waited for an electric typewriter to tell you what the error was?

… most modern installations are provided with a variety of ingenious diagnostic aids … special diagnostic programs, some of which are usually held permanently in the computer store. Their function is to provide the programmer with information that is likely to help him detect, locate, and diagnose any errors in his program. Such information is usually printed as an error message on a line printer or electric typewriter …




Programming Languages Will Proliferate

The idea of a programming language then was still a relatively new one at that time. ‘High level’ programming languages had only recently come into being – COBOL was about as old then as GoLang is now!

In spite of recent attempts to design a ‘universal’ language – for example the PL/1 language – the existing bifurcation into mathematical and commercial languages, typified by ALGOL and COBOL respectively, is likely to persist for some time yet; an economic combination of sports car and delivery van is rather unlikely.

This implied that languages would proliferate for different purposes, as indeed they have.




Towards a National Computer Grid

It’s interesting to hear someone edge towards the idea of the Internet in an age before DNS, TCP/IP, SNAT or indeed the idea of networking in general.

The scheme envisages an hierarchical arrangement of subscriber terminals (‘remote stations in our terminology), multiplexor devices to concentrate and sort out incoming messages and so save data transmission costs, local area computers and regional computers, all linked together by a network of communication lines of varying data carrying capacity. It is proposed to make use of some of the long distance lines already provided for the telephone network



Moore’s Law

A logarithmic graph shows Moore’s Law, though it hadn’t been given that name yet. His 1965 paper was published in the same year as this book. Not only transistors, but magnetic core capacity was seen as growing at a similar rate.


Note that it was the bit, not the byte, kilobyte or megabyte that was the unit of choice at the time.

Multi-Tenancy, 1960s Style

The idea of computer systems that could run multiple programs simultaneously was a novel one. As was BASIC.

In the ‘MIT’ system twenty programs can be ‘active’ simultaneously. … The user of course is not aware of this swapping although he may realise what it is that makes the computer work more slowly than if he had it entirely to himself.



Computer Programming and ‘Libraries’

It was dawning on practitioners at the time that ‘constructing a computer program is like building a house’:


It is clearly a great boon for the programmer to have at his disposal a collection of standard subroutines which have been thoroughly tested in advance and known to work correctly.

I wonder what the author would have made of NodeJS libraries?




Computers at Work

Software had begun to ‘eat the world’ even 50 years ago. I met my wife in the early 2000s through a more modern equivalent of the ‘marriage bureau’ (yes, that was a thing, and I remember them), but it’s interesting to consider that law and medicine arguably haven’t really been revolutionised by computer technology yet (leaving aside hardware innovations).

The table below estimates that there were 70,000 computers worldwide in 1968. Microsoft was founded seven years later with the vision of ‘a computer on every desk and in every home’, which seems tame today when I have more computers on me now than pockets in my clothes.


Here is a rough estimate of the global distribution of computers in the middle of 1968:

North America                        46000
United Kingdom                        3000
Western and Central Europe           11000
USSR, Eastern Europe and China        5000
Other areas                           5000
                           Total     70000

Author is currently working on the second edition of Docker in Practice 

Get 39% off with the code: 39miell2



A Complete Chef Infrastructure on Your Laptop



An automated setup of a Chef infrastructure ready to develop on.

Can be used to:

  • Develop cookbooks offline
  • Train users in Chef
  • Simulate Chef ‘search’ code (the original impetus)
  • Test cookbooks

Known to work on Mac and Linux.



Here’s a video of it running on my Mac:




To run:

git clone --recursive
cd shutit-chef-env

and eventually you’ll be handed a terminal with this message:


You are on the host.

The chef node is chefnode1.vagrant.test

The chef workstation is chefworkstation.vagrant.test

The chef server is chefserver.vagrant.test


and you can vagrant ssh into any .one of the boxes and do your worst.

If you re-run ./ you will destroy the existing machines and they will be rebuilt.

By default a vagrant snapshot is performed on completion.


The code for this is here.


Ask me on twitter: @ianmiell



This is based on work in progress from the second edition of Docker in Practice 

Get 39% off with the code: 39miell2

Ten Things I Wish I’d Known Before Using Vagrant


One of the ironies of working a lot with Docker, Kubernetes, and OpenShift is that I’ve had to learn a lot about Vagrant and Virtualbox. Mostly I use it to spin up OpenShift clusters on my local machine, but I’ve had to poke into lots of corners to get various other things working too.

Here’s a list of things I wish I’d known about before I started.

1) Control CPU and Memory

If you do a vagrant init then this is buried in the comments of the resulting Vagrantfile.

 config.vm.provider "virtualbox" do |vb| 
   vb.memory = "1024" 

You can also set the CPUs used by the VM:

 config.vm.provider "virtualbox" do |vb| 
  vb.memory = "1024
  vb.cpus = "2"

2) Pattern for multiple machines

It’s not immediately obvious how you create multiple machines on a host in Vagrant.

You can use this as an example:

Vagrant.configure("2") do |config| 
 config.vm.define "chefserver" do |chefserver| = "centos/7" 
  chefserver.vm.hostname = "chefserver.vagrant.test" 
  chefserver.vm.provider :virtualbox do |v| 
   v.customize ["modifyvm", :id, "--memory", "1024"] 
 config.vm.define "chefworkstation1" do |chefworkstation1| = "ubuntu/xenial" 
  chefworkstation1.vm.hostname = "chefworkstation1.vagrant.test" 
  chefworkstation1.vm.provider :virtualbox do |v| 
   v.customize ["modifyvm", :id, "--memory", "512"] 

Each VM is defined with a name (eg chefworksation1 above) whose characteristics can then be accessed or modified with the dot notation (chefworkstation.vm.hostname). Characteristics of the ‘provider’ (which, by the way, just means: the thing under the hood that runs the VMs – typically VirtualBox or libvirt, but could even be not-VMs like Docker).

3) Increase Swap Space

This one was a leap forward for my Vagrant usage. By adding swap space to my VMs, I could increase the memory available to them without overcommitting memory on the host.

A short-cut script is available here.

4) Landrush

This is the single most important plugin for simulating clusters. It was a game-changer for me.

I can’t say it’s been without its challenges, especially as my networking skills (like many developers’) isn’t my ‘A’ game.

One big challenge I faced (for example) was how to make Docker play nice with it (contact me if you want details).

But the work this plugin does for me is invaluable.

5) vagrant global-status


When I run a lot of vagrant machines I often find myself reaching for this command.

$ vagrant global-status
id      name    provider   state   directory
df8bccb master1 virtualbox running /space/git/shutit-openshift-cluster/vagrant_run/shutit_openshift_cluster_v6lepQ
4074674 etcd2   virtualbox running /space/git/shutit-openshift-cluster/vagrant_run/shutit_openshift_cluster_v6lepQ

I also use it as the basis of a bunch of cleanup scripts (which occasionally cause me to swear as I remove important VMs by accident).

6) Get a GUI

OK, maybe I’m a dunce but it took me a good while before I realised I could get a GUI out of a vagrant up command

 config.vm.provider "virtualbox" do |vb| 
  # Display the VirtualBox GUI when booting the machine 
  vb.gui = true  
  # Customize the amount of memory on the VM: 
  vb.memory = "1024" 


7) Persistent Storage

Adding virtual disks to Vagrant machines is a complex affair for the uninitiated.

Fortunately there’s a plugin for that.

8) Vagrant snapshot

Re-building a lot of vagrant machines with the same commands?

Cache your builds with vagrant snapshot.

9) Syncing folders with host

If you find you run out of space on your Vagrant machines, you may want to just mount folders from the host:

config.vm.synced_folder "/space", "/space", 
  owner: "imiell", 
  group: "imiell"

Be aware that file permission mapping can be important in this context, so if things don’t work, read up on uids etc..

10) Clipboard control

You can control the clipboard using the modifyvm snippet.

 config.vm.provider "virtualbox" do |vb| 
  vb.gui = true 
  vb.memory = "4096" 
  vb.customize ['modifyvm', :id, '--clipboard', 'bidirectional'] 

This is based on work in progress from the second edition of Docker in Practice 

Get 39% off with the code: 39miell2



A Checklist for Docker in the Enterprise (Updated)



Docker is extremely popular with developers, having gone as a product from zero to pretty much everywhere in a few years.

I started tinkering with Docker four years ago, got it going in a relatively small corp (700 employees) in a relatively unregulated environment. This was great fun: we set up our own registry, installed Docker on our development servers, installed Jenkins plugins to use Docker containers in our CI pipeline, even wrote our own build tool to get over the limitations of Dockerfiles.

I now work for an organisation working in arguably the most heavily regulated industry, with over 100K employees. The IT security department itself is bigger than the entire company I used to work for.

There’s no shortage of companies offering solutions that claim to meet all the demands of an enterprise Docker platform, and I seem to spend most of my days being asked for opinions on them.

I want to outline the areas that may be important to an enterprise when considering developing a Docker infrastructure.




You will need a registry. There’s an open source one (Distribution), but there’s numerous offerings out there to choose from if you want to pay for an enterprise one.

  • Does this registry play nice with your authentication system?
  • Does it have role-based access control (RBAC)?

Authentication and authorization is a big deal for enterprises. While a quick and cheap ‘free for all’ registry solution will do the job in development, if you have security or RBAC standards to maintain, these requirements will come to the top of your list.

  • Does it have a means of promoting images?

All images are not created equal. Some are ‘quick and dirty’ dev experiments where ‘correctness’ is not a requirement, while others are intended for bullet-proof production usage. Your organisation’s workflows may require that you distinguish between the two, and a registry can help you with this, by managing a process via separate instances, or through gates enforced by labels.

  • Does it cohere well with your other artefact stores?

You likely already have an artefact store for tar files, internal packages and the like. In an ideal world, your registry would simply be a feature within that. If that’s not an option, integration or management overhead will be a cost you should be aware of.

Image Scanning

An important one.

When images are uploaded to your registry, you have a golden opportunity to check that they conform to standards. For example, could these questions be answered:

  • Is there a shellshock version of bash on there?
  • Is there an out of date ssl library?
  • Is it based on a fundamentally insecure or unacceptable base image?
  • Are the ‘wrong’ or out of date (based on your org’s standards) development libraries, or tools being used?

Static image analysers exist and you probably want to use one.

What’s particularly important to understand here is that these scanners are not perfect, and can miss very obviously bad things that end up within images. So you have to decide whether paying for a scanner is worth the effort, and what you want to get out of the scanning process.

In particular, are you wanting to:

  • Prevent malicious actors inserting objects into your builds?
  • Enforce company-wide standards on software usage?
  • Quickly patch known and standard CVEs?

These questions should form the basis of your image scanning evaluations. As usual, you will need to consider integration costs also.

Image Building

How are images going to be built? Which build methods will be supported and/or are strategic for your organisation? How do these fit together?

Dockerfiles are the standard, but some users might want to use S2I, Docker + Chef/Puppet/Ansible or even hand-craft them.

  • Which CM tool do you want to mandate (if any)
  • Can you re-use your standard governance process for your configuration management of choice?
  • Can anyone build an image?

Real-world experience suggests that the Dockerfile approach is one that is deeply ingrained and popular with developers. The overhead of learning a more sophisticated CM tool to conform to company standards for VMs is often not one they care for. Methods like S2I or Chef/Puppet/Ansible are more generally used for convenience or code reuse. Supporting Dockerfiles will ensure that you will get fewer questions and pushback from the development community.

It is also a useful way round limitations with whatever build method you support: ‘you can always do it yourself with a Dockerfile if you want’.

Image Integrity

You need to know that the images running on your system haven’t been tampered with between building and running.

  • Have you got a means of signing images with a secure key?
  • Have you got a key store you can re-use?
  • Can that key store integrate with the products you choose?

Image integrity is still an emergent area, even after a few years. There is generally a ‘wait and see’ approach from most vendors. Docker Inc. have done some excellent work in this area, but Notary (their open source signing solution) is considered difficult to install outside of their Datacentre product, and is a significant value-add there.

Third Party Images

Vendors will arrive with Docker images expecting there to be a process of adoption.

  • Do you have a governance process already for ingesting vendor technology?

You will need to know not only whether the image is ‘safe’, but also who will be responsible for updates to the image when required?

  • Can it be re-used for other Docker images?

There are potential licensing issues here! Do you have a way to prevent images available to be re-used by other projects/teams?

  • Do you need to mandate specific environments (eg DMZs) for these to run on?
  • Will Docker be available in those environments?

For example, many network-level applications operate on a similar level to network appliances, and require access that means it must be run isolated from other containers or project work. Do you have a means of running images in these contexts?


If you already have software development lifecycle (SDLC) processes, how does Docker fit in?

  • How will patches be handled?
  • How do you identify which images need updating?
  • How do you update them?
  • How do you tell teams to update?
  • How do you force them to update if they don’t do so in a timely way?

This is intimately related to the scanning solutions mentioned above. Integration of these items with your existing SDLC processes will likely need to be considered at some point.


Somehow information like database passwords need to be passed into your containers. This can be done at build time (probably a bad idea), or at run time.

  • How will secrets be managed within your containers?
  • Is the use of this information audited/tracked and secure?

Again, this is an emergent area that’s still fast-changing. Integrations of existing solutions like OpenShift/Origin with Hashicorp’s Vault exist (eg see here), and core components like Docker Swarm have secrets support, while Kubernetes 1.7 beefed up its security features recently.

Base Image?

If you run Docker in an enterprise, you might want to mandate the use of a company-wide base image:

  • What should go into this base image?
  • What standard tooling should be everywhere?
  • Who is responsible for it?

Be prepared for lots of questions about this base image! Developers get very focussed on thin images (a topic dealt with at length in my book Docker in Practice).

Security and Audit

The ‘root’ problem

By default, access to the docker command (and specifically access to the Docker UNIX socket) implies privileges over the whole machine. This is explicitly called out in the Docker documentation. This is unlikely to be acceptable to most sec teams in production.

You will need to answer questions like these:

  • Who (or what) is able to run the docker command?
  • What control do you have over who runs it?
  • What control do you have over what is run?

Solutions exist for this, but they are relatively new and generally part of other larger solutions.

OpenShift, for example, has robust RBAC control, but this comes with buying into a whole platform. Container security tools like Twistlock and Aquasec offer a means of managing these, so this might be factored into consideration of those options.

Monitoring what’s running, aka ‘runtime control’

A regulated enterprise is likely to want to be able to determine what is running across its estate. What can not be accounted for?

  • How do you tell what’s running?
  • Can you match that content up to your registry/registries?
  • Have any containers changed critical files since startup?

Again, this comes with some other products that might form part of your Docker strategy, so watch out for them.

Another frequently-seen selling point in this space is anomaly detection. Security solutions offer fancy machine learning solutions that claim to ‘learn’ what a container is supposed to do, and alert you if it appears to do something out of the ordinary, like connect out to a foreign application port unrelated to the application.

While this sounds great, you need to think about how this will work operationally – you can get a lot of false positives, and these may require a lot of curation – are you equipped to handle that?


When things go wrong people will want to know what happened. In the ‘old’ world of physicals and VMs there were a lot of safeguards in place to assist post-incident investigation. A Docker world can become one without ‘black box recorders’.

  • Can you tell who ran a container?
  • Can you tell who built a container?
  • Can you determine what a container did once it’s gone?
  • Can you determine what a container might have done once it’s gone?

In this context you might want to mandate the use of specific logging solutions, to ensure that information about system activity persists across container instantiations.

Sysdig and their Falco is another interesting and promising product in this area.



Application logging is likely to be a managed or controlled area of concern:

  • Do the containers log what’s needed for operations?
  • Do they follow standards for logging?
  • Where do they log to?

Container usage can follow very different patterns from traditional machine/VM deployments. Logging volume may increase, causing extra storage demands.


Containers can quickly proliferate across your estate, and this is where orchestration comes in. Do you want to mandate one?

  • Does your orchestrator of choice play nicely with other pieces of your Docker infrastructure?
  • Do you want to bet on one orchestrator, hedge with a mainstream one, or just sit it out until you have to make a decision?

Kubernetes seems to be winning the orchestration war. These days, not choosing Kubenetes (assuming you need to choose one) needs a good reason to back it up.

Operating System

Enterprise operating systems can lag behind the latest and greatest.

  • Is your standard OS capable of supporting all the latest features? For example, some orchestrators and Docker itself require kernel versions or packages that may be more recent than is supported. This can come as a nasty surprise…
  • Which version of Docker is available in your local package manager?

Docker versions sometimes have significant differences between them (1.10 was a big one), and these can take careful management to navigate through. There can also be differences between vendors’ Docker (or perhaps we should say ‘Moby‘ versions), which can be significant. RedHat’s docker binary calls out to RedHat’s registry before Docker’s, for example!


Dev environments

  • Developers love having admin. Are you ready to effectively give them admin with Docker?

There are options here to give developers a VM in which to run Docker builds locally, or just the docker client, with the server running elsewhere.

  • Are their clients going to be consistent with deployment?

If they’re using docker-compose on their desktop, they might resent switching to Kubernetes pods in UAT and production!


Jenkins is the most popular CI tool, but there’s other alternatives popular in the enterprise, such as TeamCity.

Docker brings with it many potential plugins that developers are eager to use. Many of these are not well-written safe, or even consistent with other plugins.

  • What’s your policy around CI/CD plugins?
  • Are you ready to switch on a load of new plugins PDQ?
  • Does your process for CI cater for ephemeral Jenkins instances as well as persistent, supported ones?



Shared Storage

Docker has in its core the use of volumes that are independent of the running containers, in which persistent data is stored.

  • Is shared storage easy to provision?

NFS servers have their limitations but are mature and generally well-supported in larger organisations.

  • Is shared storage support ready for increased demand?
  • Is there a need for shared storage to be available across deployment locations?

You might have multiple data centres and/or cloud providers. Do all these locations talk to each other? Do they need to?


Enterprises often have their own preferred Software Defined Networking (SDN) solutions, such as Nuage, or new players like Calico.

  • Do you have a prescribed SDN solution?
  • How does that interact with your chosen solutions?
  • Does SDN interaction create an overhead that will cause issues?


Having an aPaaS such as OpenShift or Tutum Cloud can resolve many of the above questions by centralising and making supportable the context in which Docker is run.

  • Have you considered using an aPaaS?
  • Which one answers the questions that need answering?


Cloud Providers

If you’re using a cloud provider such as Amazon or Google:

  •  How do you plan to deliver images and run containers on your cloud provider?
  • Do you want to tie yourself into their Docker solutions, or make your usage cloud-agnostic?

A Note on Choosing Solutions

Finally, it’s worth discussing two approaches that can be taken to solve your Docker needs. You can go all-in with a single supplier, or piece together a solution from smaller products that fulfil each (or subsets) of your requirements separately.

The benefits of going all-in with a single supplier include:

  • Single point of support
  • Less integration effort and overhead
  • Faster delivery
  • Greater commitment and focus from the supplier
  • Influence over product direction
  • Easier to manage

Single supplier solutions often demand payment per node, which can result in escalating costs that can lead to regret, or constraints on your architectures which might not be appreciated at first.

The benefits of ‘piecing together’ a solution include:

  • More flexible solutions can be delivered at different rates, depending on organisational need
  • A less monolithic approach can allow you to back out of ‘mistakes’ made in the product acquisition process
  • It can be cheaper in the long run, not least because…
  • You are not ‘locked-in’ to one supplier that can hold you to ransom in future


The enterprise Docker field is confusing and fast-changing. Developing a strategy that is cost-effective, safe, complete, adaptive, delivered fast, and coming without lock-in is one of the biggest challenges facing large-scale organisations today.

Best of luck!


This is based on work in progress from the second edition of Docker in Practice 

Get 39% off with the code: 39miell2