├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin └── building ├── building.gemspec ├── lib └── building.rb └── test └── test_app2container.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'highline' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | highline (1.6.20) 5 | 6 | PLATFORMS 7 | ruby 8 | 9 | DEPENDENCIES 10 | highline 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 CenturyLink Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NOTE 2 | 3 | This repo is no longer being maintained. Users are welcome to fork it, but we make no warranty of its functionality. 4 | 5 | building 6 | ========== 7 | A simple dev tool CLI for building Docker containers for any app using Heroku Buildpacks 8 | 9 | Install 10 | ------- 11 | 12 | $ sudo gem install building 13 | $ building 14 | Usage: building [options] CONTAINER_NAME [TAG] 15 | -o, --output FIGCONF Output a fig configuration file 16 | -f, --from FROM Change the default FROM (progrium/buildstep) 17 | -d, --dockerfile DOCKERFILE External Dockerfile to append to the building generated Dockerfile 18 | -i, --include CMD Extra commands during the image build 19 | -b, --buildpack URL Add an external Buildpack URL 20 | -p, --p PORT Run the container after it is built on a certain port 21 | -h, --help Display this screen 22 | 23 | Usage 24 | ----- 25 | 26 | To convert any app into a Docker container using Heroku Buildpacks, just use this simple gem: 27 | 28 | $ building myuser/container-name 29 | create Dockerfile 30 | building docker build -t myuser/container-name:latest . 31 | hint To run your app, try: docker run -d -p 8080 -e "PORT=8080" myuser/container-name:latest 32 | hint To re-build your app, try: docker build -t myuser/container-name . 33 | 34 | You can version your apps by adding a verison number. 35 | 36 | $ building myuser/container-name 1.2 37 | identical Dockerfile 38 | building docker build -t myuser/container-name:1.2 . 39 | hint To run your app, try: docker run -d -p 8080 -e "PORT=8080" myuser/container-name:1.2 40 | hint To re-build your app, try: docker build -t myuser/container-name . 41 | 42 | Also, you can have building run the app for you automatically by adding a -p flag with a port number. 43 | 44 | $ building -p 8080 myuser/container-name 1.2 45 | identical Dockerfile 46 | building docker build -t myuser/container-name:1.2 . 47 | running docker run -d -p 8080 -e "PORT=8080" myuser/container-name:1.2 48 | 49 | Fig Integration 50 | --------------- 51 | 52 | If you never want to interact with the docker command line, building can pair up with fig with the -o flag. 53 | 54 | $ brew install python # if you are on a Mac 55 | $ sudo pip install -U fig 56 | $ building -o fig.yml myuser/container-name 57 | $ fig up -d 58 | Creating myapp_web_1... 59 | $ fig scale web=3 60 | Starting myapp_web_2... 61 | Starting myapp_web_3... 62 | $ fig ps 63 | Name Command State Ports 64 | -------------------------------------------------- 65 | myapp_web_3 /start web Up 49192->8080/tcp 66 | myapp_web_2 /start web Up 49191->8080/tcp 67 | myapp_web_1 /start web Up 49190->8080/tcp 68 | 69 | This gives you a full Heroku like scaling environment in just a few easy commands. 70 | 71 | External Buildpacks 72 | ------------------- 73 | 74 | To add an external buildpack, you can specify it with a -b flag. For example, here is how to get HHVM working in a Docker container: 75 | 76 | $ building -b https://github.com/hhvm/heroku-buildpack-hhvm.git -f ctlc/buildstep:ubuntu12.04 wordpress 77 | 78 | In this case, the latest buildpack is compiled against Ubuntu 13.10, whereas the default Linux distro used for HHVM is Ubuntu 12.10. 79 | 80 | Adding Your Own Packages to the Standard Container 81 | -------------------------------------------------- 82 | 83 | Sometimes you need a few more packages built-in to your container. Here is how to do that: 84 | 85 | $ building -i "apt-get update && apt-get install -qy libapache2-mod-php5 php5-mysql php5-memcache php5-curl" wordpress 86 | 87 | Or you can save your modifications to a file for a cleaner building command. 88 | 89 | $ echo "apt-get update && apt-get install -qy libapache2-mod-php5 php5-mysql php5-memcache php5-curl" > Dockerfile.include 90 | $ building -d Dockerfile.include wordpress 91 | 92 | 93 | Creating Your Own Base Containers 94 | --------------------------------- 95 | 96 | Other times, you will need a more customized OS tuned to your needs. For example, the HHVM example above uses a custom VM. You can use https://github.com/progrium/buildstep as a starting point to build any container version you need for your applications. Here is how I built ctlc/buildstep:ubuntu12.04: 97 | 98 | $ git clone https://github.com/progrium/buildstep.git 99 | $ cd buildstep 100 | $ echo "FROM ubuntu:lucid 101 | RUN apt-get update && apt-get install python-software-properties -y 102 | RUN apt-add-repository ppa:brightbox/ruby-ng 103 | RUN apt-get update && apt-get install git-core curl rubygems libmysqlclient-dev libxml2 libxslt1.1 libfreetype6 libjpeg-dev liblcms-utils libxt6 libltdl-dev -y 104 | RUN mkdir /build 105 | ADD ./stack/ /build 106 | RUN LC_ALL=C DEBIAN_FRONTEND=noninteractive /build/prepare 107 | RUN apt-get clean" > Dockerfile 108 | 109 | $ docker build -t ctlc/buildstep:ubuntu12.04 . 110 | $ docker push ctlc/buildstep 111 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | 3 | Rake::TestTask.new do |t| 4 | t.libs << 'test' 5 | end 6 | 7 | desc "Run tests" 8 | task :default => :test 9 | -------------------------------------------------------------------------------- /bin/building: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "building")) 4 | 5 | BUILDING_BANNER = "Usage: building [options] CONTAINER_NAME [TAG]" 6 | 7 | options = {} 8 | 9 | opt_parser = OptionParser.new do |opts| 10 | opts.banner = BUILDING_BANNER 11 | 12 | opts.on("-o", "--output FIGCONF", "Output a fig configuration file") do |fig| 13 | options[:fig] = fig 14 | end 15 | 16 | opts.on("-f", "--from FROM", "Change the Dockerfile's FROM (chose from: ctlc/buildstep:ubuntu13.10, ctlc/buildstep:ubuntu13.04, ctlc/buildstep:ubuntu12.10, ctlc/buildstep:ubuntu12.04)") do |from| 17 | options[:from] = from 18 | end 19 | 20 | opts.on("-d", "--dockerfile DOCKERFILE", "External Dockerfile to append to the building generated Dockerfile") do |file| 21 | options[:file] = file 22 | end 23 | 24 | opts.on("-i", "--include CMD", "Extra commands during the image build") do |includes| 25 | options[:includes] = includes 26 | end 27 | 28 | opts.on("-b", "--buildpack URL", "Add an external Buildpack URL") do |buildpack_url| 29 | options[:buildpack_url] = buildpack_url 30 | end 31 | 32 | opts.on("-p", "--p PORT", "Run the container after it is built on a certain port") do |port| 33 | options[:port] = port 34 | end 35 | 36 | opts.on( '-h', '--help', 'Display this screen' ) do 37 | puts opts 38 | exit 39 | end 40 | 41 | end 42 | 43 | opt_parser.parse! 44 | 45 | if ARGV[0].nil? 46 | puts opt_parser 47 | exit -1 48 | end 49 | 50 | puts Building.convert(ARGV[0], ARGV[1], options) 51 | -------------------------------------------------------------------------------- /building.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'building' 3 | s.version = '1.1.2' 4 | s.date = '2014-03-31' 5 | s.summary = "Build a Docker container for any app using Heroku Buildpacks" 6 | s.description = "Build a Docker container for any app using Heroku Buildpacks" 7 | s.authors = ["Lucas Carlson"] 8 | s.email = 'lucas@rufy.com' 9 | s.files = ["lib/building.rb","bin/building"] 10 | s.requirements = ['bundler', 'highline'] 11 | s.homepage = 'http://github.com/CenturyLinkLabs/building' 12 | s.license = 'MIT' 13 | s.executables = ['building'] 14 | end 15 | -------------------------------------------------------------------------------- /lib/building.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'fileutils' 3 | require 'bundler/setup' 4 | require 'highline/import' 5 | 6 | class Building 7 | def self.convert(app_name, tag, options={}) 8 | Building.new(app_name, tag, options) 9 | end 10 | 11 | def initialize(app_name, tag, options={}) 12 | @app_name = app_name 13 | @tag = tag || "latest" 14 | @buildpack_url = options[:buildpack_url] 15 | @includes = options[:includes] 16 | @file = options[:file] 17 | @from = options[:from] 18 | @fig = options[:fig] 19 | @port = options[:port] 20 | 21 | create_dockerfile 22 | build_container 23 | 24 | build_fig if @fig 25 | 26 | if @port 27 | run_container(@port) 28 | else 29 | explain_container(8080) 30 | end 31 | 32 | exit 0 33 | end 34 | 35 | def create_dockerfile 36 | dockerfile = [] 37 | dockerfile << "FROM #{@from || "ctlc/buildstep:ubuntu13.10"}\n" 38 | 39 | if @buildpack_url 40 | dockerfile << <<-eof 41 | RUN git clone --depth 1 #{@buildpack_url} /build/buildpacks/#{@buildpack_url.split("/").last.sub(".git","")} 42 | RUN echo #{@buildpack_url} | cat - /build/buildpacks.txt > /tmp/out && mv /tmp/out /build/buildpacks.txt 43 | eof 44 | end 45 | 46 | if @includes 47 | dockerfile << "RUN #{@includes}\n" 48 | end 49 | 50 | if @file 51 | dockerfile << IO.read(@file) 52 | end 53 | 54 | dockerfile << <<-eof 55 | ADD . /app 56 | RUN /build/builder 57 | CMD /start web 58 | eof 59 | 60 | skip = false 61 | 62 | if File.exists?("Dockerfile") 63 | if IO.read("Dockerfile") == dockerfile.join("\n") 64 | skip = true 65 | say %{ <%= color('identical', [BLUE, BOLD]) %> Dockerfile} 66 | else 67 | say %{ <%= color('conflict', [RED, BOLD]) %> Dockerfile} 68 | choice = ask("Overwrite Dockerfile? [Yn] ") 69 | if choice.downcase == "n" 70 | say("Aborting...") 71 | exit 1 72 | else 73 | say %{ <%= color('force', [YELLOW, BOLD]) %> Dockerfile} 74 | end 75 | end 76 | else 77 | say %{ <%= color('create', [GREEN, BOLD]) %> Dockerfile} 78 | end 79 | 80 | if !skip 81 | File.open("Dockerfile" , "w") do |file| 82 | file << dockerfile.join("\n") 83 | end 84 | end 85 | end 86 | 87 | def build_fig 88 | figfile = <<-eof 89 | web: 90 | image: #{@app_name}:#{@tag} 91 | command: /start web 92 | environment: 93 | PORT: #{@port || 8080} 94 | ports: 95 | - #{@port || 8080} 96 | eof 97 | 98 | skip = false 99 | 100 | if File.exists?(@fig) 101 | if IO.read(@fig) == figfile 102 | skip = true 103 | say %{ <%= color('identical', [BLUE, BOLD]) %> #{@fig}} 104 | else 105 | say %{ <%= color('conflict', [RED, BOLD]) %> #{@fig}} 106 | choice = ask("Overwrite #{File.expand_path(@fig)}? [Yn] ") 107 | if choice.downcase == "n" 108 | say("Aborting...") 109 | exit 1 110 | else 111 | say %{ <%= color('force', [YELLOW, BOLD]) %> #{@fig}} 112 | end 113 | end 114 | else 115 | say %{ <%= color('create', [GREEN, BOLD]) %> #{@fig}} 116 | end 117 | 118 | if !skip 119 | File.open(@fig , "w") do |file| 120 | file << figfile 121 | end 122 | end 123 | end 124 | 125 | def build_container 126 | build_cmd = "docker build -t #{@app_name}:#{@tag} ." 127 | say %{ <%= color('building', [BLUE, BOLD]) %> #{build_cmd}} 128 | pid = fork { exec build_cmd } 129 | Process.waitpid(pid) 130 | end 131 | 132 | def explain_container(port) 133 | if @fig 134 | run = "fig up -d" 135 | else 136 | run = "docker run -d -p #{port} -e \"PORT=#{port}\" #{@app_name}:#{@tag}" 137 | end 138 | rebuild = "docker build -t #{@app_name} ." 139 | 140 | say " <%= color('hint', [YELLOW, BOLD]) %> To run your app, try: <%= color('#{run}', [BOLD]) %>" 141 | say " <%= color('hint', [YELLOW, BOLD]) %> To re-build your app, try: <%= color('#{rebuild}', [BOLD]) %>" 142 | end 143 | 144 | def run_container(port) 145 | run = "docker run -d -p #{port} -e \"PORT=#{port}\" #{@app_name}:#{@tag}" 146 | say " <%= color('running', [BLUE, BOLD]) %> #{run}" 147 | exec run 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /test/test_app2container.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'building' 3 | 4 | class BuildingTest < Test::Unit::TestCase 5 | def setup(options={}) 6 | Building.convert( 7 | "test-app", 8 | "latest", 9 | options 10 | ) 11 | end 12 | end 13 | --------------------------------------------------------------------------------