├── .dockerignore ├── Dockerfile ├── README.md ├── image-graph-cmd.rb ├── image-graph-web.rb ├── image-graph.sh ├── public ├── index.html └── texture-noise.png ├── sample-cmd.png └── sample-web.png /.dockerignore: -------------------------------------------------------------------------------- 1 | README.md 2 | *.png 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | MAINTAINER ian.miell@gmail.com 4 | 5 | RUN apt-get update 6 | 7 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y graphviz ruby-dev gem build-essential 8 | RUN gem install docker-api sinatra 9 | 10 | ADD . /usr/src/app/ 11 | WORKDIR /usr/src/app 12 | RUN chmod +x image-graph.sh 13 | 14 | CMD [""] 15 | ENTRYPOINT ["./image-graph.sh"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## image-graph 2 | Generates a nice graph showing the hierarchy of Docker images in your local 3 | image cache. 4 | 5 | Looks at all of the Docker image layers cached on the local system and 6 | generates a PNG image showing the relationship of the various layers. 7 | 8 | ### Usage 9 | 10 | The Ruby *image-graph* script is itself packaged as a Docker image so it can 11 | easily be executed with the Docker *run* command. 12 | 13 | Since the script interacts with the Docker API in order to inspect your local 14 | image cache it needs access to the Docker API socket. When starting the container, the `-v` flag needs to be used to make the Docker socket available inside the container. 15 | 16 | By default, when the container is started it will generate a PNG that is streamed to `stdout`: 17 | 18 | docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ 19 | centurylink/image-graph > docker_images.png 20 | 21 | You'll need to redirect the container's output to a file in order to save/view the generated image. The name of the output file does not matter, but it is recommended that you use a `.png` extension so that your image viewer will properly recognize the format of the file. 22 | 23 | If you supply an environment variabled named *PORT* using the `-e` flag the container will spin-up a web server on the designated port that hosts a web-based version of the image graph: 24 | 25 | docker run -d -v /var/run/docker.sock:/var/run/docker.sock \ 26 | -e PORT=3000 -p 3000:3000 centurylink/image-graph 27 | 28 | In the example above, the web server will be started on port 3000. When using this option you'll also want to use the `-p` flag to map the container port to a port on the host so that you can access the server from outside the container. The container port you specify with the `-p` flag should match the value you specified for the *PORT* environment variable. 29 | 30 | When a value for the *PORT* envrionment variable is provided, the PNG version of the image is **not** streamed to `stdout`. 31 | 32 | ### Example 33 | 34 | Here's an example graph generated by this utility: 35 | 36 | ![Sample Image](https://github.com/CenturyLinkLabs/docker-image-graph/raw/master/sample-cmd.png) 37 | 38 | And the web-based graph: 39 | 40 | ![Sample Image](https://github.com/CenturyLinkLabs/docker-image-graph/raw/master/sample-web.png) 41 | 42 | ### FAQs 43 | 44 | **How is this different than running `docker images --viz | dot -Tpng`?** 45 | 46 | Actually, it's not different at all. The Ruby script executed by the image 47 | essentially generates the same output that the `--viz` flag does and then pipes 48 | it to the Graphviz *dot* tool to generate the graph. 49 | 50 | **So, why do we need to execute a container when the same thing can be 51 | achieved with a one-line Docker command?** 52 | 53 | While this will work for some versions of Docker, the `--viz` flag has been 54 | marked as deprecated and will likely be removed in some future release. 55 | 56 | Additionally, my primary Docker environment is CoreOS which does not have 57 | Graphviz installed and provides no way for me to install it myself -- in 58 | an environment like this, the only option is to run Graphviz inside a 59 | container. 60 | 61 | -------------------------------------------------------------------------------- /image-graph-cmd.rb: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/ruby 2 | require 'open3' 3 | require 'docker' 4 | 5 | dot_file = [] 6 | dot_file << 'digraph docker {' 7 | 8 | Docker::Image.all(all: true).each do |image| 9 | 10 | id = image.id[0..11] 11 | tags = image.info['RepoTags'].reject { |t| t == ':' }.join('\n') 12 | parent_id = image.info['ParentId'][0..11] 13 | 14 | if parent_id.empty? 15 | dot_file << "base -> \"#{id}\" [style=invis]" 16 | else 17 | dot_file << "\"#{parent_id}\" -> \"#{id}\"" 18 | end 19 | 20 | unless tags.empty? 21 | dot_file << "\"#{id}\" [label=\"#{id}\\n#{tags}\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];" 22 | end 23 | end 24 | 25 | dot_file << 'base [style=invisible]' 26 | dot_file << '}' 27 | 28 | Open3.popen3('/usr/bin/dot -Tpng') do |stdin, stdout, stderr| 29 | stdin.puts(dot_file.join("\n")) 30 | stdin.close 31 | STDOUT.write stdout.read 32 | end 33 | -------------------------------------------------------------------------------- /image-graph-web.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'docker' 3 | 4 | set :port, ENV['PORT'] 5 | set :bind, '0.0.0.0' 6 | 7 | get '/' do 8 | File.read(File.join('public', 'index.html')) 9 | end 10 | 11 | get '/images.json' do 12 | 13 | Docker::Image.all(all: 1).map do |image| 14 | label = "#{image.short_id} — #{image.size} MB#{image.tags}" 15 | 16 | [ { v: image.id, f: label }, image.parent_id, image.cmd, ] 17 | end.to_json 18 | end 19 | 20 | delete '/images/:image_id.json' do 21 | image = Docker::Image.get(params['image_id']) 22 | 23 | begin 24 | image.remove() 25 | "true" 26 | rescue => ex 27 | "false" 28 | end 29 | end 30 | 31 | class Docker::Image 32 | 33 | NOP_PREFIX = '#(nop) ' 34 | 35 | def short_id 36 | id[0..11] 37 | end 38 | 39 | def parent_id 40 | info['ParentId'] 41 | end 42 | 43 | def size 44 | info['VirtualSize'] / 1024 / 1024 45 | end 46 | 47 | def tags 48 | info['RepoTags'].reject { |t| t == ':' }.join(', ') 49 | end 50 | 51 | def cmd 52 | cmd = json['ContainerConfig']['Cmd'] 53 | 54 | if cmd && cmd.size == 3 55 | cmd = cmd.last 56 | 57 | if cmd.start_with?(NOP_PREFIX) 58 | cmd = cmd.split(NOP_PREFIX).last 59 | else 60 | cmd = "RUN #{cmd}".squeeze(' ') 61 | end 62 | end 63 | 64 | cmd 65 | rescue => ex 66 | '' 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /image-graph.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ -z "$PORT"]; then 4 | ruby ./image-graph-cmd.rb 5 | else 6 | ruby ./image-graph-web.rb 7 | fi 8 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 90 | 91 | 127 | 128 | 129 | 130 |
131 | Docker Image Cache 132 |
- Double-click on node to collapse/expand
133 |
- Select node and press 'x' to delete
134 |
135 | 136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /public/texture-noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docker-in-practice/docker-image-graph/a7c4b4c753f60d437f34de9360764cc99b60fb4e/public/texture-noise.png -------------------------------------------------------------------------------- /sample-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docker-in-practice/docker-image-graph/a7c4b4c753f60d437f34de9360764cc99b60fb4e/sample-cmd.png -------------------------------------------------------------------------------- /sample-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docker-in-practice/docker-image-graph/a7c4b4c753f60d437f34de9360764cc99b60fb4e/sample-web.png --------------------------------------------------------------------------------