Skip to content

Instantly share code, notes, and snippets.

@docwhat
Forked from kwk/remove-orphan-images.sh
Last active September 15, 2015 14:41
Show Gist options
  • Save docwhat/4d9721fe04d7a89ad770 to your computer and use it in GitHub Desktop.
Save docwhat/4d9721fe04d7a89ad770 to your computer and use it in GitHub Desktop.
A script to remove orphaned docker images in a docker registry. Ported to Ruby for easy of understanding.
Style/MultilineOperationIndentation:
EnforcedStyle: indented
Style/FileName:
Enabled: false
Metrics/AbcSize:
Enabled: false
Metrics/LineLength:
Max: 120

docker-registry-garbage-collect

The new home page is docwhat/docker-registry-garbage-collect on github.

Docker Registery version 1 has one huge glaring problem: It doesn't clean up "dangling" or "orphaned" images.

This happens when you push up "foobar:latest" and it clobbers a previous "foobar:latest". The images that made up the previous versions are now orphaned and left floating in limbo.

Notes

For maximum paranoia, the script will not remove images from the last hour. This is in case you're uploading something that hasn't been tagged yet.

License

Public domain. This stuff is trivial and it is needed.

Credits

#!/usr/bin/env ruby
require 'optparse'
require 'pathname'
require 'json'
require 'fileutils'
# Cleans up all images not referenced by a tag.
class Application
attr_reader :base_dir
def initialize(args)
@base_dir = Pathname.new('/data/registry')
@args = args
end
def log_stream
@log_stream ||= File.open('/tmp/docker-remove-orphan-images.log', 'a')
.tap { |l| l.puts "*** STARTING AT #{Time.now} ***" }
end
def info(str)
log_stream.puts "INFO: #{str}"
puts "INFO: #{str}"
end
def repository_dir
base_dir + 'repositories'
end
def image_dir
base_dir + 'images'
end
def libraries
@libraries ||= repository_dir
.children
.select(&:directory?)
end
def repositories
@repositories ||= libraries
.map(&:children)
.flatten
.select(&:directory?)
end
def tags
@tags ||= repositories
.map(&:children)
.flatten
.select { |p| p.basename.to_s =~ /^tag_/ }
end
def all_image_hashes
@all_image_hashes ||= image_dir
.children
.select(&:directory?)
.select { |p| (p + '_checksum').exist? }
.reject { |p| (p + '_checksum').mtime >= timestamp }
.map(&:basename)
.map(&:to_s)
end
def used_image_hashes
@used_image_hashes ||= tags
.map(&:read)
.map(&:chomp)
.map { |h| image_dir + h + 'ancestry' }
.select(&:file?)
.map { |p| JSON.load p }
.flatten
.sort
.uniq
end
def unused_image_hashes
@unused_image_hashes ||= all_image_hashes - used_image_hashes
end
def timestamp
@timestamp ||= Time.new - (60 * 60) # in seconds
end
def remove_index_references!
repositories.each do |repo|
filename = repo + '_index_images'
original_index = JSON.load filename.read
modified_index = original_index.reject { |x| unused_image_hashes.include? x['id'] }
next if original_index.size == modified_index.size
info "Updating index for #{repo}"
filename.open('w') { |f| f.write modified_index.to_json }
end
end
def remove_unused_images!
unused_image_hashes.each do |hash|
filename = image_dir + hash
info "Removing #{hash}"
FileUtils.rm_rf filename
end
end
def run
remove_index_references!
remove_unused_images!
ensure
log_stream.close unless log_stream.closed?
end
end
Application.new(ARGV).run if $PROGRAM_NAME == __FILE__
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment