Most Docker users are aware of the docker inspect command which is used to get metadata on a container or image, and may have used the -f argument to pull out some specific data, for example using docker inspect -f {{.IPAddress}} to get a container's IP Address. However, a lot of users seem confused by the syntax of this feature and very few people take full advantage of it (most people seem to pipe the output through grep, which is effective but messy). In this post, I'll look into how the -f argument works and show some examples of how to use it.
Put simply, the -f argument takes a Go template as input and runs it against the metadata for the given containers or images. The first problem is the phrase "Go template" is rather nebulous, at least to anyone inexperienced in Go — my first feeling was atavistic fear from the memory of C++ templates. Thankfully, Go templates has nothing in common with C++ templates or generics, and is in fact a template engine that takes a data source and a template pattern and combines the two to produce an output document. This idea should be very familiar to most web developers and common examples of template engines include Jinga2 (commonly used with Python and Flask), Mustache and JavaServer Pages. The concept is perhaps best explained by a simple example:
docker inspect -f 'Hello from container {{.Name}}' jenkins
(out) Hello from container /jenkins
As we can see, the argument to -f is a simple pattern (or template) that we want to apply to the metadata of container. In fact, we don't even have to do anything with the metadata:
docker inspect -f "This is a bit pointless" jenkins
(out) This is a bit pointless
But, if we learn a bit of Go templating magic, things that were once tricky become much simpler. For example, to find the names of all containers with a non-zero exit code:
docker inspect -f '{{if ne 0.0 .State.ExitCode }}{{.Name}} {{.State.ExitCode}}{{ end }}' $(docker ps -aq)
(out) /tender_colden 1
(out) /clever_mcclintock 126
(out) # <- empty line
(out) /grave_bartik 1
(Unfortunately Docker prints a new-line for each container, whether it matches the if or not).
Or to find the host directory for the volume /var/jenkins_home in my jenkins-data container:
docker inspect -f '{{index .Volumes "/var/jenkins_home"}}' jenkins-data
(out) /var/lib/docker/vfs/dir/5a6f7b306b96af38723fc4d31def1cc515a0d75c785f3462482f60b730533b1a
At the moment, both of these examples are probably a little hard to parse. There are a few basics you need to understand before you can get started with your own templates.
Directives
The {{ }} syntax is used for processing directives. Anything outside of the curly brackets will be output literally.
The . Context
A "." stands for the "current context". Most of the time this is the whole data structure for the metadata of the container, but it can be rebound in certain cirumstances, including using the with action:
docker inspect -f '{{.State.Pid}}' jenkins
(out) 6331
docker inspect -f '{{with .State}} {{.Pid}} {{end}}' jenkins
(out) 6331
You can always get the root context by using $. e.g.
docker inspect -f '{{with .State}} {{$.Name}} has pid {{.Pid}} {{end}}' jenkins
(out) /jenkins has pid 6331
Note that it is perfectly valid to use a "." by itself, the following will just output the full data completely unformatted:
docker inspect -f '{{.}}' jenkins
(out) ...
Data Types
The inspect data seems to consist of floats, strings and booleans. These can be tested and compared using various functions. At the moment, it seems that all numbers are floats, although Go templates do support integers, which would seem to be more appropriate in most cases (I've submitted an issue about this). Use double quotes when working with strings.
Values which appear as "null" appear to be not present in the data at all, and can't be compared:
docker inspect -f '{{.ExecIDs}}' jenkins
(out) NO VALUE HERE!
docker inspect -f '{{eq .ExecIDs .ExecIDs}}' jenkins
(out) FATA[0000] template: :1:2: executing "" at eq .ExecIDs .ExecIDs: error calling eq: invalid type for comparison
Data Structures
The inspect data uses maps and arrays. Maps are normally easy, you can just chain stuff together to access inner maps as we've seen already:
docker inspect -f '{{.State.ExitCode}}' jenkins
(out) 0
However, this isn't possible if the key for the map isn't in a suitable format (for example, if it uses a non alphanumeric character). If that's the case, you can use the index function as we did in the volumes example:
docker inspect -f '{{index .Volumes "/var/jenkins_home"}}' jenkins-data
(out) /var/lib/docker/vfs/dir/5a6f7b306b96af38723fc4d31def1cc515a0d75c785f3462482f60b730533b1a
You can also use index to get an array entry by number:
docker inspect -f '{{.HostConfig.Binds}}' jenkins
(out) [/var/run/docker.sock:/var/run/docker.sock /usr/bin/docker:/usr/bin/docker]
docker inspect -f '{{index .HostConfig.Binds 1}}' jenkins
(out) /usr/bin/docker:/usr/bin/docker
Functions
Apart from index, there are a few more functions that are likely to be useful. The logical functions and, or and not are available and operate largely as expected, returning a boolean result. Note that as they are functions, they cannot be used infix i.e:
docker inspect -f '{{and true true}}' jenkins
(out) true
Not:
docker inspect -f '{{true and true}}' jenkins
(out) FATA[0000] template: :1:2: executing "" at <true>: can't give argument to non-function true
The following comparison functions are available:
- eq (equals)
- ne (not equal)
- lt (less than)
- le (less than or equal to)
- gt (greater than)
- ge (greater than or equal to)
They can compare strings, floats or integers:
docker inspect -f '{{eq "abc" "abc"}}' jenkins
(out) true
docker inspect -f '{{ge 1 -1}}' jenkins
(out) true
docker inspect -f '{{lt 4.5 4.6}}' jenkins
(out) true
$ docker inspect -f '{{ne 4.5 4.5}}' jenkins
(out) false
But note that output types have to match, and numbers in Docker seem to be all floats, despite how they are printed:
docker inspect -f '{{eq "4.5" 4.5}}' jenkins
(out) FATA[0000] template: :1:2: executing "" at <eq "4.5" 4.5>: error calling eq: incompatible types for comparison
docker inspect -f '{{gt .State.Pid 1}}' jenkins
(out) FATA[0000] template: :1:2: executing "" at <gt .State.Pid 1>: error calling gt: incompatible types for comparison
docker inspect -f '{{gt .State.Pid 1.0}}' jenkins
(out) true
UPDATE: This seems to have been fixed; now both integers and floats will work correctly e.g:
docker inspect -f '{{gt .State.Pid 1}}' redis
(out) true
docker inspect -f '{{gt .State.Pid 1.0}}' redis
(out) true
(This was tested in Docker 1.7).
There is also a json function for generating JSON:
docker inspect -f '{{json .NetworkSettings.Ports}}' jenkins
(out) {"50000/tcp":null,"8080/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"}]}
So you can combine templates with the jq tool, which you may be more familiar with:
docker inspect -f json .State}}' jenkins-data | jq '.StartedAt'
(out) "2015-03-15T20:26:30.526796706Z"
Of course, the default output of docker inspect is also JSON, so this also works:
docker inspect jenkins-data | jq '.[] | .State.StartedAt'
(out) "2015-03-15T20:26:30.526796706Z"
There are a few more functions listed in the official Go documentation, but it weirdly seems to be missing the json function (which I learnt from Nathan LeClaire's blog) — if someone knows the reason for this, please let me know!
If Statements
Conditional statements can be handled with an if statement and normally use one of the previous comparison functions:
docker inspect -f '{{if eq .State.ExitCode 0.0}}
Normal Exit
{{else if eq .State.ExitCode 1.0}}
Not a Normal Exit
{{else}}
Still Not a Normal Exit
{{end}}' jenkins
(out) Normal Exit
Note the {{end}} statement is required to terminate the if. The else if and else parts are optional.
Conclusion
I think this covers most of the stuff you are likely to need when using templates with docker inspect, but there are more features available, such as using range to iterate over data, defining your own functions and using pipes.
I have yet to find a good, complete reference to using Go templates. The best I have found so far is a chapter from a the free e-book "Network programming with Go" by Jan Newmarch.
There is also the official Go documentation, but it is little terse and hard to follow, especially if you're not a Go programmer.