diff --git a/contrib/docker-build/README b/contrib/docker-build/README new file mode 100644 index 0000000000..f648753b90 --- /dev/null +++ b/contrib/docker-build/README @@ -0,0 +1,68 @@ +# docker-build: build your software with docker + +## Description + +docker-build is a script to build docker images from source. It will be deprecated once the 'build' feature is incorporated into docker itself (See https://github.com/dotcloud/docker/issues/278) + +Author: Solomon Hykes + + +## Install + +docker-builder requires: + +1) A reasonably recent Python setup (tested on 2.7.2). + +2) A running docker daemon at version 0.1.4 or more recent (http://www.docker.io/gettingstarted) + + +## Usage + +First create a valid Changefile, which defines a sequence of changes to apply to a base image. + + $ cat Changefile + # Start build from a know base image + from base:ubuntu-12.10 + # Update ubuntu sources + run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list + run apt-get update + # Install system packages + run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git + run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl + run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang + # Insert files from the host (./myscript must be present in the current directory) + copy myscript /usr/local/bin/myscript + + +Run docker-build, and pass the contents of your Changefile as standard input. + + $ IMG=$(./docker-build < Changefile) + +This will take a while: for each line of the changefile, docker-build will: + +1. Create a new container to execute the given command or insert the given file +2. Wait for the container to complete execution +3. Commit the resulting changes as a new image +4. Use the resulting image as the input of the next step + + +If all the steps succeed, the result will be an image containing the combined results of each build step. +You can trace back those build steps by inspecting the image's history: + + $ docker history $IMG + ID CREATED CREATED BY + 1e9e2045de86 A few seconds ago /bin/sh -c cat > /usr/local/bin/myscript; chmod +x /usr/local/bin/git + 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang + 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl + 77db140aa62a A few seconds ago /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q git + 83e85d155451 A few seconds ago /bin/sh -c apt-get update + bfd53b36d9d3 A few seconds ago /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list + base 2 weeks ago /bin/bash + 27cf78414709 2 weeks ago + + +Note that your build started from 'base', as instructed by your Changefile. But that base image itself seems to have been built in 2 steps - hence the extra step in the history. + + +You can use this build technique to create any image you want: a database, a web application, or anything else that can be build by a sequence of unix commands - in other words, anything else. + diff --git a/contrib/docker-build/docker-build b/contrib/docker-build/docker-build new file mode 100755 index 0000000000..934e47ec8f --- /dev/null +++ b/contrib/docker-build/docker-build @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +# docker-build is a script to build docker images from source. +# It will be deprecated once the 'build' feature is incorporated into docker itself. +# (See https://github.com/dotcloud/docker/issues/278) +# +# Author: Solomon Hykes + + + +# First create a valid Changefile, which defines a sequence of changes to apply to a base image. +# +# $ cat Changefile +# # Start build from a know base image +# from base:ubuntu-12.10 +# # Update ubuntu sources +# run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list +# run apt-get update +# # Install system packages +# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git +# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl +# run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +# # Insert files from the host (./myscript must be present in the current directory) +# copy myscript /usr/local/bin/myscript +# +# +# Run docker-build, and pass the contents of your Changefile as standard input. +# +# $ IMG=$(./docker-build < Changefile) +# +# This will take a while: for each line of the changefile, docker-build will: +# +# 1. Create a new container to execute the given command or insert the given file +# 2. Wait for the container to complete execution +# 3. Commit the resulting changes as a new image +# 4. Use the resulting image as the input of the next step + + +import sys +import subprocess +import json +import hashlib + +def docker(args, stdin=None): + print "# docker " + " ".join(args) + p = subprocess.Popen(["docker"] + list(args), stdin=stdin, stdout=subprocess.PIPE) + return p.stdout + +def image_exists(img): + return docker(["inspect", img]).read().strip() != "" + +def run_and_commit(img_in, cmd, stdin=None): + run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip() + print "---> Waiting for " + run_id + result=int(docker(["wait", run_id]).read().rstrip()) + if result != 0: + print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result) + sys.exit(1) + return docker(["commit", run_id]).read().rstrip() + +def insert(base, src, dst): + print "COPY {} to {} in {}".format(src, dst, base) + if dst == "": + raise Exception("Missing destination path") + stdin = file(src) + stdin.seek(0) + return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin) + + +def main(): + base="" + steps = [] + try: + for line in sys.stdin.readlines(): + line = line.strip() + # Skip comments and empty lines + if line == "" or line[0] == "#": + continue + op, param = line.split(" ", 1) + if op == "from": + print "FROM " + param + base = param + steps.append(base) + elif op == "run": + print "RUN " + param + result = run_and_commit(base, param) + steps.append(result) + base = result + print "===> " + base + elif op == "copy": + src, dst = param.split(" ", 1) + result = insert(base, src, dst) + steps.append(result) + base = result + print "===> " + base + else: + print "Skipping uknown op " + op + except: + docker(["rmi"] + steps) + raise + print base + +if __name__ == "__main__": + main() diff --git a/contrib/docker-build/example.changefile b/contrib/docker-build/example.changefile new file mode 100644 index 0000000000..19261de82b --- /dev/null +++ b/contrib/docker-build/example.changefile @@ -0,0 +1,11 @@ +# Start build from a know base image +from base:ubuntu-12.10 +# Update ubuntu sources +run echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list +run apt-get update +# Install system packages +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl +run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang +# Insert files from the host (./myscript must be present in the current directory) +copy myscript /usr/local/bin/myscript