Unprivileged Docker Builds – A Proof of Concept

I work at a very ‘locked-down’ enterprise, where direct access to Docker is effectively verboten.

This, fundamentally, is because access to Docker is effectively giving users root. From Docker’s own pages:

First of all, only trusted users should be allowed to control your Docker daemon.

Most home users get permissions in their account (at least in Linux) by adding themselves to the docker group, which may as well be root. In Mac, installing Docker also gives you root-like power if you know what you’re doing.

Platform Proxies

Many Docker platforms (like OpenShift) work around this by putting an API between the user and the Docker socket.

However, for untrusted users this creates a potentially painful dev experience that contrasts badly with their experience at home:

  • Push change to git repo
  • Wait for OpenShift to detect the change
  • Wait for OpenShift to pull the repo
  • Wait for OpenShift to build the image
    • The last step can take a long time if the build is not cached – which can easily happen when you have lots of build nodes and you miss the cache

vs ‘at home’

  • Hit build on your local machine
  • See if the build works

What we really want is the capability to build images when you are not root, or privileged in any way.

Thinking about it, you don’t need privileges to create a Docker image. It’s just a bunch of files in a tar file conforming to a spec. But constructing one that conforms to spec is harder than simply building a tar, as you need root-like privileges to do most useful things, like installing rpms or apt packages.


I was excited when I saw Google announced kaniko, which claimed:

…we’re excited to introduce kaniko, an open-source tool for building container images from a Dockerfile even without privileged root access.


Since it doesn’t require any special privileges or permissions, you can run kaniko in a standard Kubernetes cluster, Google Kubernetes Engine, or in any environment that can’t have access to privileges or a Docker daemon.

I took that to mean it could take a Dockerfile and produce a tar file with the image as a non-privileged user on a standard VM. Don’t you?

I also got lots of pings from people across my org asking when they could have it, which meant I had to take time out to look at it.


Pretty quickly I discovered that kaniko does nothing of the sort. You still need access to Docker to build it (which was a WTF moment). Even if you --force it not to (which you are told not to), you still can’t do anything useful without root.


I’m still not sure why Kaniko exists as a ‘new’ technology when OpenShift already allows users to build images in a controlled way.

Rootless Containers

After complaining about it on GitHub I got some good advice.

There’s a really useful page here that outlines the state of the art in rootless containers for building, shipping and running.

It’s not for the faint hearted as the range of the technology required is somewhat bewildering, but there’s an enthusiastic mini community of people all trying to make this happen.

A Proof of Concept Build

I managed to get a build of a simple yum install on a centos:7 base image built as a completely unprivileged user using Vagrant and ShutIt to automate the build.

It shows the build of this Dockerfile as an unprivileged user (person) who does not have access to the docker socket (Docker does not even need to be installed for the run – it’s only used to build the proot binary, which could be done elsewhere):

FROM centos:7
RUN yum install -y httpd
CMD echo Hello host && sleep infinity

A video of this is available here and the code for this reproducible build is here. The interesting stuff is here.


Technologies Involved

I can’t claim deep knowledge of the technologies here (so please correct me where I’m wrong/incomplete), but here’s a quick run-down of the technologies used to achieve a useful rootless build.

  • runC

This allows us to run containers that conform to the OCI spec. Docker (for example) is a superset of the OCI spec of containers.

Although we use ‘runrootless’ to run the containers, runc is still needed to back it (I think – at least I had to install it to get this to work).

  • skopeo

Skopeo gives us the ability to take an OCI image and turn it into a Docker one later. It’s also used by orca-build (below) to copy images around while it’s building from Dockerfiles.

  • umoci

umoci modifies container images. It’s also used by orca-build to unpack and repack the image created at each stage. orca-build will

  • orca-build

orca-build is a wrapper around runC, and has some support for rootless builds. It uses these technologies:

It also takes Dockerfiles as input (with a subset of Docker commands).

  • runrootless

runrootless allows us to run OCI containers as part of each stage of the build, and in turn uses proot to allow root-like commands.

  • proot

proot allows you to create a root filesystem (such as is in a Docker container) without having root privileges. I suspected that proot was root by the back door using a setuid flag, but it appears not to be the case (which is good news).

This is required to do standard build tasks like yum or apt commands.

  • User namespaces

These must be switched on in the kernel, so that the build can believe it’s root while actually being an unprivileged user from the point of view of the kernel.

This is currently switched off by default in CentOS, but is easily switched on.

Try it

A reproducible VM is available here.

The kaniko work is available here.

See also:

Jessie Frazelle’s post on building securely on K8s

Special thanks to @lordcyphar for his great work generally in this area, and specifically helping me get this working.

If you like this post, you might like  Learn Git the Hard WayLearn Bash the Hard Way or Docker in Practice



Get 39% off Docker in Practice with the code: 39miell2


Learn Git Rebase Interactively

If you’ve ever wondered what a rebase is, then you could try and read up on it and understand it from the man pages. Many people struggle with this, and I was among them. The man pages have been improved lately, but are still daunting to the casual user.

A more effective way to learn is to be thrown into it at the deep end by being forced to do it at work. Unfortunately this can be a terrible way to learn as you are panicked about breaking something in some way you don’t understand.

So I came up with an interactive tutorial that you can run to learn what a rebase is.

Run The Tutorial

Running the tutorial has only two dependencies: pip and docker.

If you have those, then run:

pip install shutit # You might need sudo for this, depending on your pip setup
git clone https://github.com/ianmiell/git-rebase-tutorial
cd git-rebase-tutorial


Here’s a demo of it in action:


At any point, you can hit these button combinations (hit both at the same time, CTRL key first):


This submits your current state for checking.


Gives you a hint.


Restores the state of the tutorial to a pristine state at this stage.


Skips the current stage of the tutorial.


Quits the ShutIt session.


Raise an issue here.

If you can reproduce with ./run.sh -l debug and post the output, then that’s ideal.

If you like this post, you might like  Learn Git the Hard WayLearn Bash the Hard Way or Docker in Practice



Get 39% off Docker in Practice with the code: 39miell2


Terminal Perf Graphs in one Command

Sysstat and Graphs

If you have the sysstat package set up on your server, then you likely already know you can get historical CPU performance information with sar like this:

$ sar | head
Linux 4.15.0-10-generic (cage) 27/03/18 _x86_64_ (4 CPU)
00:00:01 CPU %user %nice %system %iowait %steal %idle
00:01:01 all 1.79  0.00  21.70   0.03    0.00   76.47
00:02:01 all 1.28  0.01  10.09   0.01    0.00   88.62
00:03:01 all 1.39  0.00  6.09    0.01    0.00   92.51
00:04:01 all 1.20  0.00  5.23    0.02    0.00   93.55
00:05:01 all 1.26  0.00  5.74    0.01    0.00   92.98
00:06:01 all 1.30  0.00  20.46   0.83    0.00   77.40
00:07:01 all 0.82  0.01  9.28    0.02    0.00   89.87

and you also probably know you can get the history of various metrics, not just CPU, eg disk IO, run queue size, and so on.

This info is great, but sometimes you just want a quick view of what’s going on.

You can faff around with some platform to try and get the information graphed in a sophisticated way if you have the time, skills and inclination. But mostly I just want a quick view with the least fuss.

So I used this script and bundled it into a container image to produce ascii graphs with one command. They look like this (click here to enlarge):

sar_report output. Click to enlarge

At first they’re hard to parse, but you quickly get used to them.

They’re great for quickly seeing when things went south, and what else went on at the time.


To run this on your host, do:

$ docker run \
  -e SAR_REPORT_DAY="${SAR_REPORT_DAY:-$(date +%d)}" \
  -e LINES=${LINES:-$(stty size | awk '{print $1}')} \
  -e COLUMNS=${COLUMNS:-$(stty size | awk '{print $NF}')} \
  -e TERM=${TERM:-xterm} \
  -v /var/log/sysstat:/var/log/sysstat:ro \

The -e arguments set the ‘day’ to report on (defaults to today’s day), and pass the terminal settings to

The -v flag mounts the /var/log/systat folder into the container (it is mounted read-only, to reduce any risk/fear of the Docker container messing up your host’s filesystem).

To change the day, set (eg) SAR_REPORT_DAY=01 in the terminal before running the command.

You’ll need Docker (and sysstat, of course) installed and running on your host for this to work out of the box.

Or you can run the command:

$ ./sar_report

from the repo‘s folder.

Suggestions? Problems?

The code is here and is a work in progress – please suggest changes/raise bugs etc. on Github.


If you like this post, you might like  Learn Git the Hard WayLearn Bash the Hard Way or Docker in Practice




Get 39% off Docker in Practice with the code: 39miell2












git log – the Good Parts

If you’re managing a complex git codebase with multiple developers, then you may well be using a tool like GitHub or BitBucket to delve into the history and figure out branch and merge issues.

These GUIs are great for providing a nice user interface for managing pull requests and simple histories and the like, but when the workflow SHTF there’s no substitute for using git log and its relatively little-known flags to really dig into the situation.

You’re going to run through this with me so that I know you’ve got it. Type the commands in bold to follow.

This is based on material from my book Learn Git the Hard Way, a free sample available here.

An Example Git Repository

Run this to download a fairly typical git repository that I work on:

$ git clone https://github.com/ianmiell/cookbook-openshift3-frozen
$ cd cookbook-openshift3-frozen

NB this is a copy of the original repo, ‘frozen’ here to provide stable output. 


git log

git log is the vanilla log command you are probably already familiar with:

$ git log

commit f40f8813d7fb1ab9f47aa19a27099c9e1836ed4f 
Author: Ian Miell <ian.miell@gmail.com>
Date: Sat Mar 24 12:00:23 2018 +0000


commit 14df2f39d40c43f9b9915226bc8455c8b27e841b
Author: Ian Miell <ian.miell@gmail.com>
Date: Sat Mar 24 11:55:18 2018 +0000


commit 5d42c78c30e9caff953b42362de29748c1a2a350
Author: Ian Miell <ian.miell@gmail.com>
Date: Sat Mar 24 09:43:45 2018 +0000


It outputs 5+ lines per commit, with date, author commit message and id. It goes in reverse time order, which makes sense for most cases, as you are mostly interested in what happened recently.

NOTE: output can vary depending on version, aliases,
and whether you are outputting to a terminal!
My version here was 2.7.4.


Most of the time I don’t care about the author or the date, so in order that I can see more per screen, I use --oneline to only show the commit id and comment per-commit.

$ git log --oneline
ecab26a JENKINSFILE: Upgrade from 1.3 only
886111a JENKINSFILE: default is master if not a multi-branch Jenkins build
9816651 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3
bf36cf5 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3


You might want more information than that, though, like which branch was that commit on? Where are the tags?

The --decorate flag provides this.

$ git log --oneline --decorate
ecab26a (HEAD -> master, origin/master, origin/HEAD) JENKINSFILE: Upgrade from 1.3 only
886111a JENKINSFILE: default is master if not a multi-branch Jenkins build
9816651 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3

More recent versions of git put this in the terminal by default, so things are improving for my fingers.

(Remember that your version might do --decorate by default fir git log when output goes to the terminal instead of a file).


$ git log --oneline --decorate --all
ecab26a (HEAD -> master, origin/master, origin/HEAD) JENKINSFILE: Upgrade from 1.3 only
886111a JENKINSFILE: default is master if not a multi-branch Jenkins build
9816651 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3
a1eceaf DOCS: Known issue added to upgrade docs
774a816 (origin/first_etcd, first_etcd) first_etcd
7bbe328 first_etcd check
654f8e1 (origin/iptables_fix, iptables_fix) retry added to iptables to prevent race conditions with iptables updates
e1ee997 Merge branch 'development'

Can you see what it does? If you can’t, compare it to --oneline above and dig around to figure it out.

That’s great, but what would be great is a visual representation of all those branches…


--graph gives you that visual representation, but in the terminal. While it might not look as slick as some git GUIs, it does have the benefit of being consistently viewed anywhere, and much more configurable to your specific needs.

And when you’re trying to piece together what happened on a 15-team project that doesn’t rebase, it can be essential…

$ git log --oneline --decorate --all --graph
* ecab26a (HEAD -> master, origin/master, origin/HEAD) JENKINSFILE: Upgrade from 1.3 only
* 886111a JENKINSFILE: default is master if not a multi-branch Jenkins build
* 9816651 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3
| * bf36cf5 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3
| |\ 
| | * 313c03a JENKINSFILE: quick mode is INFO level only
| | * 340a8f2 JENKINSFILES: divided up into separate jobs
| | * 79e82bc JENKINSFILE: upgrades-specific Jenkinsfile added
| * | dce4c71 Add logic for additional FW for master (When not a node)
* | | d21351c Update utils/atomic
|/ / 
* | 3bd51ba Fix issue with ETCD
* | b87091a Add missing FW for HTTPD
* a29df49 Missing (s)
* 51dff3a Fix rubocop


The above can be hard for the newcomer to parse, and there is little out there to guide you, but a few tips here can make it much easier to read.

The * indicates that there is a commit on the line, and the details of the commit (here the commit id, and first line of the comment) are on the right hand side.

The lines and position of the * indicate the lineage (or parentage) of each change. So, to take these three lines for example:

| * bf36cf5 Merge branch 'master' of github.com:IshentRas/cookbook-openshift3
| |\ 
| | * 313c03a JENKINSFILE: quick mode is INFO level only

The green pipes indicate that while the two changes listed here were going on, another branch had a gap between its two changes (9816651 and d21351c).

The blue line takes you to one parent of the bf36cf5 merge (what’s the commit id of the blue parent?), and the pink one goes to the other parent commit (313c03a).

It’s worth taking a bit of time to figure out what’s going on here, as it will pay dividends in a crisis later…

If you like this post, you’ll like my book Learn Git the Hard Way

It covers all this and much more in a similar style.



If you’re looking at the whole history of a project and want to get a feel for its shape before diving in, you may want to see only the significant points of change (ie the lines affected by -–decorate above).

These remove any commit that wasn’t tagged, branched (ie there’s no reference). The root commit is always there too.

$ git log --oneline --decorate --all --graph --simplify-by-decoration
* ecab26a (HEAD -> master, origin/master, origin/HEAD) JENKINSFILE: Upgrade from 1.3 only
| * 774a816 (origin/first_etcd) first_etcd
| * 654f8e1 (origin/iptables_fix) retry added to iptables to prevent race conditions with iptables updates
* 652b1ff (origin/new-logic-upgrade) Fix issue iwith kitchen and remove sensitive output
* ed226f7 First commit

Try tagging a specific commit not listed above, and then re-run the command.

File Info

Using --oneline can be a bit sparse, so --stat can give you useful information about what changed.

The number indicates the numbers of lines that were changed, with insertions represented by a + sign, and deletions by a -. There’s no concept of a ‘change’ to a line as such: the old line is deleted, and then the new one added even if only one character changed.

$ git log --oneline --decorate --all --graph --stat
* ecab26a (HEAD -> master, origin/master, origin/HEAD) JENKINSFILE: Upgrade from 1.3 only
| Jenkinsfile.upgrades | 2 +-
| 1 file changed, 1 insertion(+), 1 deletion(-)
* 886111a JENKINSFILE: default is master if not a multi-branch Jenkins build
| Jenkinsfile.full | 2 +-
| Jenkinsfile.upgrades | 2 +-
| 2 files changed, 2 insertions(+), 2 deletions(-)

If you find --stat hard to remember, then an alternative is to use --name-only, but with that you lose the information about numbers of changes to files.

Regex on Commits

This one’s also really handy. The -G flag allows you to search for all commits and only return commits and their files whose changes include that regexp.

This one, for example, looks for changes that contain the text chef-client

$ git log -G 'chef-client' --graph --oneline --stat
* 22c2b1b Fix script for deploying origin
| scripts/origin_deploy.sh | 65 ++++++++++++-----------------------------------------------------
| 1 file changed, 12 insertions(+), 53 deletions(-)
| * | 1a112bf - Move origin_deploy.sh in scripts folder - Enable HTTPD at startup
| | | origin_deploy.sh | 148 ----------------------------------------------------------------------------------------------------------------------------------------------------
| | | scripts/origin_deploy.sh | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| | | 2 files changed, 148 insertions(+), 148 deletions(-)
| * | 9bb795d - Add MIT LICENCE model - Add script to auto deploy origin instance
|/ / 
| | origin_deploy.sh | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| | 1 file changed, 93 insertions(+)

If you’ve ever spent ages searching through git log --patch output looking for a specific change this is a godsend…

The eccentrically-named --pickaxe-all gives you information about all files that changed in the commit, rather than just the ones that matched the regexp in the commit.

$ git log -G 'chef-client' --graph --oneline --stat --pickaxe-all

Try it out!


If you like this post, you’ll like my book Learn Git the Hard Way

It covers all this and much more in a similar style.


If you liked this post, check out:

Five Key Git Concepts Explained the Hard Way

Create Your Own Git Diagrams

Ten Things I Wish I’d Known About bash

A Non-Cloud Serverless Application Pattern Using Git and Docker

Five Key Git Concepts Explained the Hard Way

If you’ve ever read a git man page, you’ll know that trying to understand git can be an intimidating experience.

There’s even a git man page generator that produces joke git pages:

If <upstream> is not specified, the upstream configured in
branch.<name>.remote and branch.<name>.merge options will be used 
(see git-config(1) for details) and the --fork-point option is 
assumed. If you are currently not on any branch or if the current
branch does not have a configured upstream, the rebase will abort.
git-land-remote lands some applied remotes over the packed applied branches, and it is in various cases a possibility that a filter-branched error must prevent staged cleaning of some named stages.

One of the above extracts is a joke, one is real…

So here’s five core git concepts explained.

Hopefully after reading this the man pages will start to make more sense. If you’re confused by one I’ve missed, contact me to write it up for you (@ianmiell or LinkedIn).

This post uses the ‘hard way‘ method to teach the concepts by having you type out the commands and think through what’s going on, without having to worry about breaking anything.

I use the same method to teach git in my book Learn Git the Hard Way.    



1) Reference

Many will know this already, but I need to make sure you know it because it’s so fundamental.

A ‘reference’ is a string that points to a commit.

There are four main types of reference: HEAD, Tag, Branch, and Remote Reference


HEAD is a special reference that always points to where the git repository is.

If you checked out a branch, it’s pointed to the last commit in that branch. If you checked out a specific commit, it’s pointed to that commit. If you check out at a tag, it’s pointed to the commit of that tag.

Every time you commit, the HEAD reference/pointer is moved from the old to the new commit. This happens automatically, but it’s all going on under the hood.


A tag is a reference that points to a specific commit. Whatever else happens (and unlike the HEAD), that tag will stay pointed at the commit it was originally pointed at.


A branch is like a tag, but will move when the HEAD moves.

You can only be on one branch at a time.

Type out these commands and explain what’s going on. Take your time:

$ mkdir lgthw_origin
$ cd lgthw_origin
$ git init
$ echo 1 > afile
$ git add afile
$ git commit -m firstcommit
$ git log --oneline --decorate --all --graph
$ git branch otherbranch
$ git tag firstcommittag
$ git log --oneline --decorate --all --graph
$ echo 2 >> afile
$ git commit -am secondcommit
$ git checkout otherbranch
$ git log --oneline --decorate --all --graph
$ echo 3 >> afile
$ git commit -am thirdcommit
$ git log --oneline --decorate --all --graph

Now do it again and explain to someone else what’s going on.

Remote Reference

A remote reference is a reference to code that’s from another repository. See below for more on that…

2)  ‘Detached Head’

Now that you know what HEAD is, then understanding what a ‘detached head’ is will be much easier.

A ‘detached head’ is a git repository that’s checked out but has no branch associated with it.

Continuing from the above listing, type this in:

$ git checkout firstcommittag

You get that scary message:

Note: checking out 'firstcommit'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

git checkout -b <new-branch-name>

HEAD is now at 1b1499c... firstcommit

but if you follow the instructions:

$ git log --oneline --decorate --all --graph
$ git checkout -b firstcommitbranch
$ git log --oneline --decorate --all --graph

you can figure out what’s going on. There was a tag, but no branch at that commit, so the HEAD was detached from a branch.

3) Remote Reference

A remote reference is a reference to a commit on another git repository.

$ cd ..
$ git clone lgthw_origin lgthw_cloned
$ cd lgthw_cloned
$ git remote -v
$ git log --oneline --decorate --all --graph

The log graph looks different doesn’t it?

Compare that to the ​git log output in the other folder and think about how they differ. What word do you see multiple times in the output that you didn’t see before?

The cloned repo has its own copy of the branch (firstcommitbranch) and tag (firstcommit) because that’s where the repository’s HEAD was when you cloned it.

$ git branch -a

shows all the branches visible in this repository, both local and remote.

Compare that to the output of the same command in the original folder. How does it differ?

Now check out your local master:

$ git checkout master

and you get a message saying:

Branch master set up to track remote branch master from origin.
Switched to a new branch 'master'

So you’ve got a local reference master which ‘tracks’ the master in the remote repository. The local reference is master, and the remote reference is origin/master. Git assumed you meant your local master to track the remote master.

The two branches look the same, but they are linked only by the configuration of this repository.

$ cd ../lgthw_origin
$ git checkout master
$ echo origin_change >> afile
$ git commit -am 'Change on the origin'

Then go back to the cloned repository and fetch the changes from the origin:

$ cd ../lgthw_cloned
$ git fetch origin
git log --oneline --decorate --all --graph

Can you see what happened to your local master branch, and what happened to the origin’s? Why are they now separate?

Note that you didn’t git pull the change. git pull does a fetch and a merge, and we don’t want to confuse here by skipping steps and making it look like magic.

In fact, git pull is best avoided when you are learning git…

If you like this post, you’ll like my book Learn Git the Hard Way

It covers all this and much more in a similar style.


4) Fast Forward

Your git log graph should have looked like this:

* 90694b9 (origin/master) Change on the origin
* d20fc9a (HEAD -> master) secondcommit
| * 2e7ae21 (origin/otherbranch) thirdcommit
* 6c14f2f (tag: firstcommittag, origin/firstcommitbranch, origin/HEAD, firstcommitbranch) firstcommit

(Your ids may differ from the above – otherwise it should be the same.)

Now, do you see how the Change on the origin commit is not branched from your local HEAD/master commit secondcommit – it’s in a ‘straight line’ from the firstcommit tag?

That means that if you ‘merge’ origin/master into your local master, git can figure out that all it needs to do is move the HEAD and master reference to where the origin/master branch is and its ‘merge’ job is done.

$ git merge origin/master
$ git log --oneline --decorate --all --graph

This is all a ‘fast forward’ is: git saw that there’s no need to do any merging, it can just ‘fast forward’ the references to the point you are merging to. Or if you prefer, it just moves the pointers along rather than create a new merge commit.

We just did a git pull, by the way. A git pull consists of a git fetch and a git merge. Breaking it down into these two steps helps reduce the mystery of why things can go wrong.

As an exercise, after finishing this article do the whole exercise again, but make a change to both origin/master and master and then do the fetch and merge to see what happens when a fast-forward is not possible.

5) Rebase

master and origin/master are now in sync, so now run these commands to see what a rebase is:

$ cd ../lgthw_origin 
$ git status
$ echo origin_change_rebase >> afile 
$ git commit -am 'origin change rebase' 
$ git log --oneline --decorate --all --graph 

OK so far? You’ve made a change on master on the origin repo:

$ cd ../lgthw_cloned 
$ echo cloned_change_rebase >> anewfile 
$ git add anewfile 
$ git commit -m 'cloned change rebase in anewfile' 
$ git log --oneline --decorate --all --graph 
$ git fetch origin 
$ git log --oneline --decorate --all --graph 
$ git rebase origin/master 
$ git log --oneline --decorate --all --graph

Can you see what’s happened?

If not, have a close look at the last two git log outputs.

That’s what a rebase is – it takes a set of commits and moves (or ‘re-bases’) them to another commit.


If you liked this post, you’ll like my book Learn Git the Hard Way

It covers all this and much more in a similar style.


If you liked this post, you might also like these:

Create your own Git diagrams

A Git Serverless Pattern

Power Git Log Graphing

Interactive Git Rebase and Bisect Tutorials


Create Your Own Git Diagrams


Ever wondered how to create your own git diagrams?

You know, the ones that look like this?


I’ve created a Docker image to allow you to easily create your own.

$ docker pull imiell/gitdags

The git repo is here.

How To Run

The examples folder is a good place to start.

A good way to get started is to run this:

$ git clone https://github.com/ianmiell/gitdags
$ cd gitdags/examples
$ docker run -v $(pwd):/files imiell/gitdags /convert_files.sh

It will convert the *.tex (LaTeX) files into example .png images more or less complex than the one at the top.

If you want to learn more about git, read my book Learn Git the Hard Way, available at $8:


How To Make Your Own

To show you how to make your own images, let’s break down this example:

      % Commit DAG 
      \gitDAG[grow right sep = 2em]{ 
        A -- { 
      % Branch 
        {experimental} % node name and text 
        {above=of C} % node placement 
        {C} % target 
        {master} % node name and text 
        {below=of B} % node placement 
        {B} % target 
      % HEAD reference 
        {below=of master} % node placement 
        {master} % target     

Breaking it down into chunks, the content is framed by what is more or less boilerplate:


The subcaption package might be used by a more advanced diagram, but is not necessary to this particular one.

Next, the nodes and their links are specified in a \gitDAG section:

\gitDAG[grow right sep = 2em]{ 
   A -- { 

The nodes are linked by a simple pair of dashes. The arrows are put in for you.

The curlies indicate a division into branches, where each line represents one line of development.

If you want to merge two branches together, then you can give them the same name, like this:

A -- { 
  C -- D, 
  B -- D, 

You can ‘grow’ the graph down, up, or left (as well as right, above), and make the separation larger or smaller by changing the value of 2em.

You can also ‘fade them out’ by marking them as ‘unreachable’:

[nodes=unreachable] D -- E

There are four main types of ‘external pointer’ node:


The comments on the \gittag example here are mostly self-explanatory, and apply to all four (apart from for \gitHEAD – see below):

  [v0p1]       % node name
  {v0.1}       % node text
  {above=of A} % node placement
  {A}          % target

The ‘target’ line refers to where the arrow points, and the ‘node placement’ line refers to where the ‘arrow node’ is positioned – it can be above=of or below=of as well as left=of or right=of.

gitHEAD ignores the node text, and just puts HEAD as the text.

Other types of node are available, but there’s no documentation on them that I can find, and literally no mention of them on GitHub anywhere outside the original source. I may try and figure them out later.


This work is based on the great work of Chris Freeman here.

And of course the original gitdags work here.

See also here for a StackOverflow discussion.

Without the above I’d have been floundering in a sea of LaTeX ignorance.

If you want to learn more about git, read my book Learn Git the Hard Way, available at $8:


If you liked this post, you might also like these:

Five Key Git Concepts Explained the Hard Way

Ten More Things I Wish I’d Known About bash

Project Management as Code with Graphviz

A Non-Cloud Serverless Application Pattern Using Git and Docker

Power ‘git log’ graphing


Five Things I Did to Change a Team’s Culture

Culture – Be Specific!

People often talk about culture being the barrier to adoption of DevOps, but they are rarely specific about this.

This was succinctly put by Charity Majors here:


What to Do?

Here I discuss a few things I did to try and change a culture a few years ago in a demoralised and dysfunctional centralised IT team that I managed following the sudden departure of the IT Director.

Whether it worked or not I don’t know – you’d have to ask the team (and I was poached a couple of months after I started), but I felt a big difference pretty quickly.

1) Get on the Floor

The first thing I did was spend two weeks doing triage of incoming requests. This had a few useful effects.

  • I saw one of the two main pipelines of work into the team

The IT team was working on 1) Requests received via tickets and 2) Out-of-band requests from management (“Can you just implement a new video conferencing system? Thanks.”)

Getting a handle on 1) was the shortest path to get savings fast, so I started there. Number 2) was going to be a tougher nut to crack (mostly finding ways to say ‘no’ without getting fired). Improving 1) would help with 2).

  • I discovered the triage process was broken

The triage process was not serving its purpose. It had been given to a weaker member of staff because no-one else wanted to do it, and he was not adding any value by thinking about what was being presented to him.

I put some controls into the process from above and moved the duty around the team.

  • The ticket count dropped by 75%

I cut the open tickets by 75% in a week by deduplicating and applying simple call queue techniques to the backlog. Dropping that number didn’t drop the work by 75% (probably more like 30-40%), but it improved morale and focus significantly. I also implemented some of the techniques talked about here to reduce running costs.

  • I was seen as someone who wanted to get involved

While I had to be careful not to get into the weeds, by getting my hands dirty my credibility with the team grew.

More importantly, I could start to challenge them when I didn’t buy what they were saying. They had become used to pulling out certain excuses for failure. This wasn’t because there were not good reasons, but because they had felt ignored for so long they had stopped trying to engage openly. That culture needed to change, and being able to argue from within was critical to achieving that.

2) Move People to Other Teams

One of the things I’m absolutely certain of is that a critical feature of effective complex organisations is that they make people do all the jobs.

Only when people have seen things from all angles can they make real and effective adaptations to changing circumstances or effect real change within a complex organisation.

There’s an incredibly powerful talk here by John Allspaw where he discusses how the Navy does this to help solve the challenges aircraft carriers face:

‘So you want to understand an aircraft carrier. Imagine a busy day, and you shrink San Francisco airport to one short runway, one ramp, and one gate. Make planes take off and land at the same time at half the present time interval, rock the runway from side to side, and require that everyone that leaves returns that same day. Make sure the equipment is so close to the edge of the envelope that it’s fragile, then turn off the radar to avoid detection, impose strict controls on radios, fuel the aircraft in place with their engines running, have enemies in the air and scatter live bombs and rockets around. Now wet the whole thing down with salt water and oil and man it with 20 year olds, half of whom have never seen a plane close up. Oh, and by the way: try not to kill anyone.

(See 19 minutes in for this part of the talk.)

I made the IT staff go and sit with the developers for a couple of weeks as soon as I could. The resistance I got to this idea, even among the keen ones, was deeply surprising to me. There was a profound tendency to put others on a pedestal and fear humiliation by going outside their comfort zone.

The results, however, were immediate. Relations between teams improved dramatically, and areas of tension that had been bubbling for years got resolved as IT staff had seen things ‘from the other side’, which changed their view of why blockers should be removed, and – equally important – how they could be removed by more creative means that the ‘other side’ could not see. Once staff saw the drivers of frustration, they could implement solutions for the problem itself, and not necessarily what was being asked for.

3) Remove Bad Influences

People don’t like to talk about this, but one of the most effective ways to change culture is to fire people.

There’s a probably apocryphal story about an Orson Welles trick, where he would get a stooge to show up to work on the first day on a shoot, do something Welles didn’t want, and Welles would fire him.

The message to the crew would be unambiguous: my way, or the highway.

That’s obviously an extreme example, but I’ve seen the powerful effects of removing people who are obstructing change. That doesn’t mean you don’t follow due process, or give people clear warnings, or help them to mend their ways, but nothing sends a message of ‘I disapprove of this bad behaviour’ better than dealing with it firmly.

And check point 6 on this deck about Jeff Bezos’ mandate to change the way Amazon worked:

Anyone who doesn’t do this will be fired.

One of the first questions I generally ask myself when considering the latest attempt from on high to bring cultural change to my group is: what changes here that gets me fired?

4) Take Responsibility for Hiring

As with firing, who comes into the team is vital. I was shocked to discover that it was not considered standard to have the overall manager personally vet new hires.

While I didn’t know my Active Directory from my LDAP, I did know the difference between a bright young thing and an irritating know-all, so I took responsibility for any new hires. I deferred to my colleagues on knowledge calls, but that was not often a deciding factor either way. Far more important was how useful they would make themselves.

5) Take Responsibility for Training

There’s a great quote from Andy Grove, founder of Intel about training:

Training is the manager’s job. Training is the highest leverage activity a manager can do to increase the output of an organization. If a manager spends 12 hours preparing training for 10 team members that increases their output by 1% on average, the result is 200 hours of increased output from the 10 employees (each works about 2000 hours a year). Don’t leave training to outsiders, do it yourself.

And training isn’t just about being in a room and explaining things to people – it’s about getting in the field and showing people how to respond to problems, how to think about things, and where they need to go next. The point is: take ownership of it.

I personally trained people in things like Git and Docker and basic programming whenever I got the chance to. This can demystify these skills and empower your staff to go further. It also sends a message about what’s important – if the boss spends time on triage, training and hiring, then they must be important.

Anything else?

What have you done to change culture in a group? Let me know.


If you want to learn more about bash, read my book Learn Bash the Hard Way, available at $5:


Or my book on Docker: