├── .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 alpine:3.1 2 | 3 | MAINTAINER CenturyLink Labs 4 | ENTRYPOINT ["/usr/src/app/image-graph.sh"] 5 | CMD [""] 6 | 7 | RUN apk update && apk add ruby-dev graphviz ttf-ubuntu-font-family ca-certificates 8 | RUN gem install --no-rdoc --no-ri docker-api sinatra 9 | RUN dot -c 10 | 11 | ADD . /usr/src/app/ 12 | WORKDIR /usr/src/app/ 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Analytics](https://ga-beacon.appspot.com/UA-49491413-7/docker-image-graph/README?pixel)](https://github.com/CenturyLinkLabs/docker-image-graph) 2 | [![](https://badge.imagelayers.io/centurylink/image-graph.svg)](https://imagelayers.io/?images=centurylink/image-graph:latest 'Get your own badge on imagelayers.io') 3 | 4 | 5 | ## NOTE 6 | 7 | This repo is no longer being maintained. Users are welcome to fork it, but we make no warranty of its functionality. 8 | 9 | ## image-graph 10 | Generates a nice graph showing the hierarchy of Docker images in your local 11 | image cache. 12 | 13 | Looks at all of the Docker image layers cached on the local system and 14 | generates a PNG image showing the relationship of the various layers. 15 | 16 | ### Usage 17 | 18 | The Ruby *image-graph* script is itself packaged as a Docker image so it can 19 | easily be executed with the Docker *run* command. 20 | 21 | Since the script interacts with the Docker API in order to inspect your local 22 | 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. 23 | 24 | By default, when the container is started it will generate a PNG that is streamed to `stdout`: 25 | 26 | docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ 27 | centurylink/image-graph > docker_images.png 28 | 29 | 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. 30 | 31 | 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: 32 | 33 | docker run -d -v /var/run/docker.sock:/var/run/docker.sock \ 34 | -e PORT=3000 -p 3000:3000 centurylink/image-graph 35 | 36 | 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. 37 | 38 | When a value for the *PORT* envrionment variable is provided, the PNG version of the image is **not** streamed to `stdout`. 39 | 40 | ### Example 41 | 42 | Here's an example graph generated by this utility: 43 | 44 | ![Sample Image](https://github.com/CenturyLinkLabs/docker-image-graph/raw/master/sample-cmd.png) 45 | 46 | And the web-based graph: 47 | 48 | ![Sample Image](https://github.com/CenturyLinkLabs/docker-image-graph/raw/master/sample-web.png) 49 | 50 | ### FAQs 51 | 52 | **How is this different than running `docker images --viz | dot -Tpng`?** 53 | 54 | Actually, it's not different at all. The Ruby script executed by the image 55 | essentially generates the same output that the `--viz` flag does and then pipes 56 | it to the Graphviz *dot* tool to generate the graph. 57 | 58 | **So, why do we need to execute a container when the same thing can be 59 | achieved with a one-line Docker command?** 60 | 61 | While this will work for some versions of Docker, the `--viz` flag has been 62 | marked as deprecated and will likely be removed in some future release. 63 | 64 | Additionally, my primary Docker environment is CoreOS which does not have 65 | Graphviz installed and provides no way for me to install it myself -- in 66 | an environment like this, the only option is to run Graphviz inside a 67 | container. 68 | -------------------------------------------------------------------------------- /image-graph-cmd.rb: -------------------------------------------------------------------------------- 1 | #! /usr/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 | puts dot_file 28 | -------------------------------------------------------------------------------- /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 | else 63 | cmd = cmd.last 64 | end 65 | 66 | cmd 67 | rescue => ex 68 | '' 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /image-graph.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ -z "$PORT" ]; then 4 | ruby ./image-graph-cmd.rb | dot -Tpng 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/CenturyLinkLabs/docker-image-graph/f7e1d25d4b2f77340ec097ea46a4b961ae97b7f5/public/texture-noise.png -------------------------------------------------------------------------------- /sample-cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CenturyLinkLabs/docker-image-graph/f7e1d25d4b2f77340ec097ea46a4b961ae97b7f5/sample-cmd.png -------------------------------------------------------------------------------- /sample-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CenturyLinkLabs/docker-image-graph/f7e1d25d4b2f77340ec097ea46a4b961ae97b7f5/sample-web.png --------------------------------------------------------------------------------