Skip to main content
Docker

How to Use Docker Inspect Command

Docker inspect command allows you to get information about Docker containers, volumes and networks. Learn to use this command.

Debdut Chakraborty

One of the essential Docker commands is docker inspect. It lets you extract information about various docker objects, knowing how to use it is something EVERYONE should know about.

In case you were wondering, Docker objects or resources are simply things like containers, volumes, networks etc.

The main strength of inspect comes from its formatting capabilities.

For example, you can extract the IP address of a running container by inspecting it, and formatting in a specific way.

➟ docker container inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx
172.17.0.2

Docker uses go-templates for formatting its output.

In this article, first I'll go through the basics of Docker inspect command, and then I'll focus on how to format the output to your specific needs.

What does Docker inspect do?

Inspect provides you with a bunch of metadata about all the different objects managed by docker. The kind of information varies from object to object.

For example, if you inspect a volume, you'll get information related to when it was created, the volume driver in use, location in the host filesystem, labels etc.

If it is a network that you're inspecting, then you'll get things like its subnet, gateway, connected containers and their IP addresses, labels and other information.

To understand better what inspect provides for a given object, I recommend you run the commands and see for yourself.

What are the objects that can be inspected?

In docker, an object or object type is all the constructs that are controlled by docker. This includes the following:-

  1. Containers.
  2. Images.
  3. Networks.
  4. Volumes.
  5. Contexts.
  6. Plugins.
  7. Nodes (swarm object).
  8. Services (swarm object).
  9. Secrets (swarm object).
  10. Configs (swarm object).

Using Docker inspect command

There are two ways you can use the inspect sub-command.

  1. docker inspect [object] [options]
  2. docker [object_type] inspect [object] [options]

The second method is the one you should be using always. The inspect sub-command provides a pretty formatted JSON output, I'll get into that in a moment.

Create a volume named unique.

docker volume create unique

Now create a network named the same, unique.

docker network create unique

Now let's try to inspect the object named unique using the first syntax.

docker inspect unique

My output:-

➟ docker inspect unique
[
    {
        "Name": "unique",
        "Id": "09a7e2163ee058b1057d95599f764d571ec6a42a5792803dc125e706caa525b0",
        "Created": "2021-05-07T15:47:20.341493099+05:30",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

In my system, as you can see inspect inspected the network, but what if I intended to inspect the volume?

This is the problem with docker inspect, when you have two different objects named the same, you can't just use docker inspect [object_name]. To inspect exactly what you want, you'll need to either use the ID of the object, or use the --type=[object_type] option. You can write the previous command with the --type option like so:-

docker inspect --type=volume unique

Although this works, I believe this is unnecessary since we already have the other syntax. You can just use the object specific sub-command like I'm doing here:-

docker volume inspect unique

It is less to write, and a lot easier to read.

Some useful Docker inspect command examples

In this section, I'm going to record a list of common queries and what the relevant inspect command would look like to get those information.

Container queries

For the examples, I'll have a sample nginx container running, and all the commands will be run against this running container. The command I used to run this container:-

docker container run \
	--rm --name nginx \
    -p target=80,published=127.0.0.1:8081,protocol=tcp \
    -p target=80,published=[::1]:8081,protocol=tcp \
    -e ENV_VAR=somevalue \
    -e ENV_VAR2=linux \
    -v $PWD:/mnt:ro \
    -v /tmp:/tmp:ro \
    -d nginx

1. ID of a container by name

You can get the ID of the container using the following command:-

docker container inspect -f '{{.Id}}' [container_name]

Example:-

➟ docker container inspect -f '{{.Id}}' nginx
0409779fc2d976387170d664a6aed5ee80a460f8a8dd02c44a02af97df0bb956

2. Container's main process

The main container process is basically ENTRYPOINT + CMD. The printf command will format the output to give the required information.

docker container inspect -f '{{printf "%s " .Path}}{{range .Args}}{{printf "%s " .}}{{end}}' [container_name|id]

Example:-

➟ docker container inspect -f '{{printf "%s " .Path}}{{range .Args}}{{printf "%s " .}}{{end}}' nginx
/docker-entrypoint.sh nginx -g daemon off;

3. Listing the port bindings

The following command lists all the container-to-host port bindings.

docker container inspect -f '{{range $target, $published := .NetworkSettings.Ports}}{{range $published}}{{printf "%s -> %s:%s\n" $target .HostIp .HostPort}}{{end}}{{end}}' [container_name|id]

Example:-

➟ docker container inspect -f '{{range $target, $published := .NetworkSettings.Ports}}{{range $published}}{{printf "%s -> %s:%s\n" $target .HostIp .HostPort}}{{end}}{{end}}' nginx
80/tcp -> ::1:8081
80/tcp -> 127.0.0.1:8081
You can achieve the same with docker container port command.

4. Listing its IP addresses

A container can be connected to multiple networks, instead of printing one of those many IP addresses, you can print all of those IP addresses with this command.

docker container inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [container_name|id]

Example:-

➟ docker container inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx
172.17.0.2

5. Listing the environment variables

You can list the environment variable of a container as well.

docker container inspect -f '{{range .Config.Env}}{{printf "%s\n" .}}{{end}}' [container_name|id]

Example:-

➟ docker container inspect -f '{{range .Config.Env}}{{printf "%s\n" .}}{{end}}' nginx
ENV_VAR=somevalue
ENV_VAR2=linux
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NGINX_VERSION=1.19.10
NJS_VERSION=0.5.3
PKG_RELEASE=1~buster

6. Listing the volumes/bind mounts along with the mode

The following command prints the bind mounts in this format, "[source] -> [destination], mode: [mode]".

docker container inspect -f '{{range .Mounts}}{{printf "%s -> %s, mode: %s\n" .Source .Destination .Mode}}{{end}}' [container_name|id]

Example:-

➟ docker container inspect -f '{{range .Mounts}}{{printf "%s -> %s, mode: %s\n" .Source .Destination .Mode}}{{end}}' nginx
/home/debdut -> /mnt, mode: ro
/tmp -> /tmp, mode: ro

Volume queries

There's not much to inspecting a volume except for knowing the host location, which is in data-dir/volumes. You can get that information with the following command:-

docker volume inspect -f '{{.Mountpoint}}' [volume_name|id]

Example:-

➟ docker volume create unique 
unique
~ 
➟ docker volume inspect -f '{{.Mountpoint}}' unique 
/var/lib/docker/volumes/unique/_data

Network queries

There are two queries that I personally find myself going for frequently, one is knowing a networks subnet and all the containers that are connected to that network and the IPs associated with them.

For this I created a simple network with the docker network create unique command.

1. Getting the subnet

To get the subnet, use the following command:-

docker network inspect -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}' [network_name|id]

Example:-

➟ docker network inspect -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}' unique 
172.21.0.0/16

2. Listing the containers connected along with their IP addresses

The command looks like this,

docker network inspect -f '{{range .Containers}}{{printf "%s -> %s\n" .Name .IPv4Address}}{{end}}' [network_name|id]

Example:-

➟ docker network inspect -f '{{range .Containers}}{{printf "%s -> %s\n" .Name .IPv4Address}}{{end}}' unique 
cranky_wescoff -> 172.21.0.5/16
nginx -> 172.21.0.2/16
upbeat_carson -> 172.21.0.3/16
objective_jones -> 172.21.0.4/16

Formatting the output of Docker inspect command

inspect provides us with a JSON array for the output, which you can filter using something like jq. So if you're experienced in jq, you might want to just use it. The problem with jq is that it doesn't come preinstalled in most of the Linux distributions, while the default formatting mechanism of docker .. inspect is already there, and it is very powerful.

Docker uses go-templates to format its output. This article is not going to be about go-templates, but if you want to learn more, you can read about it here.

Internally JSONs are represented using various Go data structures. That's why go templates actually work with go data types. Since I don't want to explain what these data structures are, instead of using those terms, I'll use JSON terms to make it more understandable.

Extracting simple fields

Consider a JSON object like the following:-

{
	"mary": 43,
    "john": 44
}

Let's say you want to extract the information that is associated with the key mary. To do so what you use is the dot [.] notation, where you prefix the key with a dot, and add keys recursively for any nested keys. This is the same as jq. So .mary here would be 43.

Consider the following JSON now.

{
	"mary": {
    	"jane": 43,
        "soyas": 56
    },
    "john": 65
}

In this case .mary.jane would be 43, and similarly .mary.soyas would be 56.

A similar syntax can be used with go-templates. To format the output, you need to pass the template to -f or --format option of the inspect sub-command. Let's revise the output of the volume inspection.

[
    {
        "CreatedAt": "2021-05-07T15:53:10+05:30",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/unique/_data",
        "Name": "unique",
        "Options": {},
        "Scope": "local"
    }
]

If you want to know the Mountpoint, you'd use the following command:-

docker volume inspect -f '{{.Mountpoint}}' unique
➟ docker volume inspect -f '{{.Mountpoint}}' unique
/var/lib/docker/volumes/unique/_data

You're probably noticing the curly braces right there, these are like blocks, expressions are encapsulated inside these blocks.

Let's try something nested now. Inspect the network, and look for the IPAM section.

➟ docker network inspect unique
<snipped>
 "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
<snipped>

Looking at this you can pretty easily figure out what this network's driver is. But instead of looking for it like this, you can format it out of the whole JSON output.

Notice the Driver key is nested within IPAM. So the dot expression to extract the driver would be .IPAM.Driver. See it in action:-

➟ docker network inspect -f '{{.IPAM.Driver}}' unique
default

Looping over objects or lists (range)

JSON objects are like associative arrays in Bash, or Hashes, where the keys are strings and the values can be of any data type.

To make you understand this bit easier I'll start with a practical example. Consider the .NetworkSettings.Networks section in an inspect result of a container. It lists the networks the container is connected to, and for each network, some more associated details like the IP address.

Think if someone asks you to tell him/her the IP address of a container. Choosing just one network and the associated IP doesn't make much sense, what would be better is to list all the IP addresses that are associated with all the networks.

You can achieve this with a simple bash for loop if you already know the network names. Like in my case I can do something like the following:-

for network in bridge unique; do
	docker container inspect -f \
    	"{{.NetworkSettings.Networks.$network.IPAddress}}" nginx
done

But obviously this is limiting in a large scale since we can't possibly always remember all the network names.

You can mitigate this problem by using the template action range. range goes over a map (an associative array or JSON object) and provides us with not the key, but the values for each iteration (this behavior is changeable).

So in this case, you can write a block like {{range .NetworkSettings.Networks}} to loop over the values of each networks or the data associated with each networks, and from that you can extract the IP address like you'd from a normal JSON-like structure, i.e. {{.IPAddress}}}. One thing to remember is to always end the whole template that starts with range, with {{end}}.

Putting all that together, you can rewrite the previous for loop right this way:-

docker container inspect -f \
	'{{range .NetworkSettings.Networks}}
     {{.IPAddress}}{{end}}' nginx

Sample output:-

➟ docker container inspect -f \
> '{{range .NetworkSettings.Networks}}
>      {{.IPAddress}}{{end}}' nginx

     172.17.0.2
     172.21.0.2

Using the index function on arrays & objects

You can use the index function to extract parts from your JSON object or array. If the structure is a JSON object, you'd use {{index .Field "key"}}, if the structure is a JSON array, you'd use {{index .Field index}}.

In the previous example you printed all the IP addresses of a container. Let's say you know one of the networks it is connected to (bridge) and want to print the IP address associated with that network. You can do that with index function like this:-

docker container inspect -f '{{(index .NetworkSettings.Networks "bridge").IPAddress}}' nginx

Output:-

➟ docker container inspect -f '{{(index .NetworkSettings.Networks "bridge").IPAddress}}' nginx
172.17.0.2

Using json function

The data exported after formatting is not in JSON, it's in some go data structure. But you can convert that to JSON by using the json function.

The top level of the JSON object is .. So to print that you'd do something like this:-

docker network inspect -f '{{.}}' unique
➟ docker network inspect -f '{{.}}' unique 
{unique 09a7e2163ee058b1057d95599f764d571ec6a42a5792803dc125e706caa525b0 2021-05-07 15:47:20.341493099 +0530 IST local bridge false {default map[] [{172.21.0.0/16  172.21.0.1 map[]}]} false false false {} false map[2646cbbde5efc218bb6f3a5c882f8eb9e3e4331d090ad46ccc0a2eec9c2eea1b:{nginx c0291394a48f7e8e8aa98fd31631eb00e68daacbee9cf24bac530f16359d051d 02:42:ac:15:00:02 172.21.0.2/16 }] map[] map[] [] map[]}

What you're seeing here is a big struct consisting of other structs basic data types and maps. These are not very readable, nor usable outside of go. But you can convert these to JSON using the json function. Just prepend a field with json like I'm doing here:-

➟ docker network inspect -f '{{json .}}' unique
➟ docker network inspect -f '{{json .}}' unique 
{"Name":"unique","Id":"09a7e2163ee058b1057d95599f764d571ec6a42a5792803dc125e706caa525b0","Created":"2021-05-07T15:47:20.341493099+05:30","Scope":"local","Driver":"bridge","EnableIPv6":false,"IPAM":{"Driver":"default","Options":{},"Config":[{"Subnet":"172.21.0.0/16","Gateway":"172.21.0.1"}]},"Internal":false,"Attachable":false,"Ingress":false,"ConfigFrom":{"Network":""},"ConfigOnly":false,"Containers":{"2646cbbde5efc218bb6f3a5c882f8eb9e3e4331d090ad46ccc0a2eec9c2eea1b":{"Name":"nginx","EndpointID":"c0291394a48f7e8e8aa98fd31631eb00e68daacbee9cf24bac530f16359d051d","MacAddress":"02:42:ac:15:00:02","IPv4Address":"172.21.0.2/16","IPv6Address":""}},"Options":{},"Labels":{}}

To make it look a bit better pipe it to jq.

➟ docker network inspect -f '{{json .}}' unique | jq
{
  "Name": "unique",
  "Id": "09a7e2163ee058b1057d95599f764d571ec6a42a5792803dc125e706caa525b0",
  "Created": "2021-05-07T15:47:20.341493099+05:30",
  "Scope": "local",
  "Driver": "bridge",
  "EnableIPv6": false,
  "IPAM": {
    "Driver": "default",
    "Options": {},
    "Config": [
      {
        "Subnet": "172.21.0.0/16",
        "Gateway": "172.21.0.1"
      }
    ]
  },
  "Internal": false,
  "Attachable": false,
  "Ingress": false,
  "ConfigFrom": {
    "Network": ""
  },
  "ConfigOnly": false,
  "Containers": {
    "2646cbbde5efc218bb6f3a5c882f8eb9e3e4331d090ad46ccc0a2eec9c2eea1b": {
      "Name": "nginx",
      "EndpointID": "c0291394a48f7e8e8aa98fd31631eb00e68daacbee9cf24bac530f16359d051d",
      "MacAddress": "02:42:ac:15:00:02",
      "IPv4Address": "172.21.0.2/16",
      "IPv6Address": ""
    }
  },
  "Options": {},
  "Labels": {}
}

Is that it? Absolutely not. I highly recommend you read about how to use go templates.

Here I've tried to get to started on using it without having to know much about go templates at all. I hope I succeeded.

For any clarifications, feel free to use the comments section.