Last active
April 12, 2023 19:06
-
-
Save sethwebster/ce7b5e81aba09b65066683c33f882fe9 to your computer and use it in GitHub Desktop.
Revisions
-
sethwebster revised this gist
Jan 26, 2019 . 3 changed files with 30 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,30 @@ # Gem Install Dockerfile Hack If you're hacking on your Gemfile and using Docker, you know the pain of having the `bundle install` command run after you've added or removed a gem. Using `docker-compose` you _could_ mount a volume and stage your gems there, but this adds additional complexity and doesn't always _really_ solve the problem. Enter _this_ imperfect solution: > What if we installed every gem into it's own Docker layer which would be happily cached for us? `gem-inject-docker` does just that. It takes the list of gems used by your app via `bundle list` and transforms it into a list of `RUN gem install <your gem> -v <gem version>` statements and injects them into the Dockerfile at a point of your choosing. It additionally caches this list, and only _appends_ new gems to the end to prevent a full rebuild for all previously installged gems. If a gem is _removed_ it is removed from the list of instructions and the cache. This does _not_ seem to trigger Docker to rebuild all layers. **Pros** * Will absolutely increase the speed of your gem-update related Docker builds. **Cons** * Requires you to use a build script (see `build.sh`) * Requires you to add ./tmp/docker-cache to your `.gitignore` file. If you do not do this, other people's builds might not benefit from the speed improvement * Creates a bunch of Docker layers in the local cache ### Using 1. Add a `bin` folder to your project 2. Add `gem-inject-docker.rb` and `build.sh` to that path 3. Make them executable: `chmod +x bin/gem-inject-docker.rb bin/build.sh` 4. Instead of running `docker build . [...]` run `bin/build` The first build will take longer than you are used to as a new layer is created for every gem. This is normal (for this hack). Subsequent builds will benefit from this step, however. Empty file.Empty file. -
sethwebster revised this gist
Jan 26, 2019 . 2 changed files with 2 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ #!/usr/bin/env bash ./bin/gem-inject-docker > Dockerfile.injected && \ docker build . -f Dockerfile.injected && \ rm -f Dockerfile.injected This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -19,8 +19,6 @@ # sourced from GitHub or another repo that # will not work with `gem install` IGNORED_GEMS = [ ].freeze # Where would you like the list of previously @@ -43,7 +41,7 @@ def filter_ignored(gems) def bundled_gems gem_list = `bundle list`.split("\n") gem_list[1..gem_list.length - 1].map do |gem| gem[4..-1] end end -
sethwebster created this gist
Jan 26, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,4 @@ #!/usr/bin/env bash ./bin/gem-inject-docker > Dockerfile.injected && \ docker build . -f Dockerfile.injected && \ rm -rf Dockerfile.injected This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,101 @@ #!/usr/bin/env ruby # HACK: This file is here to inject each gem # dependency into the Dockerfile as a separate line. # # Doing so will increase your first build time, but # will allow subsequent builds to complete rather # quickly. # # Previously injected gems are cached to ./tmp/docker-cache # in docker-gems.cache. # # require 'fileutils' VERSION_MATCH = /\(?((\d+)?\.(\d+)?\.(\d+)\.?(\d+)?)\)?/.freeze # List the gems you wish not to inject here. # This is useful when you have gems that are # sourced from GitHub or another repo that # will not work with `gem install` IGNORED_GEMS = [ 'contently-jwt', 'rack-delegate' ].freeze # Where would you like the list of previously # injected gems cached? TMP_DIR = './tmp/docker-cache'.freeze CACHE_FILE = File.join(TMP_DIR, 'docker-gems.cache').freeze # This token should be placed at the point in # your Dockerfile that you would like the gem # install instructions injected TOP_DELIMETER = '# --- INJECT GEMS HERE ---'.freeze # Filters the gems listed in the IGNORED_GEMS # above def filter_ignored(gems) gems.reject { |g| IGNORED_GEMS.any? { |i| g.start_with?(i) } } end # Returns the list of gems in your Gemfile def bundled_gems gem_list = `bundle list`.split("\n") gem_list[1..gem_list.length - 1].map do |gem| gem.sub(' * ', '') end end # Tranforms the list into Docker `gem install` # instructions def generate_docker_gem_install(gem_list) gem_list.map do |g| name = g.split(' ')[0] version = (VERSION_MATCH.match(g) || [])[1] "RUN gem install #{name} -v #{version}" if version && (!g.include? 'default') end.reject(&:nil?).join("\n") end # Tries to load the cached list of gems def load_previous_gems File.read(CACHE_FILE).split("\n") rescue StandardError [] end # Saves the new list to the cachefile def save_new_gem_list(list) FileUtils.mkdir_p TMP_DIR unless Dir.exist? TMP_DIR File.open(CACHE_FILE, 'w') do |file| file.write(list.join("\n")) end end # Loads the dockerfile def load_dockerfile dockerfile = File.read('Dockerfile') return dockerfile if dockerfile.include? TOP_DELIMETER warn 'Dockerfile does not contain the injection point.' warn "Add #{TOP_DELIMETER} at the point you would like the gems inserted" exit(2) end # Injects the `gem install` instructions into # the Dockerfile and returns the result def inject_dockerfile dockerfile = load_dockerfile cached_gems = load_previous_gems required_gems = filter_ignored(bundled_gems) new_gems = required_gems - cached_gems removed_gems = cached_gems - required_gems new_list = (cached_gems + new_gems) - removed_gems save_new_gem_list(new_list) dockerfile.sub(TOP_DELIMETER, TOP_DELIMETER + "\n" + generate_docker_gem_install(new_list)) end # output to STDOUT $stdout.puts inject_dockerfile