Merge pull request #31 from shykes/shykes-0.6.5-dm-plugin

Merge from dotcloud/docker master 
diff --git a/.gitignore b/.gitignore
index 8cf6616..00d66de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@
 .hg/
 .git/
 vendor/pkg/
+pyenv
diff --git a/AUTHORS b/AUTHORS
index 64f2ce2..9a0d8ec 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -94,6 +94,7 @@
 Joost Cassee <joost@cassee.net>
 Jordan Arentsen <blissdev@gmail.com>
 Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
+Josh Poimboeuf <jpoimboe@redhat.com>
 Julien Barbier <write0@gmail.com>
 Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
 Karan Lyons <karan@karanlyons.com>
@@ -165,6 +166,7 @@
 Sridhar Ratnakumar <sridharr@activestate.com>
 Steeve Morin <steeve.morin@gmail.com>
 Stefan Praszalowicz <stefan@greplin.com>
+Sven Dowideit <SvenDowideit@home.org.au>
 Thatcher Peskens <thatcher@dotcloud.com>
 Thermionix <bond711@gmail.com>
 Thijs Terlouw <thijsterlouw@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f5c9617..8c08cef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,7 +17,6 @@
 + Prevent DNS server conflicts in CreateBridgeIface
 + Validate bind mounts on the server side
 + Use parent image config in docker build
-* Fix regression in /etc/hosts
 
 #### Client
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 43137c6..4024bf2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,11 +1,14 @@
 # Contributing to Docker
 
-Want to hack on Docker? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels
-wrong or incomplete.
+Want to hack on Docker? Awesome! Here are instructions to get you
+started. They are probably not perfect, please let us know if anything
+feels wrong or incomplete.
 
 ## Build Environment
 
-For instructions on setting up your development environment, please see our dedicated [dev environment setup docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
+For instructions on setting up your development environment, please
+see our dedicated [dev environment setup
+docs](http://docs.docker.io/en/latest/contributing/devenvironment/).
 
 ## Contribution guidelines
 
diff --git a/Dockerfile b/Dockerfile
index fc94695..df08761 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -36,7 +36,7 @@
 run	apt-get install -y -q build-essential libsqlite3-dev
 
 # Install Go
-run	curl -s https://go.googlecode.com/files/go1.2rc3.src.tar.gz | tar -v -C /usr/local -xz
+run	curl -s https://go.googlecode.com/files/go1.2rc4.src.tar.gz | tar -v -C /usr/local -xz
 env	PATH	/usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
 env	GOPATH	/go:/go/src/github.com/dotcloud/docker/vendor
 run	cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
diff --git a/NOTICE b/NOTICE
index 08be17f..fb6810b 100644
--- a/NOTICE
+++ b/NOTICE
@@ -8,35 +8,12 @@
 
 The following is courtesy of our legal counsel:
 
-Transfers of Docker shall be in accordance with applicable export
-controls of any country and all other applicable legal requirements.
-Docker shall not be distributed or downloaded to or in Cuba, Iran,
-North Korea, Sudan or Syria and shall not be distributed or downloaded
-to any person on the Denied Persons List administered by the U.S.
-Department of Commerce.
 
-What does that mean? 
-Here is a further explanation from our legal counsel:
+Use and transfer of Docker may be subject to certain restrictions by the
+United States and other governments.  
+It is your responsibility to ensure that your use and/or transfer does not
+violate applicable laws. 
 
-Like all software products that utilize cryptography, the export and
-use of Docker is subject to the U.S. Commerce Department's Export
-Administration Regulations (EAR) because it uses or contains
-cryptography (see
-http://www.bis.doc.gov/index.php/policy-guidance/encryption).  Certain
-free and open source software projects have a lightweight set of
-requirements, which can generally be met by providing email notice to
-the appropriate U.S. government agencies that their source code is
-available on a publicly available repository and making the
-appropriate statements in the README.
+For more information, please see http://www.bis.doc.gov
 
-The restrictions of the EAR apply to certain denied locations
-(currently Iran, Sudan, Syria, North Korea, or Cuba) and those
-individuals on the Denied Persons List, which is available here:
-http://www.bis.doc.gov/index.php/policy-guidance/lists-of-parties-of-concern/denied-persons-list.
-If you are incorporating Docker into a new open source project, the
-EAR restrictions apply to your incorporation of Docker into your
-project in the same manner as other cryptography-enabled projects,
-such as OpenSSL, almost all Linux distributions, etc.
-
-For more information, see http://www.apache.org/dev/crypto.html and/or
-seek legal counsel.
+See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
diff --git a/README.md b/README.md
index 0d0b1cd..12ffc2e 100644
--- a/README.md
+++ b/README.md
@@ -193,10 +193,9 @@
 *Brought to you courtesy of our legal counsel. For more context,
 please see the Notice document.*
 
-Transfers of Docker shall be in accordance with applicable export controls 
-of any country and all other applicable legal requirements. Without limiting the 
-foregoing, Docker shall not be distributed or downloaded to any individual or 
-location if such distribution or download would violate the applicable US 
-government export regulations. 
+Use and transfer of Docker may be subject to certain restrictions by the
+United States and other governments.  
+It is your responsibility to ensure that your use and/or transfer does not
+violate applicable laws. 
 
 For more information, please see http://www.bis.doc.gov
diff --git a/Vagrantfile b/Vagrantfile
index 93a2219..a0bb38c 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -4,65 +4,135 @@
 BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
 BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
 VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
+AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box"
 AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
-AWS_AMI    = ENV['AWS_AMI']    || "ami-d0f89fb9"
+AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900"
+AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro'
+
 FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
 
+SSH_PRIVKEY_PATH = ENV["SSH_PRIVKEY_PATH"]
+
+# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8)
+# and install docker.
+$script = <<SCRIPT
+# The username to add to the docker group will be passed as the first argument
+# to the script.  If nothing is passed, default to "vagrant".
+user="$1"
+if [ -z "$user" ]; then
+    user=vagrant
+fi
+
+# Adding an apt gpg key is idempotent.
+wget -q -O - https://get.docker.io/gpg | apt-key add -
+
+# Creating the docker.list file is idempotent, but it may overrite desired
+# settings if it already exists.  This could be solved with md5sum but it
+# doesn't seem worth it.
+echo 'deb http://get.docker.io/ubuntu docker main' > \
+    /etc/apt/sources.list.d/docker.list
+
+# Update remote package metadata.  'apt-get update' is idempotent.
+apt-get update -q
+
+# Install docker.  'apt-get install' is idempotent.
+apt-get install -q -y lxc-docker
+
+usermod -a -G docker "$user"
+
+tmp=`mktemp -q` && {
+    # Only install the backport kernel, don't bother upgrade if the backport is
+    # already installed.  We want parse the output of apt so we need to save it
+    # with 'tee'.  NOTE: The installation of the kernel will trigger dkms to
+    # install vboxguest if needed.
+    apt-get install -q -y --no-upgrade linux-image-generic-lts-raring | \
+        tee "$tmp"
+
+    # Parse the number of installed packages from the output
+    NUM_INST=`awk '$2 == "upgraded," && $4 == "newly" { print $3 }' "$tmp"`
+    rm "$tmp"
+}
+
+# If the number of installed packages is greater than 0, we want to reboot (the
+# backport kernel was installed but is not running).
+if [ "$NUM_INST" -gt 0 ];
+then
+    echo "Rebooting down to activate new kernel."
+    echo "/vagrant will not be mounted.  Use 'vagrant halt' followed by"
+    echo "'vagrant up' to ensure /vagrant is mounted."
+    shutdown -r now
+fi
+SCRIPT
+
+# We need to install the virtualbox guest additions *before* we do the normal
+# docker installation.  As such this script is prepended to the common docker
+# install script above.  This allows the install of the backport kernel to
+# trigger dkms to build the virtualbox guest module install.
+$vbox_script = <<VBOX_SCRIPT + $script
+# Install the VirtualBox guest additions if they aren't already installed.
+if [ ! -d /opt/VBoxGuestAdditions-4.2.12/ ]; then
+    # Update remote package metadata.  'apt-get update' is idempotent.
+    apt-get update -q
+
+    # Kernel Headers and dkms are required to build the vbox guest kernel
+    # modules.
+    apt-get install -q -y linux-headers-generic-lts-raring dkms
+
+    echo 'Downloading VBox Guest Additions...'
+    wget -cq http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso
+
+    mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt
+    /mnt/VBoxLinuxAdditions.run --nox11
+    umount /mnt
+fi
+VBOX_SCRIPT
+
 Vagrant::Config.run do |config|
   # Setup virtual machine box. This VM configuration code is always executed.
   config.vm.box = BOX_NAME
   config.vm.box_url = BOX_URI
 
-  config.ssh.forward_agent = true
-
-  # Provision docker and new kernel if deployment was not done.
-  # It is assumed Vagrant can successfully launch the provider instance.
-  if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
-    # Add lxc-docker package
-    pkg_cmd = "wget -q -O - https://get.docker.io/gpg | apt-key add -;" \
-      "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \
-      "apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; "
-    # Add Ubuntu raring backported kernel
-    pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; "
-    # Add guest additions if local vbox VM. As virtualbox is the default provider,
-    # it is assumed it won't be explicitly stated.
-    if ENV["VAGRANT_DEFAULT_PROVIDER"].nil? && ARGV.none? { |arg| arg.downcase.start_with?("--provider") }
-      pkg_cmd << "apt-get install -q -y linux-headers-generic-lts-raring dkms; " \
-        "echo 'Downloading VBox Guest Additions...'; " \
-        "wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; "
-      # Prepare the VM to add guest additions after reboot
-      pkg_cmd << "echo -e 'mount -o loop,ro /home/vagrant/VBoxGuestAdditions_4.2.12.iso /mnt\n" \
-        "echo yes | /mnt/VBoxLinuxAdditions.run\numount /mnt\n" \
-          "rm /root/guest_additions.sh; ' > /root/guest_additions.sh; " \
-        "chmod 700 /root/guest_additions.sh; " \
-        "sed -i -E 's#^exit 0#[ -x /root/guest_additions.sh ] \\&\\& /root/guest_additions.sh#' /etc/rc.local; " \
-        "echo 'Installation of VBox Guest Additions is proceeding in the background.'; " \
-        "echo '\"vagrant reload\" can be used in about 2 minutes to activate the new guest additions.'; "
-    end
-    # Add vagrant user to the docker group
-    pkg_cmd << "usermod -a -G docker vagrant; "
-    # Activate new kernel
-    pkg_cmd << "shutdown -r +1; "
-    config.vm.provision :shell, :inline => pkg_cmd
+  # Use the specified private key path if it is specified and not empty.
+  if SSH_PRIVKEY_PATH
+      config.ssh.private_key_path = SSH_PRIVKEY_PATH
   end
+
+  config.ssh.forward_agent = true
 end
 
-
 # Providers were added on Vagrant >= 1.1.0
+#
+# NOTE: The vagrant "vm.provision" appends its arguments to a list and executes
+# them in order.  If you invoke "vm.provision :shell, :inline => $script"
+# twice then vagrant will run the script two times.  Unfortunately when you use
+# providers and the override argument to set up provisioners (like the vbox
+# guest extensions) they 1) don't replace the other provisioners (they append
+# to the end of the list) and 2) you can't control the order the provisioners
+# are executed (you can only append to the list).  If you want the virtualbox
+# only script to run before the other script, you have to jump through a lot of
+# hoops.
+#
+# Here is my only repeatable solution: make one script that is common ($script)
+# and another script that is the virtual box guest *prepended* to the common
+# script.  Only ever use "vm.provision" *one time* per provider.  That means
+# every single provider has an override, and every single one configures
+# "vm.provision".  Much saddness, but such is life.
 Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
   config.vm.provider :aws do |aws, override|
-    aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
-    aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
+    username = "ubuntu"
+    override.vm.box_url = AWS_BOX_URI
+    override.vm.provision :shell, :inline => $script, :args => username
+    aws.access_key_id = ENV["AWS_ACCESS_KEY"]
+    aws.secret_access_key = ENV["AWS_SECRET_KEY"]
     aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
-    override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
-    override.ssh.username = "ubuntu"
+    override.ssh.username = username
     aws.region = AWS_REGION
     aws.ami    = AWS_AMI
-    aws.instance_type = "t1.micro"
+    aws.instance_type = AWS_INSTANCE_TYPE
   end
 
-  config.vm.provider :rackspace do |rs|
-    config.ssh.private_key_path = ENV["RS_PRIVATE_KEY"]
+  config.vm.provider :rackspace do |rs, override|
+    override.vm.provision :shell, :inline => $script
     rs.username = ENV["RS_USERNAME"]
     rs.api_key  = ENV["RS_API_KEY"]
     rs.public_key_path = ENV["RS_PUBLIC_KEY"]
@@ -71,20 +141,25 @@
   end
 
   config.vm.provider :vmware_fusion do |f, override|
-    override.vm.box = BOX_NAME
     override.vm.box_url = VF_BOX_URI
     override.vm.synced_folder ".", "/vagrant", disabled: true
+    override.vm.provision :shell, :inline => $script
     f.vmx["displayName"] = "docker"
   end
 
-  config.vm.provider :virtualbox do |vb|
-    config.vm.box = BOX_NAME
-    config.vm.box_url = BOX_URI
+  config.vm.provider :virtualbox do |vb, override|
+    override.vm.provision :shell, :inline => $vbox_script
     vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
     vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
   end
 end
 
+# If this is a version 1 config, virtualbox is the only option.  A version 2
+# config would have already been set in the above provider section.
+Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
+  config.vm.provision :shell, :inline => $vbox_script
+end
+
 if !FORWARD_DOCKER_PORTS.nil?
   Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
     (49000..49900).each do |port|
diff --git a/api.go b/api.go
index 61252ab..c5a91de 100644
--- a/api.go
+++ b/api.go
@@ -479,15 +479,16 @@
 		w.Header().Set("Content-Type", "application/json")
 	}
 	sf := utils.NewStreamFormatter(version > 1.0)
-	imgID, err := srv.ImageInsert(name, url, path, w, sf)
+	err := srv.ImageInsert(name, url, path, w, sf)
 	if err != nil {
 		if sf.Used() {
 			w.Write(sf.FormatError(err))
 			return nil
 		}
+		return err
 	}
 
-	return writeJSON(w, http.StatusOK, &APIID{ID: imgID})
+	return nil
 }
 
 func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
@@ -540,43 +541,36 @@
 	if err := parseForm(r); err != nil {
 		return nil
 	}
-	config := &Config{}
 	out := &APIRun{}
-	name := r.Form.Get("name")
-
-	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
+	job := srv.Eng.Job("create", r.Form.Get("name"))
+	if err := job.DecodeEnv(r.Body); err != nil {
 		return err
 	}
-
 	resolvConf, err := utils.GetResolvConf()
 	if err != nil {
 		return err
 	}
-
-	if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
+	if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
 		out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns))
-		config.Dns = defaultDns
+		job.SetenvList("Dns", defaultDns)
 	}
-
-	id, warnings, err := srv.ContainerCreate(config, name)
-	if err != nil {
+	// Read container ID from the first line of stdout
+	job.StdoutParseString(&out.ID)
+	// Read warnings from stderr
+	job.StderrParseLines(&out.Warnings, 0)
+	if err := job.Run(); err != nil {
 		return err
 	}
-	out.ID = id
-	for _, warning := range warnings {
-		out.Warnings = append(out.Warnings, warning)
-	}
-
-	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit {
 		log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.")
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.")
 	}
-	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
+	if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit {
 		log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.")
 		out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
 	}
 
-	if !config.NetworkDisabled && srv.runtime.capabilities.IPv4ForwardingDisabled {
+	if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled {
 		log.Println("Warning: IPv4 forwarding is disabled.")
 		out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
 	}
@@ -653,26 +647,23 @@
 }
 
 func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	var hostConfig *HostConfig
-	// allow a nil body for backwards compatibility
-	if r.Body != nil {
-		if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
-			hostConfig = &HostConfig{}
-			if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
-				return err
-			}
-		}
-	}
-
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 	}
 	name := vars["name"]
-	// Register any links from the host config before starting the container
-	if err := srv.RegisterLinks(name, hostConfig); err != nil {
-		return err
+	job := srv.Eng.Job("start", name)
+	if err := job.ImportEnv(HostConfig{}); err != nil {
+		return fmt.Errorf("Couldn't initialize host configuration")
 	}
-	if err := srv.ContainerStart(name, hostConfig); err != nil {
+	// allow a nil body for backwards compatibility
+	if r.Body != nil {
+		if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
+			if err := job.DecodeEnv(r.Body); err != nil {
+				return err
+			}
+		}
+	}
+	if err := job.Run(); err != nil {
 		return err
 	}
 	w.WriteHeader(http.StatusNoContent)
diff --git a/api_test.go b/api_test.go
index 72c27f0..a58a831 100644
--- a/api_test.go
+++ b/api_test.go
@@ -609,11 +609,11 @@
 }
 
 func TestPostContainersCreate(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
 	configJSON, err := json.Marshal(&Config{
 		Image:  GetTestImage(runtime).ID,
 		Memory: 33554432,
@@ -756,27 +756,23 @@
 }
 
 func TestPostContainersStart(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
-	container, _, err := runtime.Create(
+	id := createTestContainer(
+		eng,
 		&Config{
 			Image:     GetTestImage(runtime).ID,
 			Cmd:       []string{"/bin/cat"},
 			OpenStdin: true,
 		},
-		"",
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer runtime.Destroy(container)
+		t)
 
 	hostConfigJSON, err := json.Marshal(&HostConfig{})
 
-	req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
+	req, err := http.NewRequest("POST", "/containers/"+id+"/start", bytes.NewReader(hostConfigJSON))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -784,22 +780,26 @@
 	req.Header.Set("Content-Type", "application/json")
 
 	r := httptest.NewRecorder()
-	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
+	if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err != nil {
 		t.Fatal(err)
 	}
 	if r.Code != http.StatusNoContent {
 		t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code)
 	}
 
+	container := runtime.Get(id)
+	if container == nil {
+		t.Fatalf("Container %s was not created", id)
+	}
 	// Give some time to the process to start
+	// FIXME: use Wait once it's available as a job
 	container.WaitTimeout(500 * time.Millisecond)
-
 	if !container.State.Running {
 		t.Errorf("Container should be running")
 	}
 
 	r = httptest.NewRecorder()
-	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
+	if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": id}); err == nil {
 		t.Fatalf("A running container should be able to be started")
 	}
 
diff --git a/buildfile_test.go b/buildfile_test.go
index a27ef33..d3fca3c 100644
--- a/buildfile_test.go
+++ b/buildfile_test.go
@@ -544,10 +544,7 @@
 }
 
 func TestBuildInheritance(t *testing.T) {
-	runtime, err := newTestRuntime("")
-	if err != nil {
-		t.Fatal(err)
-	}
+	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
 	srv := &Server{
diff --git a/commands.go b/commands.go
index 179a303..4b51bc4 100644
--- a/commands.go
+++ b/commands.go
@@ -130,10 +130,7 @@
 	v.Set("url", cmd.Arg(1))
 	v.Set("path", cmd.Arg(2))
 
-	if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil); err != nil {
-		return err
-	}
-	return nil
+	return cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil)
 }
 
 // mkBuildContext returns an archive of an empty context with the contents
@@ -376,15 +373,17 @@
 		cmd.Usage()
 		return nil
 	}
+	var encounteredError error
 	for _, name := range cmd.Args() {
 		status, err := waitForExit(cli, name)
 		if err != nil {
-			fmt.Fprintf(cli.err, "%s", err)
+			fmt.Fprintf(cli.err, "%s\n", err)
+			encounteredError = fmt.Errorf("Error: failed to wait one or more containers")
 		} else {
 			fmt.Fprintf(cli.out, "%d\n", status)
 		}
 	}
-	return nil
+	return encounteredError
 }
 
 // 'docker version': show version information
@@ -505,15 +504,17 @@
 	v := url.Values{}
 	v.Set("t", strconv.Itoa(*nSeconds))
 
+	var encounteredError error
 	for _, name := range cmd.Args() {
 		_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
 		if err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
+			encounteredError = fmt.Errorf("Error: failed to stop one or more containers")
 		} else {
 			fmt.Fprintf(cli.out, "%s\n", name)
 		}
 	}
-	return nil
+	return encounteredError
 }
 
 func (cli *DockerCli) CmdRestart(args ...string) error {
@@ -530,15 +531,17 @@
 	v := url.Values{}
 	v.Set("t", strconv.Itoa(*nSeconds))
 
+	var encounteredError error
 	for _, name := range cmd.Args() {
 		_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
 		if err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
+			encounteredError = fmt.Errorf("Error: failed to  restart one or more containers")
 		} else {
 			fmt.Fprintf(cli.out, "%s\n", name)
 		}
 	}
-	return nil
+	return encounteredError
 }
 
 func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
@@ -772,15 +775,19 @@
 		return nil
 	}
 
+	var encounteredError error
 	for _, name := range cmd.Args() {
 		body, _, err := cli.call("DELETE", "/images/"+name, nil)
 		if err != nil {
-			fmt.Fprintf(cli.err, "%s", err)
+			fmt.Fprintf(cli.err, "%s\n", err)
+			encounteredError = fmt.Errorf("Error: failed to remove one or more images")
 		} else {
 			var outs []APIRmi
 			err = json.Unmarshal(body, &outs)
 			if err != nil {
-				return err
+				fmt.Fprintf(cli.err, "%s\n", err)
+				encounteredError = fmt.Errorf("Error: failed to remove one or more images")
+				continue
 			}
 			for _, out := range outs {
 				if out.Deleted != "" {
@@ -791,7 +798,7 @@
 			}
 		}
 	}
-	return nil
+	return encounteredError
 }
 
 func (cli *DockerCli) CmdHistory(args ...string) error {
@@ -870,15 +877,18 @@
 	if *link {
 		val.Set("link", "1")
 	}
+
+	var encounteredError error
 	for _, name := range cmd.Args() {
 		_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
 		if err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
+			encounteredError = fmt.Errorf("Error: failed to remove one or more containers")
 		} else {
 			fmt.Fprintf(cli.out, "%s\n", name)
 		}
 	}
-	return nil
+	return encounteredError
 }
 
 // 'docker kill NAME' kills a running container
@@ -892,15 +902,16 @@
 		return nil
 	}
 
+	var encounteredError error
 	for _, name := range args {
-		_, _, err := cli.call("POST", "/containers/"+name+"/kill", nil)
-		if err != nil {
+		if _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil); err != nil {
 			fmt.Fprintf(cli.err, "%s\n", err)
+			encounteredError = fmt.Errorf("Error: failed to kill one or more containers")
 		} else {
 			fmt.Fprintf(cli.out, "%s\n", name)
 		}
 	}
-	return nil
+	return encounteredError
 }
 
 func (cli *DockerCli) CmdImport(args ...string) error {
@@ -913,8 +924,16 @@
 		cmd.Usage()
 		return nil
 	}
-	src := cmd.Arg(0)
-	repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
+
+	var src, repository, tag string
+
+	if cmd.NArg() == 3 {
+		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' as been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n")
+		src, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
+	} else {
+		src = cmd.Arg(0)
+		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
+	}
 	v := url.Values{}
 	v.Set("repo", repository)
 	v.Set("tag", tag)
@@ -1166,14 +1185,10 @@
 			fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE")
 		}
 
-		var repo string
-		var tag string
 		for _, out := range outs {
 			for _, repotag := range out.RepoTags {
 
-				components := strings.SplitN(repotag, ":", 2)
-				repo = components[0]
-				tag = components[1]
+				repo, tag := utils.ParseRepositoryTag(repotag)
 
 				if !*noTrunc {
 					out.ID = utils.TruncateID(out.ID)
@@ -1235,7 +1250,7 @@
 
 	fmt.Fprintf(cli.out, "%s%s Size: %s (virtual %s)", prefix, imageID, utils.HumanSize(image.Size), utils.HumanSize(image.VirtualSize))
 	if image.RepoTags[0] != "<none>:<none>" {
-		fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ","))
+		fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", "))
 	} else {
 		fmt.Fprint(cli.out, "\n")
 	}
@@ -1351,8 +1366,16 @@
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
-	name := cmd.Arg(0)
-	repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
+
+	var name, repository, tag string
+
+	if cmd.NArg() == 3 {
+		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'CONTAINER [REPOSITORY [TAG]]' as been deprecated. Please use CONTAINER [REPOSITORY[:TAG]]\n")
+		name, repository, tag = cmd.Arg(0), cmd.Arg(1), cmd.Arg(2)
+	} else {
+		name = cmd.Arg(0)
+		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
+	}
 
 	if name == "" {
 		cmd.Usage()
@@ -1389,7 +1412,7 @@
 
 func (cli *DockerCli) CmdEvents(args ...string) error {
 	cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
-	since := cmd.String("since", "", "Show events previously created (used for polling).")
+	since := cmd.String("since", "", "Show previously created events and then stream.")
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
@@ -1401,7 +1424,17 @@
 
 	v := url.Values{}
 	if *since != "" {
-		v.Set("since", *since)
+		loc := time.FixedZone(time.Now().Zone())
+		format := "2006-01-02 15:04:05 -0700 MST"
+		if len(*since) < len(format) {
+			format = format[:len(*since)]
+		}
+
+		if t, err := time.ParseInLocation(format, *since, loc); err == nil {
+			v.Set("since", strconv.FormatInt(t.Unix(), 10))
+		} else {
+			v.Set("since", *since)
+		}
 	}
 
 	if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil {
@@ -1658,9 +1691,16 @@
 		return nil
 	}
 
-	v := url.Values{}
-	repository, tag := utils.ParseRepositoryTag(cmd.Arg(1))
+	var repository, tag string
 
+	if cmd.NArg() == 3 {
+		fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REPOSITORY[:TAG]]\n")
+		repository, tag = cmd.Arg(1), cmd.Arg(2)
+	} else {
+		repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
+	}
+
+	v := url.Values{}
 	v.Set("repo", repository)
 	v.Set("tag", tag)
 
@@ -1971,7 +2011,7 @@
 		if len(body) == 0 {
 			return nil, resp.StatusCode, fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode))
 		}
-		return nil, resp.StatusCode, fmt.Errorf("Error: %s", body)
+		return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body))
 	}
 	return body, resp.StatusCode, nil
 }
@@ -2027,7 +2067,7 @@
 		if len(body) == 0 {
 			return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
 		}
-		return fmt.Errorf("Error: %s", body)
+		return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
 	}
 
 	if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
diff --git a/commands_test.go b/commands_test.go
index 186bce2..657ed1d 100644
--- a/commands_test.go
+++ b/commands_test.go
@@ -6,6 +6,8 @@
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
+	"os"
+	"path"
 	"regexp"
 	"strings"
 	"testing"
@@ -381,8 +383,8 @@
 		if err != nil {
 			t.Fatal(err)
 		}
-		if cmdOutput != container.ShortID()+"\n" {
-			t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
+		if cmdOutput != container.ID+"\n" {
+			t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ID+"\n", cmdOutput)
 		}
 	})
 
@@ -459,7 +461,7 @@
 	})
 }
 
-// TestAttachDetach checks that attach in tty mode can be detached
+// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
 func TestAttachDetach(t *testing.T) {
 	stdin, stdinPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()
@@ -486,8 +488,8 @@
 
 		container = globalRuntime.List()[0]
 
-		if strings.Trim(string(buf[:n]), " \r\n") != container.ShortID() {
-			t.Fatalf("Wrong ID received. Expect %s, received %s", container.ShortID(), buf[:n])
+		if strings.Trim(string(buf[:n]), " \r\n") != container.ID {
+			t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n])
 		}
 	})
 	setTimeout(t, "Starting container timed out", 10*time.Second, func() {
@@ -501,7 +503,69 @@
 	ch = make(chan struct{})
 	go func() {
 		defer close(ch)
-		if err := cli.CmdAttach(container.ShortID()); err != nil {
+		if err := cli.CmdAttach(container.ID); err != nil {
+			if err != io.ErrClosedPipe {
+				t.Fatal(err)
+			}
+		}
+	}()
+
+	setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			if err != io.ErrClosedPipe {
+				t.Fatal(err)
+			}
+		}
+	})
+
+	setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
+		stdinPipe.Write([]byte{16, 17})
+		if err := stdinPipe.Close(); err != nil {
+			t.Fatal(err)
+		}
+	})
+	closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
+
+	// wait for CmdRun to return
+	setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
+		<-ch
+	})
+
+	time.Sleep(500 * time.Millisecond)
+	if !container.State.Running {
+		t.Fatal("The detached container should be still running")
+	}
+
+	setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
+		container.Kill()
+	})
+}
+
+// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
+func TestAttachDetachTruncatedID(t *testing.T) {
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	go stdout.Read(make([]byte, 1024))
+	setTimeout(t, "Starting container timed out", 2*time.Second, func() {
+		if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	container := globalRuntime.List()[0]
+
+	stdin, stdinPipe = io.Pipe()
+	stdout, stdoutPipe = io.Pipe()
+	cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+
+	ch := make(chan struct{})
+	go func() {
+		defer close(ch)
+		if err := cli.CmdAttach(utils.TruncateID(container.ID)); err != nil {
 			if err != io.ErrClosedPipe {
 				t.Fatal(err)
 			}
@@ -824,3 +888,55 @@
 
 	return image
 }
+
+// #2098 - Docker cidFiles only contain short version of the containerId
+//sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
+// TestRunCidFile tests that run -cidfile returns the longid
+func TestRunCidFile(t *testing.T) {
+	stdout, stdoutPipe := io.Pipe()
+
+	tmpDir, err := ioutil.TempDir("", "TestRunCidFile")
+	if err != nil {
+		t.Fatal(err)
+	}
+	tmpCidFile := path.Join(tmpDir, "cid")
+
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	c := make(chan struct{})
+	go func() {
+		defer close(c)
+		if err := cli.CmdRun("-cidfile", tmpCidFile, unitTestImageID, "ls"); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	defer os.RemoveAll(tmpDir)
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
+		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(cmdOutput) < 1 {
+			t.Fatalf("'ls' should return something , not '%s'", cmdOutput)
+		}
+		//read the tmpCidFile
+		buffer, err := ioutil.ReadFile(tmpCidFile)
+		if err != nil {
+			t.Fatal(err)
+		}
+		id := string(buffer)
+
+		if len(id) != len("2bf44ea18873287bd9ace8a4cb536a7cbe134bed67e805fdf2f58a57f69b320c") {
+			t.Fatalf("-cidfile should be a long id, not '%s'", id)
+		}
+		//test that its a valid cid? (though the container is gone..)
+		//remove the file and dir.
+	})
+
+	setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
+		<-c
+	})
+
+}
diff --git a/config.go b/config.go
index 42ae23a..75cd0c7 100644
--- a/config.go
+++ b/config.go
@@ -9,7 +9,6 @@
 type DaemonConfig struct {
 	Pidfile                     string
 	Root                        string
-	ProtoAddresses              []string
 	AutoRestart                 bool
 	EnableCors                  bool
 	Dns                         []string
@@ -36,7 +35,6 @@
 	} else {
 		config.BridgeIface = DefaultNetworkBridge
 	}
-	config.ProtoAddresses = job.GetenvList("ProtoAddresses")
 	config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
 	config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
 	return &config
diff --git a/container.go b/container.go
index 2f76bf6..0e54abe 100644
--- a/container.go
+++ b/container.go
@@ -134,7 +134,11 @@
 type Port string
 
 func (p Port) Proto() string {
-	return strings.Split(string(p), "/")[1]
+	parts := strings.Split(string(p), "/")
+	if len(parts) == 1 {
+		return "tcp"
+	}
+	return parts[1]
 }
 
 func (p Port) Port() string {
@@ -168,7 +172,7 @@
 	cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
 	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
-	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
+	flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
 	flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
 	flNetwork := cmd.Bool("n", true, "Enable networking for this container")
 	flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
@@ -177,9 +181,9 @@
 	cmd.String("name", "", "Assign a name to the container")
 	flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
 
-	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
+	if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
-		*flMemory = 0
+		*flMemoryString = ""
 	}
 
 	flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
@@ -200,7 +204,7 @@
 	cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
 
 	var flVolumesFrom utils.ListOpts
-	cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container")
+	cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
 
 	flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
 
@@ -246,6 +250,18 @@
 		}
 	}
 
+	var flMemory int64
+
+	if *flMemoryString != "" {
+		parsedMemory, err := utils.RAMInBytes(*flMemoryString)
+
+		if err != nil {
+			return nil, nil, cmd, err
+		}
+
+		flMemory = parsedMemory
+	}
+
 	var binds []string
 
 	// add any bind targets to the list of container volumes
@@ -316,7 +332,7 @@
 		Tty:             *flTty,
 		NetworkDisabled: !*flNetwork,
 		OpenStdin:       *flStdin,
-		Memory:          *flMemory,
+		Memory:          flMemory,
 		CpuShares:       *flCpuShares,
 		AttachStdin:     flAttach.Get("stdin"),
 		AttachStdout:    flAttach.Get("stdout"),
@@ -341,7 +357,7 @@
 		PublishAllPorts: *flPublishAll,
 	}
 
-	if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
+	if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
 		config.MemorySwap = -1
 	}
@@ -694,24 +710,25 @@
 func (container *Container) Start() (err error) {
 	container.State.Lock()
 	defer container.State.Unlock()
+	if container.State.Running {
+		return fmt.Errorf("The container %s is already running.", container.ID)
+	}
 	defer func() {
 		if err != nil {
 			container.cleanup()
 		}
 	}()
-
-	if container.State.Running {
-		return fmt.Errorf("The container %s is already running.", container.ID)
-	}
 	if err := container.EnsureMounted(); err != nil {
 		return err
 	}
 	if container.runtime.networkManager.disabled {
 		container.Config.NetworkDisabled = true
+		container.buildHostnameAndHostsFiles("127.0.1.1")
 	} else {
 		if err := container.allocateNetwork(); err != nil {
 			return err
 		}
+		container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
 	}
 
 	// Make sure the config is compatible with the current kernel
@@ -771,9 +788,23 @@
 
 	// Apply volumes from another container if requested
 	if container.Config.VolumesFrom != "" {
-		volumes := strings.Split(container.Config.VolumesFrom, ",")
-		for _, v := range volumes {
-			c := container.runtime.Get(v)
+		containerSpecs := strings.Split(container.Config.VolumesFrom, ",")
+		for _, containerSpec := range containerSpecs {
+			mountRW := true
+			specParts := strings.SplitN(containerSpec, ":", 2)
+			switch len(specParts) {
+			case 0:
+				return fmt.Errorf("Malformed volumes-from specification: %s", container.Config.VolumesFrom)
+			case 2:
+				switch specParts[1] {
+				case "ro":
+					mountRW = false
+				case "rw": // mountRW is already true
+				default:
+					return fmt.Errorf("Malformed volumes-from speficication: %s", containerSpec)
+				}
+			}
+			c := container.runtime.Get(specParts[0])
 			if c == nil {
 				return fmt.Errorf("Container %s not found. Impossible to mount its volumes", container.ID)
 			}
@@ -786,7 +817,7 @@
 				}
 				container.Volumes[volPath] = id
 				if isRW, exists := c.VolumesRW[volPath]; exists {
-					container.VolumesRW[volPath] = isRW
+					container.VolumesRW[volPath] = isRW && mountRW
 				}
 			}
 
@@ -832,7 +863,7 @@
 		// Create the mountpoint
 		rootVolPath := path.Join(container.RootfsPath(), volPath)
 		if err := os.MkdirAll(rootVolPath, 0755); err != nil {
-			return nil
+			return err
 		}
 
 		// Do not copy or change permissions if we are mounting from the host
@@ -876,7 +907,13 @@
 		return err
 	}
 
+	var lxcStart string = "lxc-start"
+	if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
+		lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
+	}
+
 	params := []string{
+		lxcStart,
 		"-n", container.ID,
 		"-f", container.lxcConfigPath(),
 		"--",
@@ -969,11 +1006,24 @@
 	params = append(params, "--", container.Path)
 	params = append(params, container.Args...)
 
-	var lxcStart string = "lxc-start"
-	if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor {
-		lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined")
+	if RootIsShared() {
+		// lxc-start really needs / to be non-shared, or all kinds of stuff break
+		// when lxc-start unmount things and those unmounts propagate to the main
+		// mount namespace.
+		// What we really want is to clone into a new namespace and then
+		// mount / MS_REC|MS_SLAVE, but since we can't really clone or fork
+		// without exec in go we have to do this horrible shell hack...
+		shellString :=
+			"mount --make-rslave /; exec " +
+				utils.ShellQuoteArguments(params)
+
+		params = []string{
+			"unshare", "-m", "--", "/bin/sh", "-c", shellString,
+		}
 	}
-	container.cmd = exec.Command(lxcStart, params...)
+
+	container.cmd = exec.Command(params[0], params[1:]...)
+
 	// Setup logging of stdout and stderr to disk
 	if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil {
 		return err
@@ -1082,6 +1132,30 @@
 	return utils.NewBufReader(reader), nil
 }
 
+func (container *Container) buildHostnameAndHostsFiles(IP string) {
+	container.HostnamePath = path.Join(container.root, "hostname")
+	ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
+
+	hostsContent := []byte(`
+127.0.0.1	localhost
+::1		localhost ip6-localhost ip6-loopback
+fe00::0		ip6-localnet
+ff00::0		ip6-mcastprefix
+ff02::1		ip6-allnodes
+ff02::2		ip6-allrouters
+`)
+
+	container.HostsPath = path.Join(container.root, "hosts")
+
+	if container.Config.Domainname != "" {
+		hostsContent = append([]byte(fmt.Sprintf("%s\t%s.%s %s\n", IP, container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
+	} else {
+		hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...)
+	}
+
+	ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
+}
+
 func (container *Container) allocateNetwork() error {
 	if container.Config.NetworkDisabled {
 		return nil
@@ -1230,7 +1304,7 @@
 	container.State.setStopped(exitCode)
 
 	if container.runtime != nil && container.runtime.srv != nil {
-		container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image))
+		container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image))
 	}
 
 	// Cleanup
@@ -1297,7 +1371,7 @@
 	}
 
 	if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil {
-		log.Printf("error killing container %s (%s, %s)", container.ShortID(), output, err)
+		log.Printf("error killing container %s (%s, %s)", utils.TruncateID(container.ID), output, err)
 		return err
 	}
 
@@ -1317,9 +1391,9 @@
 	// 2. Wait for the process to die, in last resort, try to kill the process directly
 	if err := container.WaitTimeout(10 * time.Second); err != nil {
 		if container.cmd == nil {
-			return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", container.ShortID())
+			return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID))
 		}
-		log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", container.ShortID())
+		log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID))
 		if err := container.cmd.Process.Kill(); err != nil {
 			return err
 		}
@@ -1433,14 +1507,6 @@
 	return container.runtime.Unmount(container)
 }
 
-// ShortID returns a shorthand version of the container's id for convenience.
-// A collision with other container shorthands is very unlikely, but possible.
-// In case of a collision a lookup with Runtime.Get() will fail, and the caller
-// will need to use a langer prefix, or the full-length container Id.
-func (container *Container) ShortID() string {
-	return utils.TruncateID(container.ID)
-}
-
 func (container *Container) logPath(name string) string {
 	return path.Join(container.root, fmt.Sprintf("%s-%s.log", container.ID, name))
 }
diff --git a/container_test.go b/container_test.go
index d182f42..8899fbf 100644
--- a/container_test.go
+++ b/container_test.go
@@ -3,6 +3,7 @@
 import (
 	"bufio"
 	"fmt"
+	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
 	"math/rand"
@@ -1005,7 +1006,7 @@
 		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
 		"HOME=/",
 		"container=lxc",
-		"HOSTNAME=" + container.ShortID(),
+		"HOSTNAME=" + utils.TruncateID(container.ID),
 		"FALSE=true",
 		"TRUE=false",
 		"TRICKY=tri",
@@ -1338,6 +1339,67 @@
 	}
 }
 
+// Test that -volumes-from supports both read-only mounts
+func TestFromVolumesInReadonlyMode(t *testing.T) {
+	runtime := mkRuntime(t)
+	defer nuke(runtime)
+	container, _, err := runtime.Create(
+		&Config{
+			Image:   GetTestImage(runtime).ID,
+			Cmd:     []string{"/bin/echo", "-n", "foobar"},
+			Volumes: map[string]struct{}{"/test": {}},
+		},
+		"",
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container)
+	_, err = container.Output()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !container.VolumesRW["/test"] {
+		t.Fail()
+	}
+
+	container2, _, err := runtime.Create(
+		&Config{
+			Image:       GetTestImage(runtime).ID,
+			Cmd:         []string{"/bin/echo", "-n", "foobar"},
+			VolumesFrom: container.ID + ":ro",
+		},
+		"",
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer runtime.Destroy(container2)
+
+	_, err = container2.Output()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if container.Volumes["/test"] != container2.Volumes["/test"] {
+		t.Logf("container volumes do not match: %s | %s ",
+			container.Volumes["/test"],
+			container2.Volumes["/test"])
+		t.Fail()
+	}
+
+	_, exists := container2.VolumesRW["/test"]
+	if !exists {
+		t.Logf("container2 is missing '/test' volume: %s", container2.VolumesRW)
+		t.Fail()
+	}
+
+	if container2.VolumesRW["/test"] != false {
+		t.Log("'/test' volume mounted in read-write mode, expected read-only")
+		t.Fail()
+	}
+}
+
 // Test that VolumesRW values are copied to the new container.  Regression test for #1201
 func TestVolumesFromReadonlyMount(t *testing.T) {
 	runtime := mkRuntime(t)
diff --git a/contrib/init/sysvinit/docker b/contrib/init/sysvinit/docker
index 6c0e182..2d79c4d 100755
--- a/contrib/init/sysvinit/docker
+++ b/contrib/init/sysvinit/docker
@@ -29,7 +29,9 @@
 	. /etc/default/$BASE
 fi
 
-if [ "$1" = start ] && which initctl >/dev/null && initctl version | grep -q upstart; then
+# see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it)
+if [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | /bin/grep -q upstart; then
+	log_failure_msg "Docker is managed via upstart, try using service $BASE $1"
 	exit 1
 fi
 
diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf
index 72c132c..ee8a447 100644
--- a/contrib/init/upstart/docker.conf
+++ b/contrib/init/upstart/docker.conf
@@ -6,5 +6,10 @@
 respawn
 
 script
-	/usr/bin/docker -d
+	DOCKER=/usr/bin/$UPSTART_JOB
+	DOCKER_OPTS=
+	if [ -f /etc/default/$UPSTART_JOB ]; then
+		. /etc/default/$UPSTART_JOB
+	fi
+	"$DOCKER" -d $DOCKER_OPTS
 end script
diff --git a/contrib/vagrant-docker/README.md b/contrib/vagrant-docker/README.md
index 5852ea1..fa28e44 100644
--- a/contrib/vagrant-docker/README.md
+++ b/contrib/vagrant-docker/README.md
@@ -1,3 +1,19 @@
-# Vagrant-docker
+# Vagrant integration
 
-This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
+Currently there are at least 4 different projects that we are aware of that deals
+with integration with [Vagrant](http://vagrantup.com/) at different levels. One
+approach is to use Docker as a [provisioner](http://docs.vagrantup.com/v2/provisioning/index.html)
+which means you can create containers and pull base images on VMs using Docker's
+CLI and the other is to use Docker as a [provider](http://docs.vagrantup.com/v2/providers/index.html),
+meaning you can use Vagrant to control Docker containers.
+
+
+### Provisioners
+
+* [Vocker](https://github.com/fgrehm/vocker)
+* [Ventriloquist](https://github.com/fgrehm/ventriloquist)
+
+### Providers
+
+* [docker-provider](https://github.com/fgrehm/docker-provider)
+* [vagrant-shell](https://github.com/destructuring/vagrant-shell)
diff --git a/docker/docker.go b/docker/docker.go
index 877e7f6..d7d46ff 100644
--- a/docker/docker.go
+++ b/docker/docker.go
@@ -71,7 +71,8 @@
 		if err != nil {
 			log.Fatal(err)
 		}
-		job := eng.Job("serveapi")
+		// Load plugin: httpapi
+		job := eng.Job("initapi")
 		job.Setenv("Pidfile", *pidfile)
 		job.Setenv("Root", *flRoot)
 		job.SetenvBool("AutoRestart", *flAutoRestart)
@@ -79,12 +80,17 @@
 		job.Setenv("Dns", *flDns)
 		job.SetenvBool("EnableIptables", *flEnableIptables)
 		job.Setenv("BridgeIface", *bridgeName)
-		job.SetenvList("ProtoAddresses", flHosts)
 		job.Setenv("DefaultIp", *flDefaultIp)
 		job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
 		if err := job.Run(); err != nil {
 			log.Fatal(err)
 		}
+		// Serve api
+		job = eng.Job("serveapi", flHosts...)
+		job.SetenvBool("Logging", true)
+		if err := job.Run(); err != nil {
+			log.Fatal(err)
+		}
 	} else {
 		if len(flHosts) > 1 {
 			log.Fatal("Please specify only one -H")
diff --git a/docs/sources/api/docker_remote_api_v1.6.rst b/docs/sources/api/docker_remote_api_v1.6.rst
index 461ffef..25be478 100644
--- a/docs/sources/api/docker_remote_api_v1.6.rst
+++ b/docs/sources/api/docker_remote_api_v1.6.rst
@@ -121,8 +121,7 @@
 		"AttachStdin":false,
 		"AttachStdout":true,
 		"AttachStderr":true,
-		"PortSpecs":null,
-		"Privileged": false,
+		"ExposedPorts":{},
 		"Tty":false,
 		"OpenStdin":false,
 		"StdinOnce":false,
@@ -135,7 +134,6 @@
 		"Volumes":{},
 		"VolumesFrom":"",
 		"WorkingDir":""
-
 	   }
 	   
 	**Example response**:
@@ -242,7 +240,7 @@
 				"AttachStdin": false,
 				"AttachStdout": true,
 				"AttachStderr": true,
-				"PortSpecs": null,
+				"ExposedPorts": {},
 				"Tty": false,
 				"OpenStdin": false,
 				"StdinOnce": false,
@@ -413,7 +411,12 @@
 
            {
                 "Binds":["/tmp:/tmp"],
-                "LxcConf":{"lxc.utsname":"docker"}
+                "LxcConf":{"lxc.utsname":"docker"},
+                "ContainerIDFile": "",
+                "Privileged": false,
+                "PortBindings": {"22/tcp": [{HostIp:"", HostPort:""}]},
+                "Links": [],
+                "PublishAllPorts": false
            }
 
         **Example response**:
@@ -846,7 +849,7 @@
 				"AttachStdin":false,
 				"AttachStdout":false,
 				"AttachStderr":false,
-				"PortSpecs":null,
+				"ExposedPorts":{},
 				"Tty":true,
 				"OpenStdin":true,
 				"StdinOnce":false,
@@ -1192,7 +1195,7 @@
        
        {
            "Cmd": ["cat", "/world"],
-           "PortSpecs":["22"]
+           "ExposedPorts":{"22/tcp":{}}
        }
 
     **Example response**:
diff --git a/docs/sources/api/docker_remote_api_v1.7.rst b/docs/sources/api/docker_remote_api_v1.7.rst
index 3985f7a..0e848d4 100644
--- a/docs/sources/api/docker_remote_api_v1.7.rst
+++ b/docs/sources/api/docker_remote_api_v1.7.rst
@@ -914,7 +914,12 @@
 
 .. http:get:: /images/search
 
-	Search for an image in the docker index
+	Search for an image in the docker index.
+	
+	.. note::
+	
+	   The response keys have changed from API v1.6 to reflect the JSON 
+	   sent by the registry server to the docker daemon's request.
 	
 	**Example request**:
 
@@ -930,18 +935,28 @@
 	   Content-Type: application/json
 	   
 	   [
-		{
-			"Name":"cespare/sshd",
-			"Description":""
-		},
-		{
-			"Name":"johnfuller/sshd",
-			"Description":""
-		},
-		{
-			"Name":"dhrp/mongodb-sshd",
-			"Description":""
-		}
+		   {
+		       "description": "",
+		       "is_official": false,
+		       "is_trusted": false,
+		       "name": "wma55/u1210sshd",
+		       "star_count": 0
+		   },
+		   {
+		       "description": "",
+		       "is_official": false,
+		       "is_trusted": false,
+		       "name": "jdswinbank/sshd",
+		       "star_count": 0
+		   },
+		   {
+		       "description": "",
+		       "is_official": false,
+		       "is_trusted": false,
+		       "name": "vgauthier/sshd",
+		       "star_count": 0
+		   }
+	   ...
 	   ]
 
 	   :query term: term to search
diff --git a/docs/sources/api/remote_api_client_libraries.rst b/docs/sources/api/remote_api_client_libraries.rst
index bd8610e..f00ab1c 100644
--- a/docs/sources/api/remote_api_client_libraries.rst
+++ b/docs/sources/api/remote_api_client_libraries.rst
@@ -12,26 +12,28 @@
 find more library implementations, please list them in Docker doc bugs
 and we will add the libraries here.
 
-+----------------------+----------------+--------------------------------------------+
-| Language/Framework   | Name           | Repository                                 |
-+======================+================+============================================+
-| Python               | docker-py      | https://github.com/dotcloud/docker-py      |
-+----------------------+----------------+--------------------------------------------+
-| Ruby                 | docker-client  | https://github.com/geku/docker-client      |
-+----------------------+----------------+--------------------------------------------+
-| Ruby                 | docker-api     | https://github.com/swipely/docker-api      |
-+----------------------+----------------+--------------------------------------------+
-| Javascript (NodeJS)  | docker.io      | https://github.com/appersonlabs/docker.io  |
-|                      |                | Install via NPM: `npm install docker.io`   |
-+----------------------+----------------+--------------------------------------------+
-| Javascript           | docker-js      | https://github.com/dgoujard/docker-js      |
-+----------------------+----------------+--------------------------------------------+
-| Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  |
-| **WebUI**            |                |                                            |
-+----------------------+----------------+--------------------------------------------+
-| Java                 | docker-java    | https://github.com/kpelykh/docker-java     |
-+----------------------+----------------+--------------------------------------------+
-| Erlang               | erldocker      | https://github.com/proger/erldocker        |
-+----------------------+----------------+--------------------------------------------+
-| Go                   | go-dockerclient| https://github.com/fsouza/go-dockerclient  |
-+----------------------+----------------+--------------------------------------------+
++----------------------+----------------+--------------------------------------------+----------+
+| Language/Framework   | Name           | Repository                                 | Status   |
++======================+================+============================================+==========+
+| Python               | docker-py      | https://github.com/dotcloud/docker-py      | Active   |
++----------------------+----------------+--------------------------------------------+----------+
+| Ruby                 | docker-client  | https://github.com/geku/docker-client      | Outdated |
++----------------------+----------------+--------------------------------------------+----------+
+| Ruby                 | docker-api     | https://github.com/swipely/docker-api      | Active   |
++----------------------+----------------+--------------------------------------------+----------+
+| Javascript (NodeJS)  | docker.io      | https://github.com/appersonlabs/docker.io  | Active   |
+|                      |                | Install via NPM: `npm install docker.io`   |          |
++----------------------+----------------+--------------------------------------------+----------+
+| Javascript           | docker-js      | https://github.com/dgoujard/docker-js      | Active   |
++----------------------+----------------+--------------------------------------------+----------+
+| Javascript (Angular) | dockerui       | https://github.com/crosbymichael/dockerui  | Active   |
+| **WebUI**            |                |                                            |          |
++----------------------+----------------+--------------------------------------------+----------+
+| Java                 | docker-java    | https://github.com/kpelykh/docker-java     | Active   |
++----------------------+----------------+--------------------------------------------+----------+
+| Erlang               | erldocker      | https://github.com/proger/erldocker        | Active   |
++----------------------+----------------+--------------------------------------------+----------+
+| Go                   | go-dockerclient| https://github.com/fsouza/go-dockerclient  | Active   |
++----------------------+----------------+--------------------------------------------+----------+
+| PHP                  | Alvine         | http://pear.alvine.io/ (alpha)             | Active   |
++----------------------+----------------+--------------------------------------------+----------+
diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst
index 6d56bcc..37f371b 100644
--- a/docs/sources/commandline/cli.rst
+++ b/docs/sources/commandline/cli.rst
@@ -245,6 +245,9 @@
     Usage: docker events
 
     Get real time events from the server
+    
+    -since="": Show previously created events and then stream.
+               (either seconds since epoch, or date string as below)
 
 .. _cli_events_example:
 
@@ -277,6 +280,23 @@
     [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
     [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
 
+Show events in the past from a specified time
+.............................................
+
+.. code-block:: bash
+
+    $ sudo docker events -since 1378216169
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
+
+    $ sudo docker events -since '2013-09-03'
+    [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
+
+    $ sudo docker events -since '2013-09-03 15:49:29 +0200 CEST'
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die
+    [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop
 
 .. _cli_export:
 
@@ -460,6 +480,12 @@
 
 The main process inside the container will be sent SIGKILL.
 
+Known Issues (kill)
+~~~~~~~~~~~~~~~~~~~
+
+* :issue:`197` indicates that ``docker kill`` may leave directories
+  behind and make it difficult to remove the container.
+
 .. _cli_login:
 
 ``login``
@@ -568,6 +594,12 @@
     Remove one or more containers
         -link="": Remove the link instead of the actual container
 
+Known Issues (rm)
+~~~~~~~~~~~~~~~~~~~
+
+* :issue:`197` indicates that ``docker kill`` may leave directories
+  behind and make it difficult to remove the container.
+
 
 Examples:
 ~~~~~~~~~
@@ -590,6 +622,15 @@
 This will remove the underlying link between ``/webapp`` and the ``/redis`` containers removing all
 network communication.
 
+.. code-block:: bash
+
+    $ docker rm `docker ps -a -q`
+
+
+This command will delete all stopped containers. The command ``docker ps -a -q`` will return all
+existing container IDs and pass them to the ``rm`` command which will delete them. Any running
+containers will not be deleted.
+
 .. _cli_rmi:
 
 ``rmi``
@@ -620,7 +661,7 @@
       -h="": Container host name
       -i=false: Keep stdin open even if not attached
       -privileged=false: Give extended privileges to this container
-      -m=0: Memory limit (in bytes)
+      -m="": Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
       -n=true: Enable networking for this container
       -p=[]: Map a network port to the container
       -rm=false: Automatically remove the container when it exits (incompatible with -d)
@@ -628,7 +669,7 @@
       -u="": Username or UID
       -dns=[]: Set custom dns servers for the container
       -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume.
-      -volumes-from="": Mount all volumes from the given container
+      -volumes-from="": Mount all volumes from the given container(s)
       -entrypoint="": Overwrite the default entrypoint set by the image
       -w="": Working directory inside the container
       -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
@@ -720,6 +761,17 @@
 environment variables.  The ``-name`` flag will assign the name ``console``
 to the newly created container.
 
+.. code-block:: bash
+
+   docker run -volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd
+
+The ``-volumes-from`` flag mounts all the defined volumes from the
+refrence containers. Containers can be specified by a comma seperated
+list or by repetitions of the ``-volumes-from`` argument. The container
+id may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in
+read-only or read-write mode, respectively. By default, the volumes are mounted
+in the same mode (rw or ro) as the reference container.
+
 .. _cli_search:
 
 ``search``
diff --git a/docs/sources/conf.py b/docs/sources/conf.py
index 7aa27c7..0ccd4a4 100644
--- a/docs/sources/conf.py
+++ b/docs/sources/conf.py
@@ -40,7 +40,11 @@
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinxcontrib.httpdomain']
+extensions = ['sphinxcontrib.httpdomain', 'sphinx.ext.extlinks']
+
+# Configure extlinks
+extlinks = { 'issue': ('https://github.com/dotcloud/docker/issues/%s',
+	               'Issue ') }
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
diff --git a/docs/sources/contributing/contributing.rst b/docs/sources/contributing/contributing.rst
index 3cdb0b6..3b3b3f8 100644
--- a/docs/sources/contributing/contributing.rst
+++ b/docs/sources/contributing/contributing.rst
@@ -10,13 +10,16 @@
 The repository includes `all the instructions you need to get
 started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_.
 
-The developer environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
+The `developer environment Dockerfile
+<https://github.com/dotcloud/docker/blob/master/Dockerfile>`_
 specifies the tools and versions used to test and build Docker.
 
 If you're making changes to the documentation, see the
 `README.md <https://github.com/dotcloud/docker/blob/master/docs/README.md>`_.
 
-The documentation environment `Dockerfile <https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
+The `documentation environment Dockerfile
+<https://github.com/dotcloud/docker/blob/master/docs/Dockerfile>`_
 specifies the tools and versions used to build the Documentation.
 
-Further interesting details can be found in the `Packaging hints <https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
+Further interesting details can be found in the `Packaging hints
+<https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md>`_.
diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst
index c707fcd..e3f5f5e 100644
--- a/docs/sources/examples/python_web_app.rst
+++ b/docs/sources/examples/python_web_app.rst
@@ -86,7 +86,7 @@
 
 .. code-block:: bash
 
-    WEB_PORT=$(sudo docker port $WEB_WORKER 5000)
+    WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }')
 
 Look up the public-facing port which is NAT-ed. Find the private port
 used by the container and store it inside of the ``WEB_PORT`` variable.
diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst
index c6c8104..6d92046 100644
--- a/docs/sources/installation/amazon.rst
+++ b/docs/sources/installation/amazon.rst
@@ -102,26 +102,45 @@
    we need to set them there first. Make sure you have everything on
    amazon aws setup so you can (manually) deploy a new image to EC2.
 
+   Note that where possible these variables are the same as those honored by
+   the ec2 api tools.
    ::
 
-       export AWS_ACCESS_KEY_ID=xxx
-       export AWS_SECRET_ACCESS_KEY=xxx
+       export AWS_ACCESS_KEY=xxx
+       export AWS_SECRET_KEY=xxx
        export AWS_KEYPAIR_NAME=xxx
-       export AWS_SSH_PRIVKEY=xxx
+       export SSH_PRIVKEY_PATH=xxx
 
-   The environment variables are:
+       export BOX_NAME=xxx
+       export AWS_REGION=xxx
+       export AWS_AMI=xxx
+       export AWS_INSTANCE_TYPE=xxx
 
-   * ``AWS_ACCESS_KEY_ID`` - The API key used to make requests to AWS
-   * ``AWS_SECRET_ACCESS_KEY`` - The secret key to make AWS API requests
+   The required environment variables are:
+
+   * ``AWS_ACCESS_KEY`` - The API key used to make requests to AWS
+   * ``AWS_SECRET_KEY`` - The secret key to make AWS API requests
    * ``AWS_KEYPAIR_NAME`` - The name of the keypair used for this EC2 instance
-   * ``AWS_SSH_PRIVKEY`` - The path to the private key for the named
+   * ``SSH_PRIVKEY_PATH`` - The path to the private key for the named
      keypair, for example ``~/.ssh/docker.pem``
 
+   There are a number of optional environment variables:
+
+   * ``BOX_NAME`` - The name of the vagrant box to use.  Defaults to
+     ``ubuntu``.
+   * ``AWS_REGION`` - The aws region to spawn the vm in.  Defaults to
+     ``us-east-1``.
+   * ``AWS_AMI`` - The aws AMI to start with as a base.  This must be
+     be an ubuntu 12.04 precise image.  You must change this value if
+     ``AWS_REGION`` is set to a value other than ``us-east-1``.
+     This is because AMIs are region specific.  Defaults to ``ami-69f5a900``.
+   * ``AWS_INSTANCE_TYPE`` - The aws instance type.  Defaults to ``t1.micro``.
+
    You can check if they are set correctly by doing something like
 
    ::
 
-      echo $AWS_ACCESS_KEY_ID
+      echo $AWS_ACCESS_KEY
 
 6. Do the magic!
 
diff --git a/docs/sources/terms/container.rst b/docs/sources/terms/container.rst
index aeb7b1c..206664b 100644
--- a/docs/sources/terms/container.rst
+++ b/docs/sources/terms/container.rst
@@ -38,3 +38,10 @@
 You can promote a container to an :ref:`image_def` with ``docker
 commit``. Once a container is an image, you can use it as a parent for
 new containers.
+
+Container IDs
+.............
+All containers are identified by a 64 hexadecimal digit string (internally a 256bit 
+value). To simplify their use, a short ID of the first 12 characters can be used 
+on the commandline. There is a small possibility of short id collisions, so the 
+docker server will always return the long ID.
diff --git a/docs/sources/terms/image.rst b/docs/sources/terms/image.rst
index dafda1f..6d5c8b2 100644
--- a/docs/sources/terms/image.rst
+++ b/docs/sources/terms/image.rst
@@ -36,3 +36,11 @@
 ..........
 
 An image that has no parent is a **base image**.
+
+Image IDs
+.........
+All images are identified by a 64 hexadecimal digit string (internally a 256bit 
+value). To simplify their use, a short ID of the first 12 characters can be used 
+on the command line. There is a small possibility of short id collisions, so the 
+docker server will always return the long ID.
+
diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst
index 0097b68..d1ad081 100644
--- a/docs/sources/use/basics.rst
+++ b/docs/sources/use/basics.rst
@@ -22,22 +22,37 @@
     # Run docker in daemon mode
     sudo <path to>/docker -d &
 
-
-Running an interactive shell
-----------------------------
+Download a pre-built image
+--------------------------
 
 .. code-block:: bash
 
   # Download an ubuntu image
   sudo docker pull ubuntu
 
+This will find the ``ubuntu`` image by name in the :ref:`Central Index 
+<searching_central_index>` and download it from the top-level Central 
+Repository to a local image cache.
+
+.. NOTE:: When the image has successfully downloaded, you will see a 12 
+character hash ``539c0211cd76: Download complete`` which is the short 
+form of the image ID. These short image IDs are the first 12 characters 
+of the full image ID - which can be found using ``docker inspect`` or 
+``docker images -notrunc=true``
+
+.. _dockergroup:
+
+Running an interactive shell
+----------------------------
+
+.. code-block:: bash
+
   # Run an interactive shell in the ubuntu image,
   # allocate a tty, attach stdin and stdout
   # To detach the tty without exiting the shell,
   # use the escape sequence Ctrl-p + Ctrl-q
   sudo docker run -i -t ubuntu /bin/bash
 
-.. _dockergroup:
 
 Why ``sudo``?
 -------------
diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst
index d1747c3..7c9d339 100644
--- a/docs/sources/use/builder.rst
+++ b/docs/sources/use/builder.rst
@@ -116,6 +116,16 @@
 created from any point in an image's history, much like source
 control.
 
+Known Issues (RUN)
+..................
+
+* :issue:`783` is about file permissions problems that can occur when
+  using the AUFS file system. You might notice it during an attempt to
+  ``rm`` a file, for example. The issue describes a workaround.
+* :issue:`2424` Locale will not be set automatically.
+
+
+
 3.4 CMD
 -------
 
@@ -211,8 +221,16 @@
 All new files and directories are created with mode 0755, uid and gid
 0.
 
+.. note::
+   if you build using STDIN (``docker build - < somefile``), there is no build 
+   context, so the Dockerfile can only contain an URL based ADD statement.
+
 The copy obeys the following rules:
 
+* The ``<src>`` path must be inside the *context* of the build; you cannot 
+  ``ADD ../something /something``, because the first step of a 
+  ``docker build`` is to send the context directory (and subdirectories) to 
+  the docker daemon.
 * If ``<src>`` is a URL and ``<dest>`` does not end with a trailing slash,
   then a file is downloaded from the URL and copied to ``<dest>``.
 * If ``<src>`` is a URL and ``<dest>`` does end with a trailing slash,
diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst
index 914d5a2..38f61ba 100644
--- a/docs/sources/use/index.rst
+++ b/docs/sources/use/index.rst
@@ -20,3 +20,4 @@
    puppet
    host_integration
    working_with_volumes
+   working_with_links_names
diff --git a/docs/sources/use/working_with_links_names.rst b/docs/sources/use/working_with_links_names.rst
new file mode 100644
index 0000000..02906a8
--- /dev/null
+++ b/docs/sources/use/working_with_links_names.rst
@@ -0,0 +1,104 @@
+:title: Working with Links and Names
+:description: How to create and use links and names
+:keywords: Examples, Usage, links, docker, documentation, examples, names, name, container naming
+
+.. _working_with_links_names:
+
+Working with Links and Names
+============================
+
+From version 0.6.5 you are now able to ``name`` a container and ``link`` it to another
+container by referring to its name. This will create a parent -> child relationship
+where the parent container can see selected information about its child.
+
+.. _run_name:
+
+Container Naming
+----------------
+
+.. versionadded:: v0.6.5
+
+You can now name your container by using the ``-name`` flag. If no name is provided, Docker
+will automatically generate a name. You can see this name using the ``docker ps`` command.
+
+.. code-block:: bash
+
+    # format is "sudo docker run -name <container_name> <image_name> <command>"
+    $ sudo docker run -name test ubuntu /bin/bash
+
+    # the flag "-a" Show all containers. Only running containers are shown by default.
+    $ sudo docker ps -a
+    CONTAINER ID        IMAGE                            COMMAND             CREATED             STATUS              PORTS               NAMES
+    2522602a0d99        ubuntu:12.04                     /bin/bash           14 seconds ago      Exit 0                                  test
+
+.. _run_link:
+
+Links: service discovery for docker
+-----------------------------------
+
+.. versionadded:: v0.6.5
+
+Links allow containers to discover and securely communicate with each other by using the
+flag ``-link name:alias``. Inter-container communication can be disabled with the daemon
+flag ``-icc=false``. With this flag set to false, Container A cannot access Container B
+unless explicitly allowed via a link. This is a huge win for securing your containers.
+When two containers are linked together Docker creates a parent child relationship
+between the containers. The parent container will be able to access information via
+environment variables of the child such as name, exposed ports, IP and other selected
+environment variables.
+
+When linking two containers Docker will use the exposed ports of the container to create
+a secure tunnel for the parent to access. If a database container only exposes port 8080
+then the linked container will only be allowed to access port 8080 and nothing else if
+inter-container communication is set to false.
+
+.. code-block:: bash
+
+    # Example: there is an image called redis-2.6 that exposes the port 6379 and starts redis-server.
+    # Let's name the container as "redis" based on that image and run it as daemon.
+    $ sudo docker run -d -name redis redis-2.6
+
+We can issue all the commands that you would expect using the name "redis"; start, stop,
+attach, using the name for our container. The name also allows us to link other containers
+into this one.
+
+Next, we can start a new web application that has a dependency on Redis and apply a link
+to connect both containers. If you noticed when running our Redis server we did not use
+the -p flag to publish the Redis port to the host system. Redis exposed port 6379 and
+this is all we need to establish a link.
+
+.. code-block:: bash
+
+    # Linking the redis container as a child
+    $ sudo docker run -t -i -link redis:db -name webapp ubuntu bash
+
+When you specified -link redis:db you are telling docker to link the container named redis
+into this new container with the alias db. Environment variables are prefixed with the alias
+so that the parent container can access network and environment information from the containers
+that are linked into it.
+
+If we inspect the environment variables of the second container, we would see all the information
+about the child container.
+
+.. code-block:: bash
+
+    $ root@4c01db0b339c:/# env
+
+    HOSTNAME=4c01db0b339c
+    DB_NAME=/webapp/db
+    TERM=xterm
+    DB_PORT=tcp://172.17.0.8:6379
+    DB_PORT_6379_TCP=tcp://172.17.0.8:6379
+    DB_PORT_6379_TCP_PROTO=tcp
+    DB_PORT_6379_TCP_ADDR=172.17.0.8
+    DB_PORT_6379_TCP_PORT=6379
+    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+    PWD=/
+    SHLVL=1
+    HOME=/
+    container=lxc
+    _=/usr/bin/env
+    root@4c01db0b339c:/#
+
+Accessing the network information along with the environment of the child container allows
+us to easily connect to the Redis service on the specific IP and port in the environment.
diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html
index 193df74..ee80341 100755
--- a/docs/theme/docker/layout.html
+++ b/docs/theme/docker/layout.html
@@ -129,7 +129,8 @@
                 <div class="row footer">
                     <div class="span12 tbox">
                         <div class="tbox">
-                            <p>Docker is an open source project, sponsored by  <a href="https://dotcloud.com">dotCloud</a>, under the <a href="https://github.com/dotcloud/docker/blob/master/LICENSE" title="Docker licence, hosted in the Github repository">apache 2.0 licence</a></p>
+                            <p>Docker is an open source project, sponsored by  <a href="https://www.docker.com">Docker Inc.</a>, under the <a href="https://github.com/dotcloud/docker/blob/master/LICENSE" title="Docker licence, hosted in the Github repository">apache 2.0 licence</a></p>
+                            <p>Documentation proudly hosted by <a href="http://www.readthedocs.org">Read the Docs</a></p>
                         </div>
 
                         <div class="social links">
diff --git a/engine/engine.go b/engine/engine.go
index 10cc077..edd04fc 100644
--- a/engine/engine.go
+++ b/engine/engine.go
@@ -6,15 +6,21 @@
 	"log"
 	"os"
 	"runtime"
+	"strings"
 )
 
 type Handler func(*Job) string
 
 var globalHandlers map[string]Handler
 
+func init() {
+	globalHandlers = make(map[string]Handler)
+}
+
 func Register(name string, handler Handler) error {
-	if globalHandlers == nil {
-		globalHandlers = make(map[string]Handler)
+	_, exists := globalHandlers[name]
+	if exists {
+		return fmt.Errorf("Can't overwrite global handler for command %s", name)
 	}
 	globalHandlers[name] = handler
 	return nil
@@ -26,6 +32,22 @@
 type Engine struct {
 	root     string
 	handlers map[string]Handler
+	hack     Hack // data for temporary hackery (see hack.go)
+	id       string
+}
+
+func (eng *Engine) Root() string {
+	return eng.root
+}
+
+func (eng *Engine) Register(name string, handler Handler) error {
+	eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers)
+	_, exists := eng.handlers[name]
+	if exists {
+		return fmt.Errorf("Can't overwrite handler for command %s", name)
+	}
+	eng.handlers[name] = handler
+	return nil
 }
 
 // New initializes a new engine managing the directory specified at `root`.
@@ -56,16 +78,25 @@
 	}
 	eng := &Engine{
 		root:     root,
-		handlers: globalHandlers,
+		handlers: make(map[string]Handler),
+		id:       utils.RandomString(),
+	}
+	// Copy existing global handlers
+	for k, v := range globalHandlers {
+		eng.handlers[k] = v
 	}
 	return eng, nil
 }
 
+func (eng *Engine) String() string {
+	return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8])
+}
+
 // Job creates a new job which can later be executed.
 // This function mimics `Command` from the standard os/exec package.
 func (eng *Engine) Job(name string, args ...string) *Job {
 	job := &Job{
-		eng:    eng,
+		Eng:    eng,
 		Name:   name,
 		Args:   args,
 		Stdin:  os.Stdin,
@@ -78,3 +109,8 @@
 	}
 	return job
 }
+
+func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
+	prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
+	return fmt.Fprintf(os.Stderr, prefixedFormat, args...)
+}
diff --git a/engine/hack.go b/engine/hack.go
new file mode 100644
index 0000000..be4fadb
--- /dev/null
+++ b/engine/hack.go
@@ -0,0 +1,21 @@
+package engine
+
+type Hack map[string]interface{}
+
+func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
+	if eng.hack == nil {
+		return nil
+	}
+	val, exists := eng.hack[key]
+	if !exists {
+		return nil
+	}
+	return val
+}
+
+func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
+	if eng.hack == nil {
+		eng.hack = make(Hack)
+	}
+	eng.hack[key] = val
+}
diff --git a/engine/job.go b/engine/job.go
index c2b14d5..c4a2c3e 100644
--- a/engine/job.go
+++ b/engine/job.go
@@ -1,11 +1,16 @@
 package engine
 
 import (
+	"bufio"
+	"bytes"
 	"encoding/json"
 	"fmt"
-	"github.com/dotcloud/docker/utils"
 	"io"
+	"io/ioutil"
+	"os"
+	"strconv"
 	"strings"
+	"sync"
 )
 
 // A job is the fundamental unit of work in the docker engine.
@@ -22,24 +27,43 @@
 // This allows for richer error reporting.
 //
 type Job struct {
-	eng     *Engine
+	Eng     *Engine
 	Name    string
 	Args    []string
 	env     []string
-	Stdin   io.ReadCloser
-	Stdout  io.WriteCloser
-	Stderr  io.WriteCloser
+	Stdin   io.Reader
+	Stdout  io.Writer
+	Stderr  io.Writer
 	handler func(*Job) string
 	status  string
+	onExit  []func()
 }
 
 // Run executes the job and blocks until the job completes.
 // If the job returns a failure status, an error is returned
 // which includes the status.
 func (job *Job) Run() error {
-	randId := utils.RandomString()[:4]
-	fmt.Printf("Job #%s: %s\n", randId, job)
-	defer fmt.Printf("Job #%s: %s = '%s'", randId, job, job.status)
+	defer func() {
+		var wg sync.WaitGroup
+		for _, f := range job.onExit {
+			wg.Add(1)
+			go func(f func()) {
+				f()
+				wg.Done()
+			}(f)
+		}
+		wg.Wait()
+	}()
+	if job.Stdout != nil && job.Stdout != os.Stdout {
+		job.Stdout = io.MultiWriter(job.Stdout, os.Stdout)
+	}
+	if job.Stderr != nil && job.Stderr != os.Stderr {
+		job.Stderr = io.MultiWriter(job.Stderr, os.Stderr)
+	}
+	job.Eng.Logf("+job %s", job.CallString())
+	defer func() {
+		job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
+	}()
 	if job.handler == nil {
 		job.status = "command not found"
 	} else {
@@ -51,9 +75,87 @@
 	return nil
 }
 
+func (job *Job) StdoutParseLines(dst *[]string, limit int) {
+	job.parseLines(job.StdoutPipe(), dst, limit)
+}
+
+func (job *Job) StderrParseLines(dst *[]string, limit int) {
+	job.parseLines(job.StderrPipe(), dst, limit)
+}
+
+func (job *Job) parseLines(src io.Reader, dst *[]string, limit int) {
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		scanner := bufio.NewScanner(src)
+		for scanner.Scan() {
+			// If the limit is reached, flush the rest of the source and return
+			if limit > 0 && len(*dst) >= limit {
+				io.Copy(ioutil.Discard, src)
+				return
+			}
+			line := scanner.Text()
+			// Append the line (with delimitor removed)
+			*dst = append(*dst, line)
+		}
+	}()
+	job.onExit = append(job.onExit, wg.Wait)
+}
+
+func (job *Job) StdoutParseString(dst *string) {
+	lines := make([]string, 0, 1)
+	job.StdoutParseLines(&lines, 1)
+	job.onExit = append(job.onExit, func() {
+		if len(lines) >= 1 {
+			*dst = lines[0]
+		}
+	})
+}
+
+func (job *Job) StderrParseString(dst *string) {
+	lines := make([]string, 0, 1)
+	job.StderrParseLines(&lines, 1)
+	job.onExit = append(job.onExit, func() { *dst = lines[0] })
+}
+
+func (job *Job) StdoutPipe() io.ReadCloser {
+	r, w := io.Pipe()
+	job.Stdout = w
+	job.onExit = append(job.onExit, func() { w.Close() })
+	return r
+}
+
+func (job *Job) StderrPipe() io.ReadCloser {
+	r, w := io.Pipe()
+	job.Stderr = w
+	job.onExit = append(job.onExit, func() { w.Close() })
+	return r
+}
+
+func (job *Job) CallString() string {
+	return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
+}
+
+func (job *Job) StatusString() string {
+	// FIXME: if a job returns the empty string, it will be printed
+	// as not having returned.
+	// (this only affects String which is a convenience function).
+	if job.status != "" {
+		var okerr string
+		if job.status == "0" {
+			okerr = "OK"
+		} else {
+			okerr = "ERR"
+		}
+		return fmt.Sprintf(" = %s (%s)", okerr, job.status)
+	}
+	return ""
+}
+
 // String returns a human-readable description of `job`
 func (job *Job) String() string {
-	return strings.Join(append([]string{job.Name}, job.Args...), " ")
+	return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
 }
 
 func (job *Job) Getenv(key string) (value string) {
@@ -90,6 +192,19 @@
 	}
 }
 
+func (job *Job) GetenvInt(key string) int64 {
+	s := strings.Trim(job.Getenv(key), " \t")
+	val, err := strconv.ParseInt(s, 10, 64)
+	if err != nil {
+		return -1
+	}
+	return val
+}
+
+func (job *Job) SetenvInt(key string, value int64) {
+	job.Setenv(key, fmt.Sprintf("%d", value))
+}
+
 func (job *Job) GetenvList(key string) []string {
 	sval := job.Getenv(key)
 	l := make([]string, 0, 1)
@@ -111,3 +226,109 @@
 func (job *Job) Setenv(key, value string) {
 	job.env = append(job.env, key+"="+value)
 }
+
+// DecodeEnv decodes `src` as a json dictionary, and adds
+// each decoded key-value pair to the environment.
+//
+// If `text` cannot be decoded as a json dictionary, an error
+// is returned.
+func (job *Job) DecodeEnv(src io.Reader) error {
+	m := make(map[string]interface{})
+	if err := json.NewDecoder(src).Decode(&m); err != nil {
+		return err
+	}
+	for k, v := range m {
+		// FIXME: we fix-convert float values to int, because
+		// encoding/json decodes integers to float64, but cannot encode them back.
+		// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
+		if fval, ok := v.(float64); ok {
+			job.SetenvInt(k, int64(fval))
+		} else if sval, ok := v.(string); ok {
+			job.Setenv(k, sval)
+		} else if val, err := json.Marshal(v); err == nil {
+			job.Setenv(k, string(val))
+		} else {
+			job.Setenv(k, fmt.Sprintf("%v", v))
+		}
+	}
+	return nil
+}
+
+func (job *Job) EncodeEnv(dst io.Writer) error {
+	m := make(map[string]interface{})
+	for k, v := range job.Environ() {
+		var val interface{}
+		if err := json.Unmarshal([]byte(v), &val); err == nil {
+			// FIXME: we fix-convert float values to int, because
+			// encoding/json decodes integers to float64, but cannot encode them back.
+			// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
+			if fval, isFloat := val.(float64); isFloat {
+				val = int(fval)
+			}
+			m[k] = val
+		} else {
+			m[k] = v
+		}
+	}
+	if err := json.NewEncoder(dst).Encode(&m); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (job *Job) ExportEnv(dst interface{}) (err error) {
+	defer func() {
+		if err != nil {
+			err = fmt.Errorf("ExportEnv %s", err)
+		}
+	}()
+	var buf bytes.Buffer
+	// step 1: encode/marshal the env to an intermediary json representation
+	if err := job.EncodeEnv(&buf); err != nil {
+		return err
+	}
+	// step 2: decode/unmarshal the intermediary json into the destination object
+	if err := json.NewDecoder(&buf).Decode(dst); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (job *Job) ImportEnv(src interface{}) (err error) {
+	defer func() {
+		if err != nil {
+			err = fmt.Errorf("ImportEnv: %s", err)
+		}
+	}()
+	var buf bytes.Buffer
+	if err := json.NewEncoder(&buf).Encode(src); err != nil {
+		return err
+	}
+	if err := job.DecodeEnv(&buf); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (job *Job) Environ() map[string]string {
+	m := make(map[string]string)
+	for _, kv := range job.env {
+		parts := strings.SplitN(kv, "=", 2)
+		m[parts[0]] = parts[1]
+	}
+	return m
+}
+
+func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
+	prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
+	return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
+}
+
+func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
+	return fmt.Fprintf(job.Stdout, format, args...)
+}
+
+func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) {
+	return fmt.Fprintf(job.Stderr, format, args...)
+
+}
diff --git a/engine/init_test.go b/engine/utils.go
similarity index 90%
rename from engine/init_test.go
rename to engine/utils.go
index 48d9009..33b1269 100644
--- a/engine/init_test.go
+++ b/engine/utils.go
@@ -15,7 +15,7 @@
 	Register("dummy", func(job *Job) string { return "" })
 }
 
-func mkEngine(t *testing.T) *Engine {
+func newTestEngine(t *testing.T) *Engine {
 	// Use the caller function name as a prefix.
 	// This helps trace temp directories back to their test.
 	pc, _, _, _ := runtime.Caller(1)
@@ -38,5 +38,5 @@
 }
 
 func mkJob(t *testing.T, name string, args ...string) *Job {
-	return mkEngine(t).Job(name, args...)
+	return newTestEngine(t).Job(name, args...)
 }
diff --git a/gograph/gograph.go b/gograph/gograph.go
index 32b3a49..aa6a412 100644
--- a/gograph/gograph.go
+++ b/gograph/gograph.go
@@ -48,7 +48,7 @@
 // Graph database for storing entities and their relationships
 type Database struct {
 	conn *sql.DB
-	mux  sync.Mutex
+	mux  sync.RWMutex
 }
 
 // Create a new graph database initialized with a root entity
@@ -138,7 +138,14 @@
 
 // Return true if a name already exists in the database
 func (db *Database) Exists(name string) bool {
-	return db.Get(name) != nil
+	db.mux.RLock()
+	defer db.mux.RUnlock()
+
+	e, err := db.get(name)
+	if err != nil {
+		return false
+	}
+	return e != nil
 }
 
 func (db *Database) setEdge(parentPath, name string, e *Entity) error {
@@ -165,6 +172,9 @@
 
 // Return the entity for a given path
 func (db *Database) Get(name string) *Entity {
+	db.mux.RLock()
+	defer db.mux.RUnlock()
+
 	e, err := db.get(name)
 	if err != nil {
 		return nil
@@ -200,23 +210,36 @@
 // List all entities by from the name
 // The key will be the full path of the entity
 func (db *Database) List(name string, depth int) Entities {
+	db.mux.RLock()
+	defer db.mux.RUnlock()
+
 	out := Entities{}
 	e, err := db.get(name)
 	if err != nil {
 		return out
 	}
-	for c := range db.children(e, name, depth) {
+
+	children, err := db.children(e, name, depth, nil)
+	if err != nil {
+		return out
+	}
+
+	for _, c := range children {
 		out[c.FullPath] = c.Entity
 	}
 	return out
 }
 
+// Walk through the child graph of an entity, calling walkFunc for each child entity.
+// It is safe for walkFunc to call graph functions.
 func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
-	e, err := db.get(name)
+	children, err := db.Children(name, depth)
 	if err != nil {
 		return err
 	}
-	for c := range db.children(e, name, depth) {
+
+	// Note: the database lock must not be held while calling walkFunc
+	for _, c := range children {
 		if err := walkFunc(c.FullPath, c.Entity); err != nil {
 			return err
 		}
@@ -224,8 +247,24 @@
 	return nil
 }
 
+// Return the children of the specified entity
+func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
+	db.mux.RLock()
+	defer db.mux.RUnlock()
+
+	e, err := db.get(name)
+	if err != nil {
+		return nil, err
+	}
+
+	return db.children(e, name, depth, nil)
+}
+
 // Return the refrence count for a specified id
 func (db *Database) Refs(id string) int {
+	db.mux.RLock()
+	defer db.mux.RUnlock()
+
 	var count int
 	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
 		return 0
@@ -235,6 +274,9 @@
 
 // Return all the id's path references
 func (db *Database) RefPaths(id string) Edges {
+	db.mux.RLock()
+	defer db.mux.RUnlock()
+
 	refs := Edges{}
 
 	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
@@ -356,56 +398,51 @@
 	Edge     *Edge
 }
 
-func (db *Database) children(e *Entity, name string, depth int) <-chan WalkMeta {
-	out := make(chan WalkMeta)
+func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
 	if e == nil {
-		close(out)
-		return out
+		return entities, nil
 	}
 
-	go func() {
-		rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
-		if err != nil {
-			close(out)
+	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var entityId, entityName string
+		if err := rows.Scan(&entityId, &entityName); err != nil {
+			return nil, err
 		}
-		defer rows.Close()
+		child := &Entity{entityId}
+		edge := &Edge{
+			ParentID: e.id,
+			Name:     entityName,
+			EntityID: child.id,
+		}
 
-		for rows.Next() {
-			var entityId, entityName string
-			if err := rows.Scan(&entityId, &entityName); err != nil {
-				// Log error
-				continue
-			}
-			child := &Entity{entityId}
-			edge := &Edge{
-				ParentID: e.id,
-				Name:     entityName,
-				EntityID: child.id,
-			}
+		meta := WalkMeta{
+			Parent:   e,
+			Entity:   child,
+			FullPath: path.Join(name, edge.Name),
+			Edge:     edge,
+		}
 
-			meta := WalkMeta{
-				Parent:   e,
-				Entity:   child,
-				FullPath: path.Join(name, edge.Name),
-				Edge:     edge,
-			}
+		entities = append(entities, meta)
 
-			out <- meta
-			if depth == 0 {
-				continue
-			}
+		if depth != 0 {
 			nDepth := depth
 			if depth != -1 {
 				nDepth -= 1
 			}
-			sc := db.children(child, meta.FullPath, nDepth)
-			for c := range sc {
-				out <- c
+			entities, err = db.children(child, meta.FullPath, nDepth, entities)
+			if err != nil {
+				return nil, err
 			}
 		}
-		close(out)
-	}()
-	return out
+	}
+
+	return entities, nil
 }
 
 // Return the entity based on the parent path and name
diff --git a/hack/ROADMAP.md b/hack/ROADMAP.md
index e7dbf80..01fac1f 100644
--- a/hack/ROADMAP.md
+++ b/hack/ROADMAP.md
@@ -5,7 +5,7 @@
 
 For a more complete view of planned and requested improvements, see [the Github issues](https://github.com/dotcloud/docker/issues).
 
-Tu suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request.
+To suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request.
 
 
 ## Container wiring and service discovery
diff --git a/hack/infrastructure/docker-ci/Dockerfile b/hack/infrastructure/docker-ci/Dockerfile
index 3ac8d90..d894330 100644
--- a/hack/infrastructure/docker-ci/Dockerfile
+++ b/hack/infrastructure/docker-ci/Dockerfile
@@ -1,14 +1,16 @@
-# VERSION:        0.22
-# DOCKER-VERSION  0.6.3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
-# DESCRIPTION:    Deploy docker-ci on Amazon EC2
+# VERSION:        0.25
+# DOCKER-VERSION  0.6.6
+# AUTHOR:         Daniel Mizyrycki <daniel@docker.com>
+# DESCRIPTION:    Deploy docker-ci on Digital Ocean
 # COMMENTS:
 #     CONFIG_JSON is an environment variable json string loaded as:
 #
 # export CONFIG_JSON='
-#     { "AWS_TAG":             "EC2_instance_name",
-#       "AWS_ACCESS_KEY":      "EC2_access_key",
-#       "AWS_SECRET_KEY":      "EC2_secret_key",
+#     { "DROPLET_NAME":        "docker-ci",
+#       "DO_CLIENT_ID":        "Digital_Ocean_client_id",
+#       "DO_API_KEY":          "Digital_Ocean_api_key",
+#       "DOCKER_KEY_ID":       "Digital_Ocean_ssh_key_id",
+#       "DOCKER_CI_KEY_PATH":  "docker-ci_private_key_path",
 #       "DOCKER_CI_PUB":       "$(cat docker-ci_ssh_public_key.pub)",
 #       "DOCKER_CI_KEY":       "$(cat docker-ci_ssh_private_key.key)",
 #       "BUILDBOT_PWD":        "Buildbot_server_password",
@@ -33,9 +35,11 @@
 
 from ubuntu:12.04
 
-run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
-run apt-get update; apt-get install -y python2.7 python-dev python-pip ssh rsync less vim
-run pip install boto fabric
+run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' \
+    > /etc/apt/sources.list
+run apt-get update; apt-get install -y git python2.7 python-dev libevent-dev \
+    python-pip ssh rsync less vim
+run pip install requests fabric
 
 # Add deployment code and set default container command
 add . /docker-ci
diff --git a/hack/infrastructure/docker-ci/VERSION b/hack/infrastructure/docker-ci/VERSION
new file mode 100644
index 0000000..0bfccb0
--- /dev/null
+++ b/hack/infrastructure/docker-ci/VERSION
@@ -0,0 +1 @@
+0.4.5
diff --git a/hack/infrastructure/docker-ci/buildbot/master.cfg b/hack/infrastructure/docker-ci/buildbot/master.cfg
index 52bf495..9ca5fc0 100644
--- a/hack/infrastructure/docker-ci/buildbot/master.cfg
+++ b/hack/infrastructure/docker-ci/buildbot/master.cfg
@@ -43,7 +43,7 @@
 
 # Schedulers
 c['schedulers'] = [ForceScheduler(name='trigger', builderNames=['docker',
-    'index','registry','coverage','nightlyrelease'])]
+    'index','registry','docker-coverage','registry-coverage','nightlyrelease'])]
 c['schedulers'] += [SingleBranchScheduler(name="all", treeStableTimer=None,
     change_filter=filter.ChangeFilter(branch='master',
     repository='https://github.com/dotcloud/docker'), builderNames=['docker'])]
@@ -51,7 +51,7 @@
     change_filter=filter.ChangeFilter(category='github_pullrequest'), treeStableTimer=None,
     builderNames=['pullrequest'])]
 c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['nightlyrelease',
-    'coverage'], hour=7, minute=00)]
+    'docker-coverage','registry-coverage'], hour=7, minute=00)]
 c['schedulers'] += [Nightly(name='every4hrs', branch=None, builderNames=['registry','index'],
     hour=range(0,24,4), minute=15)]
 
@@ -76,17 +76,25 @@
 
 # Docker coverage test
 factory = BuildFactory()
-factory.addStep(ShellCommand(description='Coverage', logEnviron=False,
+factory.addStep(ShellCommand(description='docker-coverage', logEnviron=False,
     usePTY=True, command='{0}/docker-coverage/coverage-docker.sh'.format(
     DOCKER_CI_PATH)))
-c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'],
+c['builders'] += [BuilderConfig(name='docker-coverage',slavenames=['buildworker'],
+    factory=factory)]
+
+# Docker registry coverage test
+factory = BuildFactory()
+factory.addStep(ShellCommand(description='registry-coverage', logEnviron=False,
+    usePTY=True, command='docker run registry_coverage'.format(
+    DOCKER_CI_PATH)))
+c['builders'] += [BuilderConfig(name='registry-coverage',slavenames=['buildworker'],
     factory=factory)]
 
 # Registry functional test
 factory = BuildFactory()
 factory.addStep(ShellCommand(description='registry', logEnviron=False,
     command='. {0}/master/credentials.cfg; '
-    '/docker-ci/functionaltests/test_registry.sh'.format(BUILDBOT_PATH),
+    '{1}/functionaltests/test_registry.sh'.format(BUILDBOT_PATH, DOCKER_CI_PATH),
     usePTY=True))
 c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'],
     factory=factory)]
@@ -95,16 +103,17 @@
 factory = BuildFactory()
 factory.addStep(ShellCommand(description='index', logEnviron=False,
     command='. {0}/master/credentials.cfg; '
-    '/docker-ci/functionaltests/test_index.py'.format(BUILDBOT_PATH),
+    '{1}/functionaltests/test_index.py'.format(BUILDBOT_PATH, DOCKER_CI_PATH),
     usePTY=True))
 c['builders'] += [BuilderConfig(name='index',slavenames=['buildworker'],
     factory=factory)]
 
 # Docker nightly release
+nightlyrelease_cmd = ('docker version; docker run -i -t -privileged -e AWS_S3_BUCKET='
+    'test.docker.io dockerbuilder hack/dind dockerbuild.sh')
 factory = BuildFactory()
-factory.addStep(ShellCommand(description='NightlyRelease', logEnviron=False,
-    usePTY=True, command='docker run -privileged'
-    ' -e AWS_S3_BUCKET=test.docker.io dockerbuilder'))
+factory.addStep(ShellCommand(description='NightlyRelease',logEnviron=False,
+    usePTY=True, command=nightlyrelease_cmd))
 c['builders'] += [BuilderConfig(name='nightlyrelease',slavenames=['buildworker'],
     factory=factory)]
 
diff --git a/hack/infrastructure/docker-ci/deployment.py b/hack/infrastructure/docker-ci/deployment.py
index 655bdd4..c04219d 100755
--- a/hack/infrastructure/docker-ci/deployment.py
+++ b/hack/infrastructure/docker-ci/deployment.py
@@ -1,11 +1,11 @@
 #!/usr/bin/env python
 
-import os, sys, re, json, base64
-from boto.ec2.connection import EC2Connection
+import os, sys, re, json, requests, base64
 from subprocess import call
 from fabric import api
 from fabric.api import cd, run, put, sudo
 from os import environ as env
+from datetime import datetime
 from time import sleep
 
 # Remove SSH private key as it needs more processing
@@ -20,42 +20,41 @@
 env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1',
     env['CONFIG_JSON'],flags=re.DOTALL)
 
-
-AWS_TAG = env.get('AWS_TAG','docker-ci')
-AWS_KEY_NAME = 'dotcloud-dev'       # Same as CONFIG_JSON['DOCKER_CI_PUB']
-AWS_AMI = 'ami-d582d6bc'            # Ubuntu 13.04
-AWS_REGION = 'us-east-1'
-AWS_TYPE = 'm1.small'
-AWS_SEC_GROUPS = 'gateway'
-AWS_IMAGE_USER = 'ubuntu'
+DROPLET_NAME = env.get('DROPLET_NAME','docker-ci')
+TIMEOUT = 120            # Seconds before timeout droplet creation
+IMAGE_ID = 1004145       # Docker on Ubuntu 13.04
+REGION_ID = 4            # New York 2
+SIZE_ID = 62             # memory 2GB
+DO_IMAGE_USER = 'root'   # Image user on Digital Ocean
+API_URL = 'https://api.digitalocean.com/'
 DOCKER_PATH = '/go/src/github.com/dotcloud/docker'
 DOCKER_CI_PATH = '/docker-ci'
 CFG_PATH = '{}/buildbot'.format(DOCKER_CI_PATH)
 
 
-class AWS_EC2:
-    '''Amazon EC2'''
-    def __init__(self, access_key, secret_key):
+class DigitalOcean():
+
+    def __init__(self, key, client):
         '''Set default API parameters'''
-        self.handler = EC2Connection(access_key, secret_key)
-    def create_instance(self, tag, instance_type):
-        reservation = self.handler.run_instances(**instance_type)
-        instance = reservation.instances[0]
-        sleep(10)
-        while instance.state != 'running':
-            sleep(5)
-            instance.update()
-            print "Instance state: %s" % (instance.state)
-        instance.add_tag("Name",tag)
-        print "instance %s done!" % (instance.id)
-        return instance.ip_address
-    def get_instances(self):
-        return self.handler.get_all_instances()
-    def get_tags(self):
-        return dict([(i.instances[0].id, i.instances[0].tags['Name'])
-            for i in self.handler.get_all_instances() if i.instances[0].tags])
-    def del_instance(self, instance_id):
-        self.handler.terminate_instances(instance_ids=[instance_id])
+        self.key = key
+        self.client = client
+        self.api_url = API_URL
+
+    def api(self, cmd_path, api_arg={}):
+        '''Make api call'''
+        api_arg.update({'api_key':self.key, 'client_id':self.client})
+        resp = requests.get(self.api_url + cmd_path, params=api_arg).text
+        resp = json.loads(resp)
+        if resp['status'] != 'OK':
+            raise Exception(resp['error_message'])
+        return resp
+
+    def droplet_data(self, name):
+        '''Get droplet data'''
+        data = self.api('droplets')
+        data = [droplet for droplet in data['droplets']
+            if droplet['name'] == name]
+        return data[0] if data else {}
 
 
 def json_fmt(data):
@@ -63,20 +62,36 @@
     return json.dumps(data, sort_keys = True, indent = 2)
 
 
-# Create EC2 API handler
-ec2 = AWS_EC2(env['AWS_ACCESS_KEY'], env['AWS_SECRET_KEY'])
+do = DigitalOcean(env['DO_API_KEY'], env['DO_CLIENT_ID'])
 
-# Stop processing if AWS_TAG exists on EC2
-if AWS_TAG in ec2.get_tags().values():
-    print ('Instance: {} already deployed. Not further processing.'
-        .format(AWS_TAG))
+# Get DROPLET_NAME data
+data = do.droplet_data(DROPLET_NAME)
+
+# Stop processing if DROPLET_NAME exists on Digital Ocean
+if data:
+    print ('Droplet: {} already deployed. Not further processing.'
+        .format(DROPLET_NAME))
     exit(1)
 
-ip = ec2.create_instance(AWS_TAG, {'image_id':AWS_AMI, 'instance_type':AWS_TYPE,
-    'security_groups':[AWS_SEC_GROUPS], 'key_name':AWS_KEY_NAME})
+# Create droplet
+do.api('droplets/new', {'name':DROPLET_NAME, 'region_id':REGION_ID,
+    'image_id':IMAGE_ID, 'size_id':SIZE_ID,
+    'ssh_key_ids':[env['DOCKER_KEY_ID']]})
 
-# Wait 30 seconds for the machine to boot
-sleep(30)
+# Wait for droplet to be created.
+start_time = datetime.now()
+while (data.get('status','') != 'active' and (
+ datetime.now()-start_time).seconds < TIMEOUT):
+    data = do.droplet_data(DROPLET_NAME)
+    print data['status']
+    sleep(3)
+
+# Wait for the machine to boot
+sleep(15)
+
+# Get droplet IP
+ip = str(data['ip_address'])
+print 'droplet: {}    ip: {}'.format(DROPLET_NAME, ip)
 
 # Create docker-ci ssh private key so docker-ci docker container can communicate
 # with its EC2 instance
@@ -86,7 +101,7 @@
 open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n')
 
 api.env.host_string = ip
-api.env.user = AWS_IMAGE_USER
+api.env.user = DO_IMAGE_USER
 api.env.key_filename = '/root/.ssh/id_rsa'
 
 # Correct timezone
@@ -100,20 +115,17 @@
 credentials = {
     'AWS_ACCESS_KEY': env['PKG_ACCESS_KEY'],
     'AWS_SECRET_KEY': env['PKG_SECRET_KEY'],
-    'GPG_PASSPHRASE': env['PKG_GPG_PASSPHRASE'],
-    'INDEX_AUTH': env['INDEX_AUTH']}
+    'GPG_PASSPHRASE': env['PKG_GPG_PASSPHRASE']}
 open(DOCKER_CI_PATH + '/nightlyrelease/release_credentials.json', 'w').write(
     base64.b64encode(json.dumps(credentials)))
 
 # Transfer docker
 sudo('mkdir -p ' + DOCKER_CI_PATH)
-sudo('chown {}.{} {}'.format(AWS_IMAGE_USER, AWS_IMAGE_USER, DOCKER_CI_PATH))
-call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, AWS_IMAGE_USER, ip,
+sudo('chown {}.{} {}'.format(DO_IMAGE_USER, DO_IMAGE_USER, DOCKER_CI_PATH))
+call('/usr/bin/rsync -aH {} {}@{}:{}'.format(DOCKER_CI_PATH, DO_IMAGE_USER, ip,
     os.path.dirname(DOCKER_CI_PATH)), shell=True)
 
 # Install Docker and Buildbot dependencies
-sudo('addgroup docker')
-sudo('usermod -a -G docker ubuntu')
 sudo('mkdir /mnt/docker; ln -s /mnt/docker /var/lib/docker')
 sudo('wget -q -O - https://get.docker.io/gpg | apt-key add -')
 sudo('echo deb https://get.docker.io/ubuntu docker main >'
@@ -123,7 +135,7 @@
     ' > /etc/apt/sources.list; apt-get update')
 sudo('DEBIAN_FRONTEND=noninteractive apt-get install -q -y wget python-dev'
     ' python-pip supervisor git mercurial linux-image-extra-$(uname -r)'
-    ' aufs-tools make libfontconfig libevent-dev')
+    ' aufs-tools make libfontconfig libevent-dev libsqlite3-dev libssl-dev')
 sudo('wget -O - https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz | '
     'tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go')
 sudo('GOPATH=/go go get -d github.com/dotcloud/docker')
@@ -135,13 +147,13 @@
     'phantomjs-1.9.1-linux-x86_64.tar.bz2 | tar jx -C /usr/bin'
     ' --strip-components=2 phantomjs-1.9.1-linux-x86_64/bin/phantomjs')
 
-# Preventively reboot docker-ci daily
-sudo('ln -s /sbin/reboot /etc/cron.daily')
-
 # Build docker-ci containers
 sudo('cd {}; docker build -t docker .'.format(DOCKER_PATH))
+sudo('cd {}; docker build -t docker-ci .'.format(DOCKER_CI_PATH))
 sudo('cd {}/nightlyrelease; docker build -t dockerbuilder .'.format(
     DOCKER_CI_PATH))
+sudo('cd {}/registry-coverage; docker build -t registry_coverage .'.format(
+    DOCKER_CI_PATH))
 
 # Download docker-ci testing container
 sudo('docker pull mzdaniel/test_docker')
@@ -154,3 +166,6 @@
     env['SMTP_PWD'], env['EMAIL_RCP'], env['REGISTRY_USER'],
     env['REGISTRY_PWD'], env['REGISTRY_BUCKET'], env['REGISTRY_ACCESS_KEY'],
     env['REGISTRY_SECRET_KEY']))
+
+# Preventively reboot docker-ci daily
+sudo('ln -s /sbin/reboot /etc/cron.daily')
diff --git a/hack/infrastructure/docker-ci/docker-test/Dockerfile b/hack/infrastructure/docker-ci/docker-test/Dockerfile
index 66cb976..0f3a63f 100644
--- a/hack/infrastructure/docker-ci/docker-test/Dockerfile
+++ b/hack/infrastructure/docker-ci/docker-test/Dockerfile
@@ -1,6 +1,6 @@
-# VERSION:        0.3
-# DOCKER-VERSION  0.6.3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
+# VERSION:        0.4
+# DOCKER-VERSION  0.6.6
+# AUTHOR:         Daniel Mizyrycki <daniel@docker.com>
 # DESCRIPTION:    Testing docker PRs and commits on top of master using
 # REFERENCES:     This code reuses the excellent implementation of
 #                 Docker in Docker made by Jerome Petazzoni.
@@ -15,15 +15,10 @@
 # TO_RUN:         docker run -privileged test_docker hack/dind test_docker.sh [commit] [repo] [branch]
 
 from docker
-maintainer Daniel Mizyrycki <daniel@dotcloud.com>
+maintainer Daniel Mizyrycki <daniel@docker.com>
 
-# Setup go environment. Extracted from /Dockerfile
-env     CGO_ENABLED 0
-env     GOROOT  /goroot
-env     PATH    $PATH:/goroot/bin
-env     GOPATH  /go:/go/src/github.com/dotcloud/docker/vendor
-volume  /var/lib/docker
-workdir /go/src/github.com/dotcloud/docker
+# Setup go in PATH. Extracted from /Dockerfile
+env PATH /usr/local/go/bin:$PATH
 
 # Add test_docker.sh
 add test_docker.sh /usr/bin/test_docker.sh
diff --git a/hack/infrastructure/docker-ci/docker-test/test_docker.sh b/hack/infrastructure/docker-ci/docker-test/test_docker.sh
index 895e4d9..cf8fdb9 100755
--- a/hack/infrastructure/docker-ci/docker-test/test_docker.sh
+++ b/hack/infrastructure/docker-ci/docker-test/test_docker.sh
@@ -8,31 +8,26 @@
 # Compute test paths
 DOCKER_PATH=/go/src/github.com/dotcloud/docker
 
+# Timestamp
+echo
+date; echo
+
 # Fetch latest master
+cd /
 rm -rf /go
-mkdir -p $DOCKER_PATH
+git clone -q -b master http://github.com/dotcloud/docker $DOCKER_PATH
 cd $DOCKER_PATH
-git init .
-git fetch -q http://github.com/dotcloud/docker master
-git reset --hard FETCH_HEAD
 
 # Merge commit
-#echo FIXME. Temporarily skip TestPrivilegedCanMount until DinD works reliable on AWS
-git pull -q https://github.com/mzdaniel/docker.git dind-aws || exit 1
-
-# Merge commit in top of master
 git fetch -q "$REPO" "$BRANCH"
-git merge --no-edit $COMMIT || exit 1
+git merge --no-edit $COMMIT || exit 255
 
 # Test commit
-go test -v; exit_status=$?
+./hack/make.sh test; exit_status=$?
 
 # Display load if test fails
-if [ $exit_status -eq 1 ] ; then
+if [ $exit_status -ne 0 ] ; then
     uptime; echo; free
 fi
 
-# Cleanup testing directory
-rm -rf $BASE_PATH
-
 exit $exit_status
diff --git a/hack/infrastructure/docker-ci/functionaltests/test_registry.sh b/hack/infrastructure/docker-ci/functionaltests/test_registry.sh
index 8bcd355..5864252 100755
--- a/hack/infrastructure/docker-ci/functionaltests/test_registry.sh
+++ b/hack/infrastructure/docker-ci/functionaltests/test_registry.sh
@@ -8,10 +8,12 @@
 # Setup the environment
 export SETTINGS_FLAVOR=test
 export DOCKER_REGISTRY_CONFIG=config_test.yml
+export PYTHONPATH=$(pwd)/docker-registry/test
 
 # Get latest docker registry
 git clone -q https://github.com/dotcloud/docker-registry.git
 cd docker-registry
+sed -Ei "s#(boto_bucket: ).+#\1_env:S3_BUCKET#" config_test.yml
 
 # Get dependencies
 pip install -q -r requirements.txt
@@ -20,7 +22,6 @@
 
 # Run registry tests
 tox || exit 1
-export PYTHONPATH=$(pwd)/docker-registry
 python -m unittest discover -p s3.py -s test || exit 1
 python -m unittest discover -p workflow.py -s test
 
diff --git a/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile b/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile
index 541f3a9..2100a9e 100644
--- a/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile
+++ b/hack/infrastructure/docker-ci/nightlyrelease/Dockerfile
@@ -1,20 +1,19 @@
-# VERSION:        1.2
-# DOCKER-VERSION  0.6.3
-# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
+# VERSION:        1.6
+# DOCKER-VERSION  0.6.6
+# AUTHOR:         Daniel Mizyrycki <daniel@docker.com>
 # DESCRIPTION:    Build docker nightly release using Docker in Docker.
 # REFERENCES:     This code reuses the excellent implementation of docker in docker
 #                 made by Jerome Petazzoni.  https://github.com/jpetazzo/dind
 # COMMENTS:
 #   release_credentials.json is a base64 json encoded file containing:
 #       { "AWS_ACCESS_KEY": "Test_docker_AWS_S3_bucket_id",
-#         "AWS_SECRET_KEY='Test_docker_AWS_S3_bucket_key'
-#         "GPG_PASSPHRASE='Test_docker_GPG_passphrase_signature'
-#         "INDEX_AUTH='Encripted_index_authentication' }
+#         "AWS_SECRET_KEY": "Test_docker_AWS_S3_bucket_key",
+#         "GPG_PASSPHRASE": "Test_docker_GPG_passphrase_signature" }
 # TO_BUILD:       docker build -t dockerbuilder .
-# TO_RELEASE:     docker run -i -t -privileged  -e AWS_S3_BUCKET="test.docker.io" dockerbuilder
+# TO_RELEASE:     docker run -i -t -privileged -e AWS_S3_BUCKET="test.docker.io" dockerbuilder hack/dind dockerbuild.sh
 
 from docker
-maintainer Daniel Mizyrycki <daniel@dotcloud.com>
+maintainer Daniel Mizyrycki <daniel@docker.com>
 
 # Add docker dependencies and downloading packages
 run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
@@ -24,11 +23,8 @@
 run wget -q -O /usr/bin/docker http://get.docker.io/builds/Linux/x86_64/docker-latest; chmod +x /usr/bin/docker
 
 # Add proto docker builder
-add ./dockerbuild /usr/bin/dockerbuild
-run chmod +x /usr/bin/dockerbuild
+add ./dockerbuild.sh /usr/bin/dockerbuild.sh
+run chmod +x /usr/bin/dockerbuild.sh
 
 # Add release credentials
 add ./release_credentials.json /root/release_credentials.json
-
-# Launch build process in a container
-cmd dockerbuild
diff --git a/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild b/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild
deleted file mode 100644
index 83a7157..0000000
--- a/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/bash
-
-# Variables AWS_ACCESS_KEY, AWS_SECRET_KEY, PG_PASSPHRASE and INDEX_AUTH
-# are decoded from /root/release_credentials.json
-# Variable AWS_S3_BUCKET is passed to the environment from docker run -e
-
-# Enable debugging
-set -x
-
-# Fetch docker master branch
-rm -rf  /go/src/github.com/dotcloud/docker
-cd /
-git clone -q http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker
-cd /go/src/github.com/dotcloud/docker
-
-# Launch docker daemon using dind inside the container
-./hack/dind /usr/bin/docker -d &
-sleep 5
-
-# Add an uncommitted change to generate a timestamped release
-date > timestamp
-
-# Build the docker package using /Dockerfile
-docker build -t docker .
-
-# Run Docker unittests binary and Ubuntu package
-docker run -privileged docker hack/make.sh
-exit_status=$?
-
-# Display load if test fails
-if [ $exit_status -eq 1 ] ; then
-    uptime; echo; free
-    exit 1
-fi
-
-# Commit binary and ubuntu bundles for release
-docker commit -run '{"Env": ["PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin"], "WorkingDir": "/go/src/github.com/dotcloud/docker"}' $(docker ps -l -q) release
-
-# Turn debug off to load credentials from the environment
-set +x
-eval $(cat /root/release_credentials.json  | python -c '
-import sys,json,base64;
-d=json.loads(base64.b64decode(sys.stdin.read()));
-exec("""for k in d: print "export {0}=\\"{1}\\"".format(k,d[k])""")')
-set -x
-
-# Push docker nightly
-echo docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=XXXXX -e AWS_SECRET_KEY=XXXXX -e GPG_PASSPHRASE=XXXXX release  hack/release.sh
-set +x
-docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE  release  hack/release.sh
diff --git a/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh b/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh
new file mode 100644
index 0000000..80caaec
--- /dev/null
+++ b/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# Variables AWS_ACCESS_KEY, AWS_SECRET_KEY and PG_PASSPHRASE are decoded
+# from /root/release_credentials.json
+# Variable AWS_S3_BUCKET is passed to the environment from docker run -e
+
+# Turn debug off to load credentials from the environment
+set +x
+eval $(cat /root/release_credentials.json  | python -c '
+import sys,json,base64;
+d=json.loads(base64.b64decode(sys.stdin.read()));
+exec("""for k in d: print "export {0}=\\"{1}\\"".format(k,d[k])""")')
+
+# Fetch docker master branch
+set -x
+cd /
+rm -rf /go
+git clone -q -b master http://github.com/dotcloud/docker /go/src/github.com/dotcloud/docker
+cd /go/src/github.com/dotcloud/docker
+
+# Launch docker daemon using dind inside the container
+/usr/bin/docker version
+/usr/bin/docker -d &
+sleep 5
+
+# Build Docker release container
+docker build -t docker .
+
+# Test docker and if everything works well, release
+echo docker run -i -t -privileged -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=XXXXX -e AWS_SECRET_KEY=XXXXX -e GPG_PASSPHRASE=XXXXX docker hack/release.sh
+set +x
+docker run -privileged -i -t -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE docker hack/release.sh
+exit_status=$?
+
+# Display load if test fails
+set -x
+if [ $exit_status -ne 0 ] ; then
+    uptime; echo; free
+    exit 1
+fi
diff --git a/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json b/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json
deleted file mode 100644
index ed6d53e..0000000
--- a/hack/infrastructure/docker-ci/nightlyrelease/release_credentials.json
+++ /dev/null
@@ -1 +0,0 @@
-eyAiQVdTX0FDQ0VTU19LRVkiOiAiIiwKICAiQVdTX1NFQ1JFVF9LRVkiOiAiIiwKICAiR1BHX1BBU1NQSFJBU0UiOiAiIiwKICAiSU5ERVhfQVVUSCI6ICIiIH0=
diff --git a/hack/infrastructure/docker-ci/registry-coverage/Dockerfile b/hack/infrastructure/docker-ci/registry-coverage/Dockerfile
new file mode 100644
index 0000000..e544645
--- /dev/null
+++ b/hack/infrastructure/docker-ci/registry-coverage/Dockerfile
@@ -0,0 +1,18 @@
+# VERSION:        0.1
+# DOCKER-VERSION  0.6.4
+# AUTHOR:         Daniel Mizyrycki <daniel@dotcloud.com>
+# DESCRIPTION:    Docker registry coverage
+# COMMENTS:       Add registry coverage into the docker-ci image
+# TO_BUILD:       docker build -t registry_coverage .
+# TO_RUN:         docker run registry_coverage
+
+from docker-ci
+maintainer Daniel Mizyrycki <daniel@dotcloud.com>
+
+# Add registry_coverager.sh and dependencies
+run pip install coverage flask pyyaml requests simplejson python-glanceclient \
+    blinker redis boto gevent rsa mock
+add registry_coverage.sh /usr/bin/registry_coverage.sh
+run chmod +x /usr/bin/registry_coverage.sh
+
+cmd "/usr/bin/registry_coverage.sh"
diff --git a/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh b/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh
new file mode 100755
index 0000000..e16cea8
--- /dev/null
+++ b/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -x
+
+# Setup the environment
+REGISTRY_PATH=/data/docker-registry
+export SETTINGS_FLAVOR=test
+export DOCKER_REGISTRY_CONFIG=config_test.yml
+export PYTHONPATH=$REGISTRY_PATH/test
+
+# Fetch latest docker-registry master
+rm -rf $REGISTRY_PATH
+git clone https://github.com/dotcloud/docker-registry -b master $REGISTRY_PATH
+cd $REGISTRY_PATH
+
+# Generate coverage
+coverage run -m unittest discover test || exit 1
+coverage report --include='./*' --omit='./test/*'
diff --git a/hack/infrastructure/docker-ci/report/deployment.py b/hack/infrastructure/docker-ci/report/deployment.py
index d5efb4a..5b2eaf3 100755
--- a/hack/infrastructure/docker-ci/report/deployment.py
+++ b/hack/infrastructure/docker-ci/report/deployment.py
@@ -34,7 +34,7 @@
 
 DROPLET_NAME = env.get('DROPLET_NAME','report')
 TIMEOUT = 120            # Seconds before timeout droplet creation
-IMAGE_ID = 894856        # Docker on Ubuntu 13.04
+IMAGE_ID = 1004145       # Docker on Ubuntu 13.04
 REGION_ID = 4            # New York 2
 SIZE_ID = 66             # memory 512MB
 DO_IMAGE_USER = 'root'   # Image user on Digital Ocean
diff --git a/hack/make/dyntest b/hack/make/dyntest
index 7de144e..ff607d3 100644
--- a/hack/make/dyntest
+++ b/hack/make/dyntest
@@ -22,7 +22,12 @@
 		for test_dir in $(find_test_dirs); do (
 			set -x
 			cd $test_dir
+			
+			# Install packages that are dependencies of the tests.
+			#   Note: Does not run the tests.
 			go test -i -ldflags "$LDFLAGS" $BUILDFLAGS
+			
+			# Run the tests with the optional $TESTFLAGS.
 			export TEST_DOCKERINIT_PATH=$DEST/../dynbinary/dockerinit-$VERSION
 			go test -v -ldflags "$LDFLAGS -X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\"" $BUILDFLAGS $TESTFLAGS
 		)  done
diff --git a/hack/make/test b/hack/make/test
index 8acd634..45ffe87 100644
--- a/hack/make/test
+++ b/hack/make/test
@@ -16,7 +16,12 @@
 		for test_dir in $(find_test_dirs); do (
 			set -x
 			cd $test_dir
+			
+			# Install packages that are dependencies of the tests.
+			#   Note: Does not run the tests.
 			go test -i -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS
+			
+			# Run the tests with the optional $TESTFLAGS.
 			go test -v -ldflags "$LDFLAGS $LDFLAGS_STATIC" $BUILDFLAGS $TESTFLAGS
 		)  done
 	} 2>&1 | tee $DEST/test.log
diff --git a/hack/make/ubuntu b/hack/make/ubuntu
index 5834172..d4b9fd0 100644
--- a/hack/make/ubuntu
+++ b/hack/make/ubuntu
@@ -10,7 +10,7 @@
 PACKAGE_ARCHITECTURE="$(dpkg-architecture -qDEB_HOST_ARCH)"
 PACKAGE_URL="http://www.docker.io/"
 PACKAGE_MAINTAINER="docker@dotcloud.com"
-PACKAGE_DESCRIPTION="lxc-docker is a Linux container runtime
+PACKAGE_DESCRIPTION="Linux container runtime
 Docker complements LXC with a high-level API which operates at the process
 level. It runs unix processes with strong guarantees of isolation and
 repeatability across servers.
@@ -37,27 +37,51 @@
 	# This will fail if the binary bundle hasn't been built
 	cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker
 
-	# Generate postinst/prerm scripts
-	cat >/tmp/postinst <<'EOF'
+	# Generate postinst/prerm/postrm scripts
+	cat > /tmp/postinst <<'EOF'
 #!/bin/sh
-service docker stop || true
-grep -q '^docker:' /etc/group || groupadd --system docker || true
-service docker start
-EOF
-	cat >/tmp/prerm <<'EOF'
-#!/bin/sh
-service docker stop || true
+set -e
+set -u
 
-case "$1" in
-	purge|remove|abort-install)
-		groupdel docker || true
-		;;
-		
-	upgrade|failed-upgrade|abort-upgrade)
-		# don't touch docker group
-		;;
-esac
+getent group docker > /dev/null || groupadd --system docker || true
+
+update-rc.d docker defaults > /dev/null || true
+if [ -n "$2" ]; then
+	_dh_action=restart
+else
+	_dh_action=start
+fi
+service docker $_dh_action 2>/dev/null || true
+
+#DEBHELPER#
 EOF
+	cat > /tmp/prerm <<'EOF'
+#!/bin/sh
+set -e
+set -u
+
+service docker stop 2>/dev/null || true
+
+#DEBHELPER#
+EOF
+	cat > /tmp/postrm <<'EOF'
+#!/bin/sh
+set -e
+set -u
+
+if [ "$1" = "purge" ] ; then
+	update-rc.d docker remove > /dev/null || true
+fi
+
+# In case this system is running systemd, we make systemd reload the unit files
+# to pick up changes.
+if [ -d /run/systemd/system ] ; then
+	systemctl --system daemon-reload > /dev/null || true
+fi
+
+#DEBHELPER#
+EOF
+	# TODO swaths of these were borrowed from debhelper's auto-inserted stuff, because we're still using fpm - we need to use debhelper instead, and somehow reconcile Ubuntu that way
 	chmod +x /tmp/postinst /tmp/prerm
 
 	(
@@ -66,6 +90,7 @@
 		    --name lxc-docker-$VERSION --version $PKGVERSION \
 		    --after-install /tmp/postinst \
 		    --before-remove /tmp/prerm \
+		    --after-remove /tmp/postrm \
 		    --architecture "$PACKAGE_ARCHITECTURE" \
 		    --prefix / \
 		    --depends lxc \
@@ -82,6 +107,8 @@
 		    --vendor "$PACKAGE_VENDOR" \
 		    --config-files /etc/init/docker.conf \
 		    --config-files /etc/init.d/docker \
+		    --config-files /etc/default/docker \
+		    --deb-compression xz \
 		    -t deb .
 		mkdir empty
 		fpm -s dir -C empty \
@@ -92,7 +119,12 @@
 		    --maintainer "$PACKAGE_MAINTAINER" \
 		    --url "$PACKAGE_URL" \
 		    --vendor "$PACKAGE_VENDOR" \
+		    --config-files /etc/init/docker.conf \
+		    --config-files /etc/init.d/docker \
+		    --config-files /etc/default/docker \
+		    --deb-compression xz \
 		    -t deb .
+		# note: the --config-files lines have to be duplicated to stop overwrite on package upgrade (since we have to use this funky virtual package)
 	)
 }
 
diff --git a/hack/release.sh b/hack/release.sh
index 56538ea..931ab6f 100755
--- a/hack/release.sh
+++ b/hack/release.sh
@@ -97,7 +97,7 @@
 	DEST=$1
 	F=`mktemp`
 	cat > $F
-	s3cmd --acl-public put $F $DEST
+	s3cmd --acl-public --mime-type='text/plain' put $F $DEST
 	rm -f $F
 }
 
@@ -107,14 +107,14 @@
 			echo "https://$BUCKET"
 			;;
 		*)
-			echo "http://$BUCKET.s3.amazonaws.com"
+			s3cmd ws-info s3://$BUCKET | awk -v 'FS=: +' '/http:\/\/'$BUCKET'/ { gsub(/\/+$/, "", $2); print $2 }'
 			;;
 	esac
 }
 
 # Upload the 'ubuntu' bundle to S3:
 # 1. A full APT repository is published at $BUCKET/ubuntu/
-# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/info
+# 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index
 release_ubuntu() {
 	[ -e bundles/$VERSION/ubuntu ] || {
 		echo >&2 './hack/make.sh must be run before release_ubuntu'
@@ -168,7 +168,7 @@
 
 	# Upload repo
 	s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/
-	cat <<EOF | write_to_s3 s3://$BUCKET/ubuntu/info
+	cat <<EOF | write_to_s3 s3://$BUCKET/ubuntu/index
 # Add the repository to your APT sources
 echo deb $(s3_url)/ubuntu docker main > /etc/apt/sources.list.d/docker.list
 # Then import the repository key
@@ -180,7 +180,12 @@
 # Alternatively, just use the curl-able install.sh script provided at $(s3_url)
 #
 EOF
-	echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu/info"
+
+	# Add redirect at /ubuntu/info for URL-backwards-compatibility
+	rm -rf /tmp/emptyfile && touch /tmp/emptyfile
+	s3cmd --acl-public --add-header='x-amz-website-redirect-location:/ubuntu/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/ubuntu/info
+
+	echo "APT repository uploaded. Instructions available at $(s3_url)/ubuntu"
 }
 
 # Upload a static binary to S3
@@ -189,14 +194,20 @@
 		echo >&2 './hack/make.sh must be run before release_binary'
 		exit 1
 	}
+
 	S3DIR=s3://$BUCKET/builds/Linux/x86_64
 	s3cmd --acl-public put bundles/$VERSION/binary/docker-$VERSION $S3DIR/docker-$VERSION
-	cat <<EOF | write_to_s3 s3://$BUCKET/builds/info
+	cat <<EOF | write_to_s3 s3://$BUCKET/builds/index
 # To install, run the following command as root:
 curl -O $(s3_url)/builds/Linux/x86_64/docker-$VERSION && chmod +x docker-$VERSION && sudo mv docker-$VERSION /usr/local/bin/docker
 # Then start docker in daemon mode:
 sudo /usr/local/bin/docker -d
 EOF
+
+	# Add redirect at /builds/info for URL-backwards-compatibility
+	rm -rf /tmp/emptyfile && touch /tmp/emptyfile
+	s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/builds/info
+
 	if [ -z "$NOLATEST" ]; then
 		echo "Copying docker-$VERSION to docker-latest"
 		s3cmd --acl-public cp $S3DIR/docker-$VERSION $S3DIR/docker-latest
diff --git a/image.go b/image.go
index d2d11ec..727a34a 100644
--- a/image.go
+++ b/image.go
@@ -134,10 +134,6 @@
 	return archive.Tar(layerPath, compression)
 }
 
-func (image *Image) ShortID() string {
-	return utils.TruncateID(image.ID)
-}
-
 func ValidateID(id string) error {
 	if id == "" {
 		return fmt.Errorf("Image id can't be empty")
diff --git a/iptables/iptables.go b/iptables/iptables.go
index 82ecf8b..0438bcb 100644
--- a/iptables/iptables.go
+++ b/iptables/iptables.go
@@ -55,9 +55,16 @@
 }
 
 func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr string, dest_port int) error {
+	daddr := ip.String()
+	if ip.IsUnspecified() {
+		// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
+		// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
+		// value" by both iptables and ip6tables.
+		daddr = "0/0"
+	}
 	if output, err := Raw("-t", "nat", fmt.Sprint(action), c.Name,
 		"-p", proto,
-		"-d", ip.String(),
+		"-d", daddr,
 		"--dport", strconv.Itoa(port),
 		"!", "-i", c.Bridge,
 		"-j", "DNAT",
diff --git a/network.go b/network.go
index 638fd94..3864cca 100644
--- a/network.go
+++ b/network.go
@@ -168,12 +168,28 @@
 	}
 
 	if config.EnableIptables {
+		// Enable NAT
 		if output, err := iptables.Raw("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
 			"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
 			return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
 		} else if len(output) != 0 {
 			return fmt.Errorf("Error iptables postrouting: %s", output)
 		}
+
+		// Accept incoming packets for existing connections
+		if output, err := iptables.Raw("-I", "FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"); err != nil {
+			return fmt.Errorf("Unable to allow incoming packets: %s", err)
+		} else if len(output) != 0 {
+			return fmt.Errorf("Error iptables allow incoming: %s", output)
+		}
+
+		// Accept all non-intercontainer outgoing packets
+		if output, err := iptables.Raw("-I", "FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"); err != nil {
+			return fmt.Errorf("Unable to allow outgoing packets: %s", err)
+		} else if len(output) != 0 {
+			return fmt.Errorf("Error iptables allow outgoing: %s", output)
+		}
+
 	}
 	return nil
 }
@@ -680,20 +696,30 @@
 
 	// Configure iptables for link support
 	if config.EnableIptables {
-		args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j", "DROP"}
+		args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j"}
+		acceptArgs := append(args, "ACCEPT")
+		dropArgs := append(args, "DROP")
 
 		if !config.InterContainerCommunication {
-			if !iptables.Exists(args...) {
+			iptables.Raw(append([]string{"-D"}, acceptArgs...)...)
+			if !iptables.Exists(dropArgs...) {
 				utils.Debugf("Disable inter-container communication")
-				if output, err := iptables.Raw(append([]string{"-A"}, args...)...); err != nil {
+				if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil {
 					return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err)
 				} else if len(output) != 0 {
-					return nil, fmt.Errorf("Error enabling iptables: %s", output)
+					return nil, fmt.Errorf("Error disabling intercontainer communication: %s", output)
 				}
 			}
 		} else {
-			utils.Debugf("Enable inter-container communication")
-			iptables.Raw(append([]string{"-D"}, args...)...)
+			iptables.Raw(append([]string{"-D"}, dropArgs...)...)
+			if !iptables.Exists(acceptArgs...) {
+				utils.Debugf("Enable inter-container communication")
+				if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil {
+					return nil, fmt.Errorf("Unable to allow intercontainer communication: %s", err)
+				} else if len(output) != 0 {
+					return nil, fmt.Errorf("Error enabling intercontainer communication: %s", output)
+				}
+			}
 		}
 	}
 
diff --git a/runtime.go b/runtime.go
index 1429d54..5072a00 100644
--- a/runtime.go
+++ b/runtime.go
@@ -186,6 +186,7 @@
 	if !container.State.Running {
 		close(container.waitLock)
 	} else if !nomonitor {
+		container.allocateNetwork()
 		go container.monitor()
 	}
 	return nil
@@ -195,7 +196,7 @@
 	if container.Name == "" {
 		name, err := generateRandomName(runtime)
 		if err != nil {
-			name = container.ShortID()
+			name = utils.TruncateID(container.ID)
 		}
 		container.Name = name
 
@@ -298,7 +299,7 @@
 		// Try to set the default name for a container if it exists prior to links
 		container.Name, err = generateRandomName(runtime)
 		if err != nil {
-			container.Name = container.ShortID()
+			container.Name = utils.TruncateID(container.ID)
 		}
 
 		if _, err := runtime.containerGraph.Set(container.Name, container.ID); err != nil {
@@ -506,32 +507,7 @@
 		return nil, nil, err
 	}
 
-	// Step 3: if hostname, build hostname and hosts files
-	container.HostnamePath = path.Join(container.root, "hostname")
-	ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
-
-	hostsContent := []byte(`
-127.0.0.1	localhost
-::1		localhost ip6-localhost ip6-loopback
-fe00::0		ip6-localnet
-ff00::0		ip6-mcastprefix
-ff02::1		ip6-allnodes
-ff02::2		ip6-allrouters
-`)
-
-	container.HostsPath = path.Join(container.root, "hosts")
-
-	if container.Config.Domainname != "" {
-		hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
-		hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...)
-	} else {
-		hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s\n", container.Config.Hostname)), hostsContent...)
-		hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s\n", container.Config.Hostname)), hostsContent...)
-	}
-
-	ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
-
-	// Step 4: register the container
+	// Step 3: register the container
 	if err := runtime.Register(container); err != nil {
 		return nil, nil, err
 	}
diff --git a/runtime_test.go b/runtime_test.go
index af7cc6e..390141f 100644
--- a/runtime_test.go
+++ b/runtime_test.go
@@ -3,11 +3,13 @@
 import (
 	"bytes"
 	"fmt"
+	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/sysinit"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"log"
 	"net"
+	"net/url"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -122,22 +124,19 @@
 }
 
 func setupBaseImage() {
-	config := &DaemonConfig{
-		Root:        unitTestStoreBase,
-		AutoRestart: false,
-		BridgeIface: unitTestNetworkBridge,
-	}
-	runtime, err := NewRuntimeFromDirectory(config)
+	eng, err := engine.New(unitTestStoreBase)
 	if err != nil {
+		log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err)
+	}
+	job := eng.Job("initapi")
+	job.Setenv("Root", unitTestStoreBase)
+	job.SetenvBool("Autorestart", false)
+	job.Setenv("BridgeIface", unitTestNetworkBridge)
+	if err := job.Run(); err != nil {
 		log.Fatalf("Unable to create a runtime for tests:", err)
 	}
-
-	// Create the "Server"
-	srv := &Server{
-		runtime:     runtime,
-		pullingPool: make(map[string]struct{}),
-		pushingPool: make(map[string]struct{}),
-	}
+	srv := mkServerFromEngine(eng, log.New(os.Stderr, "", 0))
+	runtime := srv.runtime
 
 	// If the unit test is not found, try to download it.
 	if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID {
@@ -153,18 +152,22 @@
 		utils.Debugf("Global runtime already exists. Skipping.")
 		return
 	}
-	globalRuntime = mkRuntime(log.New(os.Stderr, "", 0))
-	srv := &Server{
-		runtime:     globalRuntime,
-		pullingPool: make(map[string]struct{}),
-		pushingPool: make(map[string]struct{}),
-	}
+	t := log.New(os.Stderr, "", 0)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	globalRuntime = srv.runtime
 
 	// Spawn a Daemon
 	go func() {
 		utils.Debugf("Spawning global daemon for integration tests")
-		if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil {
-			log.Fatalf("Unable to spawn the test daemon:", err)
+		listenURL := &url.URL{
+			Scheme: testDaemonProto,
+			Host:   testDaemonAddr,
+		}
+		job := eng.Job("serveapi", listenURL.String())
+		job.SetenvBool("Logging", os.Getenv("DEBUG") != "")
+		if err := job.Run(); err != nil {
+			log.Fatalf("Unable to spawn the test daemon: %s", err)
 		}
 	}()
 	// Give some time to ListenAndServer to actually start
@@ -184,7 +187,7 @@
 			return image
 		}
 	}
-	log.Fatalf("Test image %v not found", unitTestImageID)
+	log.Fatalf("Test image %v not found in %s: %s", unitTestImageID, runtime.graph.Root, imgs)
 	return nil
 }
 
@@ -646,20 +649,17 @@
 }
 
 func TestDefaultContainerName(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
-	srv := &Server{runtime: runtime}
 
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	shortId, _, err := srv.ContainerCreate(config, "some_name")
-	if err != nil {
-		t.Fatal(err)
-	}
-	container := runtime.Get(shortId)
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "some_name"))
 	containerID := container.ID
 
 	if container.Name != "/some_name" {
@@ -683,20 +683,17 @@
 }
 
 func TestRandomContainerName(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
-	srv := &Server{runtime: runtime}
 
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	shortId, _, err := srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
-	container := runtime.Get(shortId)
+	container := runtime.Get(createTestContainer(eng, config, t))
 	containerID := container.ID
 
 	if container.Name == "" {
@@ -720,20 +717,17 @@
 }
 
 func TestLinkChildContainer(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
-	srv := &Server{runtime: runtime}
 
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
-	if err != nil {
-		t.Fatal(err)
-	}
-	container := runtime.Get(shortId)
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
 
 	webapp, err := runtime.GetByName("/webapp")
 	if err != nil {
@@ -749,12 +743,7 @@
 		t.Fatal(err)
 	}
 
-	shortId, _, err = srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	childContainer := runtime.Get(shortId)
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
 
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
 		t.Fatal(err)
@@ -771,20 +760,17 @@
 }
 
 func TestGetAllChildren(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
-	srv := &Server{runtime: runtime}
 
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	shortId, _, err := srv.ContainerCreate(config, "/webapp")
-	if err != nil {
-		t.Fatal(err)
-	}
-	container := runtime.Get(shortId)
+	container := runtime.Get(createNamedTestContainer(eng, config, t, "/webapp"))
 
 	webapp, err := runtime.GetByName("/webapp")
 	if err != nil {
@@ -800,12 +786,7 @@
 		t.Fatal(err)
 	}
 
-	shortId, _, err = srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	childContainer := runtime.Get(shortId)
+	childContainer := runtime.Get(createTestContainer(eng, config, t))
 
 	if err := runtime.RegisterLink(webapp, childContainer, "db"); err != nil {
 		t.Fatal(err)
diff --git a/server.go b/server.go
index 3d27d50..1d24f73 100644
--- a/server.go
+++ b/server.go
@@ -33,30 +33,25 @@
 }
 
 func init() {
-	engine.Register("serveapi", JobServeApi)
+	engine.Register("initapi", jobInitApi)
 }
 
-func JobServeApi(job *engine.Job) string {
-	srv, err := NewServer(ConfigFromJob(job))
+// jobInitApi runs the remote api server `srv` as a daemon,
+// Only one api server can run at the same time - this is enforced by a pidfile.
+// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
+func jobInitApi(job *engine.Job) string {
+	job.Logf("Creating server")
+	srv, err := NewServer(job.Eng, ConfigFromJob(job))
 	if err != nil {
 		return err.Error()
 	}
-	defer srv.Close()
-	if err := srv.Daemon(); err != nil {
-		return err.Error()
+	if srv.runtime.config.Pidfile != "" {
+		job.Logf("Creating pidfile")
+		if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
+			log.Fatal(err)
+		}
 	}
-	return "0"
-}
-
-// Daemon runs the remote api server `srv` as a daemon,
-// Only one api server can run at the same time - this is enforced by a pidfile.
-// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
-func (srv *Server) Daemon() error {
-	if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
-		log.Fatal(err)
-	}
-	defer utils.RemovePidFile(srv.runtime.config.Pidfile)
-
+	job.Logf("Setting up signal traps")
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
 	go func() {
@@ -66,8 +61,21 @@
 		srv.Close()
 		os.Exit(0)
 	}()
+	job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
+	if err := job.Eng.Register("create", srv.ContainerCreate); err != nil {
+		return err.Error()
+	}
+	if err := job.Eng.Register("start", srv.ContainerStart); err != nil {
+		return err.Error()
+	}
+	if err := job.Eng.Register("serveapi", srv.ListenAndServe); err != nil {
+		return err.Error()
+	}
+	return "0"
+}
 
-	protoAddrs := srv.runtime.config.ProtoAddresses
+func (srv *Server) ListenAndServe(job *engine.Job) string {
+	protoAddrs := job.Args
 	chErrors := make(chan error, len(protoAddrs))
 	for _, protoAddr := range protoAddrs {
 		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
@@ -81,19 +89,20 @@
 				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
 			}
 		default:
-			return fmt.Errorf("Invalid protocol format.")
+			return "Invalid protocol format."
 		}
 		go func() {
-			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true)
+			// FIXME: merge Server.ListenAndServe with ListenAndServe
+			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"))
 		}()
 	}
 	for i := 0; i < len(protoAddrs); i += 1 {
 		err := <-chErrors
 		if err != nil {
-			return err
+			return err.Error()
 		}
 	}
-	return nil
+	return "0"
 }
 
 func (srv *Server) DockerVersion() APIVersion {
@@ -154,7 +163,7 @@
 			if err := container.Kill(); err != nil {
 				return fmt.Errorf("Cannot kill container %s: %s", name, err)
 			}
-			srv.LogEvent("kill", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
+			srv.LogEvent("kill", container.ID, srv.runtime.repositories.ImageName(container.Image))
 		} else {
 			// Otherwise, just send the requested signal
 			if err := container.kill(sig); err != nil {
@@ -180,7 +189,7 @@
 		if _, err := io.Copy(out, data); err != nil {
 			return err
 		}
-		srv.LogEvent("export", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
+		srv.LogEvent("export", container.ID, srv.runtime.repositories.ImageName(container.Image))
 		return nil
 	}
 	return fmt.Errorf("No such container: %s", name)
@@ -198,39 +207,39 @@
 	return results.Results, nil
 }
 
-func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) error {
 	out = utils.NewWriteFlusher(out)
 	img, err := srv.runtime.repositories.LookupImage(name)
 	if err != nil {
-		return "", err
+		return err
 	}
 
 	file, err := utils.Download(url, out)
 	if err != nil {
-		return "", err
+		return err
 	}
 	defer file.Body.Close()
 
 	config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
 	if err != nil {
-		return "", err
+		return err
 	}
 
 	c, _, err := srv.runtime.Create(config, "")
 	if err != nil {
-		return "", err
+		return err
 	}
 
-	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, true), path); err != nil {
-		return "", err
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("", "Downloading", "%8v/%v (%v)"), sf, false), path); err != nil {
+		return err
 	}
 	// FIXME: Handle custom repo, tag comment, author
 	img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil)
 	if err != nil {
-		return "", err
+		return err
 	}
-	out.Write(sf.FormatStatus("", img.ID))
-	return img.ShortID(), nil
+	out.Write(sf.FormatStatus(img.ID, ""))
+	return nil
 }
 
 func (srv *Server) ImagesViz(out io.Writer) error {
@@ -250,9 +259,9 @@
 			return fmt.Errorf("Error while getting parent image: %v", err)
 		}
 		if parentImage != nil {
-			out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
+			out.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
 		} else {
-			out.Write([]byte(" base -> \"" + image.ShortID() + "\" [style=invis]\n"))
+			out.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
 		}
 	}
 
@@ -465,7 +474,7 @@
 			continue
 		}
 		if before != "" {
-			if container.ShortID() == before {
+			if container.ID == before || utils.TruncateID(container.ID) == before {
 				foundBefore = true
 				continue
 			}
@@ -476,7 +485,7 @@
 		if displayed == n {
 			break
 		}
-		if container.ShortID() == since {
+		if container.ID == since || utils.TruncateID(container.ID) == since {
 			break
 		}
 		displayed++
@@ -518,7 +527,7 @@
 	if err != nil {
 		return "", err
 	}
-	return img.ShortID(), err
+	return img.ID, err
 }
 
 func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
@@ -1018,37 +1027,47 @@
 			return err
 		}
 	}
-	out.Write(sf.FormatStatus("", img.ShortID()))
+	out.Write(sf.FormatStatus("", img.ID))
 	return nil
 }
 
-func (srv *Server) ContainerCreate(config *Config, name string) (string, []string, error) {
-	if config.Memory != 0 && config.Memory < 524288 {
-		return "", nil, fmt.Errorf("Memory limit must be given in bytes (minimum 524288 bytes)")
+func (srv *Server) ContainerCreate(job *engine.Job) string {
+	var name string
+	if len(job.Args) == 1 {
+		name = job.Args[0]
+	} else if len(job.Args) > 1 {
+		return fmt.Sprintf("Usage: %s ", job.Name)
 	}
-
+	var config Config
+	if err := job.ExportEnv(&config); err != nil {
+		return err.Error()
+	}
+	if config.Memory != 0 && config.Memory < 524288 {
+		return "Minimum memory limit allowed is 512k"
+	}
 	if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit {
 		config.Memory = 0
 	}
-
 	if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit {
 		config.MemorySwap = -1
 	}
-	container, buildWarnings, err := srv.runtime.Create(config, name)
+	container, buildWarnings, err := srv.runtime.Create(&config, name)
 	if err != nil {
 		if srv.runtime.graph.IsNotExist(err) {
-
 			_, tag := utils.ParseRepositoryTag(config.Image)
 			if tag == "" {
 				tag = DEFAULTTAG
 			}
-
-			return "", nil, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
+			return fmt.Sprintf("No such image: %s (tag: %s)", config.Image, tag)
 		}
-		return "", nil, err
+		return err.Error()
 	}
-	srv.LogEvent("create", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
-	return container.ShortID(), buildWarnings, nil
+	srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image))
+	job.Printf("%s\n", container.ID)
+	for _, warning := range buildWarnings {
+		job.Errorf("%s\n", warning)
+	}
+	return "0"
 }
 
 func (srv *Server) ContainerRestart(name string, t int) error {
@@ -1056,7 +1075,7 @@
 		if err := container.Restart(t); err != nil {
 			return fmt.Errorf("Cannot restart container %s: %s", name, err)
 		}
-		srv.LogEvent("restart", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
+		srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image))
 	} else {
 		return fmt.Errorf("No such container: %s", name)
 	}
@@ -1112,7 +1131,7 @@
 		if err := srv.runtime.Destroy(container); err != nil {
 			return fmt.Errorf("Cannot destroy container %s: %s", name, err)
 		}
-		srv.LogEvent("destroy", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
+		srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image))
 
 		if removeVolume {
 			// Retrieve all volumes from all remaining containers
@@ -1229,8 +1248,8 @@
 			return nil, err
 		}
 		if tagDeleted {
-			imgs = append(imgs, APIRmi{Untagged: img.ShortID()})
-			srv.LogEvent("untag", img.ShortID(), "")
+			imgs = append(imgs, APIRmi{Untagged: img.ID})
+			srv.LogEvent("untag", img.ID, "")
 		}
 	}
 	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
@@ -1258,6 +1277,26 @@
 		}
 		return nil, nil
 	}
+
+	// Prevent deletion if image is used by a running container
+	for _, container := range srv.runtime.List() {
+		if container.State.Running {
+			parent, err := srv.runtime.repositories.LookupImage(container.Image)
+			if err != nil {
+				return nil, err
+			}
+
+			if err := parent.WalkHistory(func(p *Image) error {
+				if img.ID == p.ID {
+					return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it", name, container.ID)
+				}
+				return nil
+			}); err != nil {
+				return nil, err
+			}
+		}
+	}
+
 	if strings.Contains(img.ID, name) {
 		//delete via ID
 		return srv.deleteImage(img, "", "")
@@ -1303,7 +1342,6 @@
 		return fmt.Errorf("No such container: %s", name)
 	}
 
-	// Register links
 	if hostConfig != nil && hostConfig.Links != nil {
 		for _, l := range hostConfig.Links {
 			parts, err := parseLink(l)
@@ -1317,7 +1355,6 @@
 			if child == nil {
 				return fmt.Errorf("Could not get container for %s", parts["name"])
 			}
-
 			if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil {
 				return err
 			}
@@ -1333,41 +1370,57 @@
 	return nil
 }
 
-func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
+func (srv *Server) ContainerStart(job *engine.Job) string {
+	if len(job.Args) < 1 {
+		return fmt.Sprintf("Usage: %s container_id", job.Name)
+	}
+	name := job.Args[0]
 	runtime := srv.runtime
 	container := runtime.Get(name)
 
-	if hostConfig != nil {
+	if container == nil {
+		return fmt.Sprintf("No such container: %s", name)
+	}
+	// If no environment was set, then no hostconfig was passed.
+	if len(job.Environ()) > 0 {
+		var hostConfig HostConfig
+		if err := job.ExportEnv(&hostConfig); err != nil {
+			return err.Error()
+		}
+		// Validate the HostConfig binds. Make sure that:
+		// 1) the source of a bind mount isn't /
+		//         The bind mount "/:/foo" isn't allowed.
+		// 2) Check that the source exists
+		//        The source to be bind mounted must exist.
 		for _, bind := range hostConfig.Binds {
 			splitBind := strings.Split(bind, ":")
 			source := splitBind[0]
 
 			// refuse to bind mount "/" to the container
 			if source == "/" {
-				return fmt.Errorf("Invalid bind mount '%s' : source can't be '/'", bind)
+				return fmt.Sprintf("Invalid bind mount '%s' : source can't be '/'", bind)
 			}
 
 			// ensure the source exists on the host
 			_, err := os.Stat(source)
 			if err != nil && os.IsNotExist(err) {
-				return fmt.Errorf("Invalid bind mount '%s' : source doesn't exist", bind)
+				return fmt.Sprintf("Invalid bind mount '%s' : source doesn't exist", bind)
 			}
 		}
-	}
-
-	if container == nil {
-		return fmt.Errorf("No such container: %s", name)
-	}
-	if hostConfig != nil {
-		container.hostConfig = hostConfig
+		// Register any links from the host config before starting the container
+		// FIXME: we could just pass the container here, no need to lookup by name again.
+		if err := srv.RegisterLinks(name, &hostConfig); err != nil {
+			return err.Error()
+		}
+		container.hostConfig = &hostConfig
 		container.ToDisk()
 	}
 	if err := container.Start(); err != nil {
-		return fmt.Errorf("Cannot start container %s: %s", name, err)
+		return fmt.Sprintf("Cannot start container %s: %s", name, err)
 	}
-	srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
+	srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image))
 
-	return nil
+	return "0"
 }
 
 func (srv *Server) ContainerStop(name string, t int) error {
@@ -1375,7 +1428,7 @@
 		if err := container.Stop(t); err != nil {
 			return fmt.Errorf("Cannot stop container %s: %s", name, err)
 		}
-		srv.LogEvent("stop", container.ShortID(), srv.runtime.repositories.ImageName(container.Image))
+		srv.LogEvent("stop", container.ID, srv.runtime.repositories.ImageName(container.Image))
 	} else {
 		return fmt.Errorf("No such container: %s", name)
 	}
@@ -1518,12 +1571,13 @@
 
 }
 
-func NewServer(config *DaemonConfig) (*Server, error) {
+func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
 	runtime, err := NewRuntime(config)
 	if err != nil {
 		return nil, err
 	}
 	srv := &Server{
+		Eng:         eng,
 		runtime:     runtime,
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
@@ -1567,4 +1621,5 @@
 	events      []utils.JSONMessage
 	listeners   map[string]chan utils.JSONMessage
 	reqFactory  *utils.HTTPRequestFactory
+	Eng         *engine.Engine
 }
diff --git a/server_test.go b/server_test.go
index 4072344..1ab3842 100644
--- a/server_test.go
+++ b/server_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"github.com/dotcloud/docker/utils"
+	"io/ioutil"
 	"strings"
 	"testing"
 	"time"
@@ -79,20 +80,17 @@
 }
 
 func TestCreateRm(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	id, _, err := srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
+	id := createTestContainer(eng, config, t)
 
 	if len(runtime.List()) != 1 {
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
@@ -109,27 +107,28 @@
 }
 
 func TestCreateRmVolumes(t *testing.T) {
-	runtime := mkRuntime(t)
-	defer nuke(runtime)
+	eng := NewTestEngine(t)
 
-	srv := &Server{runtime: runtime}
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
+	defer nuke(runtime)
 
 	config, hostConfig, _, err := ParseRun([]string{"-v", "/srv", GetTestImage(runtime).ID, "echo test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	id, _, err := srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
+	id := createTestContainer(eng, config, t)
 
 	if len(runtime.List()) != 1 {
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
 	}
 
-	err = srv.ContainerStart(id, hostConfig)
-	if err != nil {
+	job := eng.Job("start", id)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -148,20 +147,17 @@
 }
 
 func TestCommit(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
 	config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	id, _, err := srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
+	id := createTestContainer(eng, config, t)
 
 	if _, err := srv.ContainerCommit(id, "testrepo", "testtag", "", "", config); err != nil {
 		t.Fatal(err)
@@ -169,26 +165,27 @@
 }
 
 func TestCreateStartRestartStopStartKillRm(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
-	srv := &Server{runtime: runtime}
-
 	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	id, _, err := srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
+	id := createTestContainer(eng, config, t)
 
 	if len(runtime.List()) != 1 {
 		t.Errorf("Expected 1 container, %v found", len(runtime.List()))
 	}
 
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
+	job := eng.Job("start", id)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -200,7 +197,11 @@
 		t.Fatal(err)
 	}
 
-	if err := srv.ContainerStart(id, hostConfig); err != nil {
+	job = eng.Job("start", id)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -220,22 +221,22 @@
 }
 
 func TestRunWithTooLowMemoryLimit(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
 
 	// Try to create a container with a memory limit of 1 byte less than the minimum allowed limit.
-	if _, _, err := (*Server).ContainerCreate(&Server{runtime: runtime},
-		&Config{
-			Image:     GetTestImage(runtime).ID,
-			Memory:    524287,
-			CpuShares: 1000,
-			Cmd:       []string{"/bin/cat"},
-		},
-		"",
-	); err == nil {
+	job := eng.Job("create")
+	job.Setenv("Image", GetTestImage(runtime).ID)
+	job.Setenv("Memory", "524287")
+	job.Setenv("CpuShares", "1000")
+	job.SetenvList("Cmd", []string{"/bin/cat"})
+	var id string
+	job.StdoutParseString(&id)
+	if err := job.Run(); err == nil {
 		t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!")
 	}
-
 }
 
 func TestContainerTop(t *testing.T) {
@@ -384,9 +385,10 @@
 }
 
 func TestRmi(t *testing.T) {
-	runtime := mkRuntime(t)
+	eng := NewTestEngine(t)
+	srv := mkServerFromEngine(eng, t)
+	runtime := srv.runtime
 	defer nuke(runtime)
-	srv := &Server{runtime: runtime}
 
 	initialImages, err := srv.Images(false, "")
 	if err != nil {
@@ -398,14 +400,14 @@
 		t.Fatal(err)
 	}
 
-	containerID, _, err := srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
+	containerID := createTestContainer(eng, config, t)
 
 	//To remove
-	err = srv.ContainerStart(containerID, hostConfig)
-	if err != nil {
+	job := eng.Job("start", containerID)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -419,14 +421,14 @@
 		t.Fatal(err)
 	}
 
-	containerID, _, err = srv.ContainerCreate(config, "")
-	if err != nil {
-		t.Fatal(err)
-	}
+	containerID = createTestContainer(eng, config, t)
 
 	//To remove
-	err = srv.ContainerStart(containerID, hostConfig)
-	if err != nil {
+	job = eng.Job("start", containerID)
+	if err := job.ImportEnv(hostConfig); err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
 		t.Fatal(err)
 	}
 
@@ -521,3 +523,25 @@
 		t.Fatal("incorrect number of matches returned")
 	}
 }
+
+func TestImageInsert(t *testing.T) {
+	runtime := mkRuntime(t)
+	defer nuke(runtime)
+	srv := &Server{runtime: runtime}
+	sf := utils.NewStreamFormatter(true)
+
+	// bad image name fails
+	if err := srv.ImageInsert("foo", "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err == nil {
+		t.Fatal("expected an error and got none")
+	}
+
+	// bad url fails
+	if err := srv.ImageInsert(GetTestImage(runtime).ID, "http://bad_host_name_that_will_totally_fail.com/", "/foo", ioutil.Discard, sf); err == nil {
+		t.Fatal("expected an error and got none")
+	}
+
+	// success returns nil
+	if err := srv.ImageInsert(GetTestImage(runtime).ID, "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err != nil {
+		t.Fatalf("expected no error, but got %v", err)
+	}
+}
diff --git a/utils.go b/utils.go
index 433d00a..9d57428 100644
--- a/utils.go
+++ b/utils.go
@@ -119,6 +119,15 @@
 	}
 	if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 {
 		userConf.ExposedPorts = imageConf.ExposedPorts
+	} else if imageConf.ExposedPorts != nil {
+		if userConf.ExposedPorts == nil {
+			userConf.ExposedPorts = make(map[Port]struct{})
+		}
+		for port := range imageConf.ExposedPorts {
+			if _, exists := userConf.ExposedPorts[port]; !exists {
+				userConf.ExposedPorts[port] = struct{}{}
+			}
+		}
 	}
 
 	if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 {
@@ -325,20 +334,6 @@
 	return nil
 }
 
-func RootIsShared() bool {
-	if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
-		for _, line := range strings.Split(string(data), "\n") {
-			cols := strings.Split(line, " ")
-			if len(cols) >= 6 && cols[4] == "/" {
-				return strings.HasPrefix(cols[6], "shared")
-			}
-		}
-	}
-
-	// No idea, probably safe to assume so
-	return true
-}
-
 func BtrfsReflink(fd_out, fd_in uintptr) error {
 	res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in))
 	if res != 0 {
@@ -353,6 +348,20 @@
 	return utils.PartParser("name:alias", rawLink)
 }
 
+func RootIsShared() bool {
+	if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
+		for _, line := range strings.Split(string(data), "\n") {
+			cols := strings.Split(line, " ")
+			if len(cols) >= 6 && cols[4] == "/" {
+				return strings.HasPrefix(cols[6], "shared")
+			}
+		}
+	}
+
+	// No idea, probably safe to assume so
+	return true
+}
+
 type checker struct {
 	runtime *Runtime
 }
diff --git a/utils/utils.go b/utils/utils.go
index b760ccf..d16ffe3 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -28,6 +28,12 @@
 	INITSHA1  string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary
 )
 
+// A common interface to access the Fatal method of
+// both testing.B and testing.T.
+type Fataler interface {
+	Fatal(args ...interface{})
+}
+
 // ListOpts type
 type ListOpts []string
 
@@ -177,6 +183,40 @@
 	return fmt.Sprintf("%.4g %s", sizef, units[i])
 }
 
+// Parses a human-readable string representing an amount of RAM
+// in bytes, kibibytes, mebibytes or gibibytes, and returns the
+// number of bytes, or -1 if the string is unparseable.
+// Units are case-insensitive, and the 'b' suffix is optional.
+func RAMInBytes(size string) (bytes int64, err error) {
+	re, error := regexp.Compile("^(\\d+)([kKmMgG])?[bB]?$")
+	if error != nil {
+		return -1, error
+	}
+
+	matches := re.FindStringSubmatch(size)
+
+	if len(matches) != 3 {
+		return -1, fmt.Errorf("Invalid size: '%s'", size)
+	}
+
+	memLimit, error := strconv.ParseInt(matches[1], 10, 0)
+	if error != nil {
+		return -1, error
+	}
+
+	unit := strings.ToLower(matches[2])
+
+	if unit == "k" {
+		memLimit *= 1024
+	} else if unit == "m" {
+		memLimit *= 1024 * 1024
+	} else if unit == "g" {
+		memLimit *= 1024 * 1024 * 1024
+	}
+
+	return memLimit, nil
+}
+
 func Trunc(s string, maxlen int) string {
 	if len(s) <= maxlen {
 		return s
@@ -910,7 +950,7 @@
 func GetNameserversAsCIDR(resolvConf []byte) []string {
 	var parsedResolvConf = StripComments(resolvConf, []byte("#"))
 	nameservers := []string{}
-	re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]\.){3}([0-9]))\s*$`)
+	re := regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`)
 	for _, line := range bytes.Split(parsedResolvConf, []byte("\n")) {
 		var ns = re.FindSubmatch(line)
 		if len(ns) > 0 {
diff --git a/utils/utils_test.go b/utils/utils_test.go
index 5377c18..350ebb9 100644
--- a/utils/utils_test.go
+++ b/utils/utils_test.go
@@ -265,6 +265,39 @@
 	}
 }
 
+func TestRAMInBytes(t *testing.T) {
+	assertRAMInBytes(t, "32", false, 32)
+	assertRAMInBytes(t, "32b", false, 32)
+	assertRAMInBytes(t, "32B", false, 32)
+	assertRAMInBytes(t, "32k", false, 32*1024)
+	assertRAMInBytes(t, "32K", false, 32*1024)
+	assertRAMInBytes(t, "32kb", false, 32*1024)
+	assertRAMInBytes(t, "32Kb", false, 32*1024)
+	assertRAMInBytes(t, "32Mb", false, 32*1024*1024)
+	assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024)
+
+	assertRAMInBytes(t, "", true, -1)
+	assertRAMInBytes(t, "hello", true, -1)
+	assertRAMInBytes(t, "-32", true, -1)
+	assertRAMInBytes(t, " 32 ", true, -1)
+	assertRAMInBytes(t, "32 mb", true, -1)
+	assertRAMInBytes(t, "32m b", true, -1)
+	assertRAMInBytes(t, "32bm", true, -1)
+}
+
+func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) {
+	actualBytes, err := RAMInBytes(size)
+	if (err != nil) && !expectError {
+		t.Errorf("Unexpected error parsing '%s': %s", size, err)
+	}
+	if (err == nil) && expectError {
+		t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
+	}
+	if actualBytes != expectedBytes {
+		t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
+	}
+}
+
 func TestParseHost(t *testing.T) {
 	if addr, err := ParseHost("127.0.0.1", 4243, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" {
 		t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
@@ -448,12 +481,12 @@
 func TestGetNameserversAsCIDR(t *testing.T) {
 	for resolv, result := range map[string][]string{`
 nameserver 1.2.3.4
-nameserver 4.3.2.1
-search example.com`: {"1.2.3.4/32", "4.3.2.1/32"},
+nameserver 40.3.200.10
+search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
 		`search example.com`: {},
 		`nameserver 1.2.3.4
 search example.com
-nameserver 4.3.2.1`: {"1.2.3.4/32", "4.3.2.1/32"},
+nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
 		``: {},
 		`  nameserver 1.2.3.4   `: {"1.2.3.4/32"},
 		`search example.com
diff --git a/utils_test.go b/utils_test.go
index 7723a4d..a9678a9 100644
--- a/utils_test.go
+++ b/utils_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
@@ -20,64 +21,97 @@
 
 // Create a temporary runtime suitable for unit testing.
 // Call t.Fatal() at the first error.
-func mkRuntime(f Fataler) *Runtime {
-	// Use the caller function name as a prefix.
-	// This helps trace temp directories back to their test.
-	pc, _, _, _ := runtime.Caller(1)
-	callerLongName := runtime.FuncForPC(pc).Name()
-	parts := strings.Split(callerLongName, ".")
-	callerShortName := parts[len(parts)-1]
-	if globalTestID == "" {
-		globalTestID = GenerateID()[:4]
-	}
-	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
-	utils.Debugf("prefix = '%s'", prefix)
-
-	runtime, err := newTestRuntime(prefix)
+func mkRuntime(f utils.Fataler) *Runtime {
+	root, err := newTestDirectory(unitTestStoreBase)
 	if err != nil {
 		f.Fatal(err)
 	}
-	return runtime
-}
-
-// A common interface to access the Fatal method of
-// both testing.B and testing.T.
-type Fataler interface {
-	Fatal(args ...interface{})
-}
-
-func newTestRuntime(prefix string) (runtime *Runtime, err error) {
-	if prefix == "" {
-		prefix = "docker-test-"
-	}
-	utils.Debugf("prefix = %s", prefix)
-	utils.Debugf("newTestRuntime start")
-	root, err := ioutil.TempDir("", prefix)
-	defer func() {
-		utils.Debugf("newTestRuntime: %s", root)
-	}()
-	if err != nil {
-		return nil, err
-	}
-	if err := os.Remove(root); err != nil {
-		return nil, err
-	}
-	utils.Debugf("Copying %s to %s", unitTestStoreBase, root)
-	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
-		utils.Debugf("ERROR: Copying %s to %s returned %s", unitTestStoreBase, root, err)
-		return nil, err
-	}
-
 	config := &DaemonConfig{
 		Root:        root,
 		AutoRestart: false,
 	}
-	runtime, err = NewRuntimeFromDirectory(config)
+	r, err := NewRuntimeFromDirectory(config)
 	if err != nil {
-		return nil, err
+		f.Fatal(err)
 	}
-	runtime.UpdateCapabilities(true)
-	return runtime, nil
+	r.UpdateCapabilities(true)
+	return r
+}
+
+func createNamedTestContainer(eng *engine.Engine, config *Config, f utils.Fataler, name string) (shortId string) {
+	job := eng.Job("create", name)
+	if err := job.ImportEnv(config); err != nil {
+		f.Fatal(err)
+	}
+	job.StdoutParseString(&shortId)
+	if err := job.Run(); err != nil {
+		f.Fatal(err)
+	}
+	return
+}
+
+func createTestContainer(eng *engine.Engine, config *Config, f utils.Fataler) (shortId string) {
+	return createNamedTestContainer(eng, config, f, "")
+}
+
+func mkServerFromEngine(eng *engine.Engine, t utils.Fataler) *Server {
+	iSrv := eng.Hack_GetGlobalVar("httpapi.server")
+	if iSrv == nil {
+		panic("Legacy server field not set in engine")
+	}
+	srv, ok := iSrv.(*Server)
+	if !ok {
+		panic("Legacy server field in engine does not cast to *Server")
+	}
+	return srv
+}
+
+func NewTestEngine(t utils.Fataler) *engine.Engine {
+	root, err := newTestDirectory(unitTestStoreBase)
+	if err != nil {
+		t.Fatal(err)
+	}
+	eng, err := engine.New(root)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// Load default plugins
+	// (This is manually copied and modified from main() until we have a more generic plugin system)
+	job := eng.Job("initapi")
+	job.Setenv("Root", root)
+	job.SetenvBool("AutoRestart", false)
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+	return eng
+}
+
+func newTestDirectory(templateDir string) (dir string, err error) {
+	if globalTestID == "" {
+		globalTestID = GenerateID()[:4]
+	}
+	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, getCallerName(2))
+	if prefix == "" {
+		prefix = "docker-test-"
+	}
+	dir, err = ioutil.TempDir("", prefix)
+	if err = os.Remove(dir); err != nil {
+		return
+	}
+	if err = utils.CopyDirectory(templateDir, dir); err != nil {
+		return
+	}
+	return
+}
+
+func getCallerName(depth int) string {
+	// Use the caller function name as a prefix.
+	// This helps trace temp directories back to their test.
+	pc, _, _, _ := runtime.Caller(depth + 1)
+	callerLongName := runtime.FuncForPC(pc).Name()
+	parts := strings.Split(callerLongName, ".")
+	callerShortName := parts[len(parts)-1]
+	return callerShortName
 }
 
 // Write `content` to the file at path `dst`, creating it if necessary,
@@ -249,7 +283,9 @@
 		Volumes:   volumesUser,
 	}
 
-	MergeConfig(configUser, configImage)
+	if err := MergeConfig(configUser, configImage); err != nil {
+		t.Error(err)
+	}
 
 	if len(configUser.Dns) != 3 {
 		t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns))
@@ -261,7 +297,7 @@
 	}
 
 	if len(configUser.ExposedPorts) != 3 {
-		t.Fatalf("Expected 3 portSpecs, 1111, 2222 and 3333, found %d", len(configUser.PortSpecs))
+		t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
 	}
 	for portSpecs := range configUser.ExposedPorts {
 		if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
@@ -289,6 +325,28 @@
 	if configUser.VolumesFrom != "1111" {
 		t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom)
 	}
+
+	ports, _, err := parsePortSpecs([]string{"0000"})
+	if err != nil {
+		t.Error(err)
+	}
+	configImage2 := &Config{
+		ExposedPorts: ports,
+	}
+
+	if err := MergeConfig(configUser, configImage2); err != nil {
+		t.Error(err)
+	}
+
+	if len(configUser.ExposedPorts) != 4 {
+		t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
+	}
+	for portSpecs := range configUser.ExposedPorts {
+		if portSpecs.Port() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
+			t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs)
+		}
+	}
+
 }
 
 func TestParseLxcConfOpt(t *testing.T) {