Merge pull request #1628 from dotcloud/bump_0.6.0

Bump to 0.6.0
This commit is contained in:
Michael Crosby 2013-08-23 10:45:14 -07:00
commit 588f8e1cec
136 changed files with 5768 additions and 1719 deletions

4
.gitignore vendored
View file

@ -5,10 +5,7 @@ docker/docker
a.out
*.orig
build_src
command-line-arguments.test
.flymake*
docker.test
auth/auth.test
.idea
.DS_Store
docs/_build
@ -16,3 +13,4 @@ docs/_static
docs/_templates
.gopath/
.dotcloud
*.test

View file

@ -1,4 +1,4 @@
# Generate AUTHORS: git log --all --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12
<charles.hooper@dotcloud.com> <chooper@plumata.com>
<daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
<daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
@ -25,3 +25,4 @@ Walter Stanish <walter@pratyeka.org>
Roberto Hashioka <roberto_hashioka@hotmail.com>
Konstantin Pelykh <kpelykh@zettaset.com>
David Sissitka <me@dsissitka.com>
Nolan Darilek <nolan@thewordnerd.info>

View file

@ -22,6 +22,7 @@ Caleb Spare <cespare@gmail.com>
Calen Pennington <cale@edx.org>
Charles Hooper <charles.hooper@dotcloud.com>
Christopher Currie <codemonkey+github@gmail.com>
Colin Rice <colin@daedrum.net>
Daniel Gasienica <daniel@gasienica.ch>
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
Daniel Robinson <gottagetmac@gmail.com>
@ -39,6 +40,7 @@ Erno Hopearuoho <erno.hopearuoho@gmail.com>
Evan Wies <evan@neomantra.net>
ezbercih <cem.ezberci@gmail.com>
Fabrizio Regini <freegenie@gmail.com>
Fareed Dudhia <fareeddudhia@googlemail.com>
Flavio Castelli <fcastelli@suse.com>
Francisco Souza <f@souza.cc>
Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
@ -61,6 +63,7 @@ Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
Karan Lyons <karan@karanlyons.com>
Keli Hu <dev@keli.hu>
Ken Cochrane <kencochrane@gmail.com>
Kevin Clark <kevin.clark@gmail.com>
Kevin J. Lynagh <kevin@keminglabs.com>
kim0 <email.ahmedkamal@googlemail.com>
Kimbro Staken <kstaken@kstaken.com>
@ -81,6 +84,7 @@ Nelson Chen <crazysim@gmail.com>
Niall O'Higgins <niallo@unworkable.org>
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
Nick Stinemates <nick@stinemates.org>
Nolan Darilek <nolan@thewordnerd.info>
odk- <github@odkurzacz.org>
Paul Bowsher <pbowsher@globalpersonals.co.uk>
Paul Hammond <paul@paulhammond.org>
@ -104,6 +108,7 @@ Thomas Hansen <thomas.hansen@gmail.com>
Tianon Gravi <admwiggin@gmail.com>
Tim Terhorst <mynamewastaken+git@gmail.com>
Tobias Bieniek <Tobias.Bieniek@gmx.de>
Tobias Schmidt <ts@soundcloud.com>
Tobias Schwab <tobias.schwab@dynport.de>
Tom Hulihan <hulihan.tom159@gmail.com>
unclejack <unclejacksons@gmail.com>

View file

@ -1,5 +1,99 @@
# Changelog
## 0.6.0 (2013-08-22)
- Runtime: Load authConfig only when needed and fix useless WARNING
+ Runtime: Add lxc-conf flag to allow custom lxc options
- Runtime: Fix race conditions in parallel pull
- Runtime: Improve CMD, ENTRYPOINT, and attach docs.
* Documentation: Small fix to docs regarding adding docker groups
* Documentation: Add MongoDB image example
+ Builder: Add USER instruction do Dockerfile
* Documentation: updated default -H docs
* Remote API: Sort Images by most recent creation date.
+ Builder: Add workdir support for the Buildfile
+ Runtime: Add an option to set the working directory
- Runtime: Show tag used when image is missing
* Documentation: Update readme with dependencies for building
* Documentation: Add instructions for creating and using the docker group
* Remote API: Reworking opaque requests in registry module
- Runtime: Fix Graph ByParent() to generate list of child images per parent image.
* Runtime: Add Image name to LogEvent tests
* Documentation: Add sudo to examples and installation to documentation
+ Hack: Bash Completion: Limit commands to containers of a relevant state
* Remote API: Add image name in /events
* Runtime: Apply volumes-from before creating volumes
- Runtime: Make docker run handle SIGINT/SIGTERM
- Runtime: Prevent crash when .dockercfg not readable
* Hack: Add docker dependencies coverage testing into docker-ci
+ Runtime: Add -privileged flag and relevant tests, docs, and examples
+ Packaging: Docker-brew 0.5.2 support and memory footprint reduction
- Runtime: Install script should be fetched over https, not http.
* Packaging: Add new docker dependencies into docker-ci
* Runtime: Use Go 1.1.2 for dockerbuilder
* Registry: Improve auth push
* Runtime: API, issue 1471: Use groups for socket permissions
* Documentation: PostgreSQL service example in documentation
* Contrib: bash completion script
* Tests: Improve TestKillDifferentUser to prevent timeout on buildbot
* Documentation: Fix typo in docs for docker run -dns
* Documentation: Adding a reference to ps -a
- Runtime: Correctly detect IPv4 forwarding
- Packaging: Revert "docker.upstart: avoid spawning a `sh` process"
* Runtime: Use ranged for loop on channels
- Runtime: Fix typo: fmt.Sprint -> fmt.Sprintf
- Tests: Fix typo in TestBindMounts (runContainer called without image)
* Runtime: add websocket support to /container/<name>/attach/ws
* Runtime: Mount /dev/shm as a tmpfs
- Builder: Only count known instructions as build steps
- Builder: Fix docker build and docker events output
- Runtime: switch from http to https for get.docker.io
* Tests: Improve TestGetContainersTop so it does not rely on sleep
+ Packaging: Docker-brew and Docker standard library
* Testing: Add some tests in server and utils
+ Packaging: Release docker with docker
- Builder: Make sure ENV instruction within build perform a commit each time
* Packaging: Fix the upstart script generated by get.docker.io
- Runtime: fix small \n error un docker build
* Runtime: Let userland proxy handle container-bound traffic
* Runtime: Updated the Docker CLI to specify a value for the "Host" header.
* Runtime: Add warning when net.ipv4.ip_forwarding = 0
* Registry: Registry unit tests + mock registry
* Runtime: fixed #910. print user name to docker info output
- Builder: Forbid certain paths within docker build ADD
- Runtime: change network range to avoid conflict with EC2 DNS
* Tests: Relax the lo interface test to allow iface index != 1
* Documentation: Suggest installing linux-headers by default.
* Documentation: Change the twitter handle
* Client: Add docker cp command and copy api endpoint to copy container files/folders to the host
* Remote API: Use mime pkg to parse Content-Type
- Runtime: Reduce connect and read timeout when pinging the registry
* Documentation: Update amazon.rst to explain that Vagrant is not necessary for running Docker on ec2
* Packaging: Enabled the docs to generate manpages.
* Runtime: Parallel pull
- Runtime: Handle ip route showing mask-less IP addresses
* Documentation: Clarify Amazon EC2 installation
* Documentation: 'Base' image is deprecated and should no longer be referenced in the docs.
* Runtime: Fix to "Inject dockerinit at /.dockerinit"
* Runtime: Allow ENTRYPOINT without CMD
- Runtime: Always consider localhost as a domain name when parsing the FQN repos name
* Remote API: 650 http utils and user agent field
* Documentation: fix a typo in the ubuntu installation guide
- Builder: Repository name (and optionally a tag) in build usage
* Documentation: Move note about officially supported kernel
* Packaging: Revert "Bind daemon to 0.0.0.0 in Vagrant.
* Builder: Add no cache for docker build
* Runtime: Add hostname to environment
* Runtime: Add last stable version in `docker version`
- Builder: Make sure ADD will create everything in 0755
* Documentation: Add ufw doc
* Tests: Add registry functional test to docker-ci
- Documentation: Solved the logo being squished in Safari
- Runtime: Use utils.ParseRepositoryTag instead of strings.Split(name, ":") in server.ImageDelete
* Runtime: Refactor checksum
- Runtime: Improve connect message with socket error
* Documentation: Added information about Docker's high level tools over LXC.
* Don't read from stdout when only attached to stdin
## 0.5.3 (2013-08-13)
* Runtime: Use docker group for socket permissions
- Runtime: Spawn shell within upstart script

View file

@ -3,28 +3,37 @@ docker-version 0.4.2
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
# Build dependencies
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
# Install Go
run curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | tar -v -C /usr/local -xz
run curl -s https://go.googlecode.com/files/go1.1.2.linux-amd64.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
env CGO_ENABLED 0
run cd /tmp && echo 'package main' > t.go && go test -a -i -v
# Ubuntu stuff
run apt-get install -y -q ruby1.9.3 rubygems
run gem install fpm
run apt-get install -y -q reprepro dpkg-sig
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
# Download dependencies
run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
# Run dependencies
run apt-get install -y iptables
# lxc requires updating ubuntu sources
run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list
run apt-get update
run apt-get install -y lxc
run apt-get install -y aufs-tools
run PKG=github.com/dotcloud/tar/ REV=d06045a6d9; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV
# Docker requires code.google.com/p/go.net/websocket
run apt-get install -y -q mercurial
run PKG=code.google.com/p/go.net/ REV=84a4013f96e0; hg clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && hg checkout $REV
# Upload docker source
add . /go/src/github.com/dotcloud/docker
run ln -s /go/src/github.com/dotcloud/docker /src
# Build the binary
run cd /go/src/github.com/dotcloud/docker/docker && go install -ldflags "-X main.GITCOMMIT '??' -d -w"
env PATH /usr/local/go/bin:/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
cmd ["docker"]
run cd /go/src/github.com/dotcloud/docker && hack/release/make.sh
cmd cd /go/src/github.com/dotcloud/docker && hack/release/release.sh

View file

@ -1,5 +1,6 @@
Solomon Hykes <solomon@dotcloud.com>
Guillaume Charmes <guillaume@dotcloud.com>
Victor Vieux <victor@dotcloud.com>
api.go: Victor Vieux <victor@dotcloud.com>
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
Solomon Hykes <solomon@dotcloud.com> (@shykes)
Guillaume Charmes <guillaume@dotcloud.com> (@creack)
Victor Vieux <victor@dotcloud.com> (@vieux)
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
api.go: Victor Vieux <victor@dotcloud.com> (@vieux)
Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com> (@mzdaniel)

View file

@ -1,95 +0,0 @@
DOCKER_PACKAGE := github.com/dotcloud/docker
RELEASE_VERSION := $(shell git tag | grep -E "v[0-9\.]+$$" | sort -nr | head -n 1)
SRCRELEASE := docker-$(RELEASE_VERSION)
BINRELEASE := docker-$(RELEASE_VERSION).tgz
BUILD_SRC := build_src
BUILD_PATH := ${BUILD_SRC}/src/${DOCKER_PACKAGE}
GIT_ROOT := $(shell git rev-parse --show-toplevel)
BUILD_DIR := $(CURDIR)/.gopath
GOPATH ?= $(BUILD_DIR)
export GOPATH
GO_OPTIONS ?= -a -ldflags='-w -d'
ifeq ($(VERBOSE), 1)
GO_OPTIONS += -v
endif
GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
SRC_DIR := $(GOPATH)/src
DOCKER_DIR := $(SRC_DIR)/$(DOCKER_PACKAGE)
DOCKER_MAIN := $(DOCKER_DIR)/docker
DOCKER_BIN_RELATIVE := bin/docker
DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
.PHONY: all clean test hack release srcrelease $(BINRELEASE) $(SRCRELEASE) $(DOCKER_BIN) $(DOCKER_DIR)
all: $(DOCKER_BIN)
$(DOCKER_BIN): $(DOCKER_DIR)
@mkdir -p $(dir $@)
@(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
@echo $(DOCKER_BIN_RELATIVE) is created.
$(DOCKER_DIR):
@mkdir -p $(dir $@)
@if [ -h $@ ]; then rm -f $@; fi; ln -sf $(CURDIR)/ $@
@(cd $(DOCKER_MAIN); go get -d $(GO_OPTIONS))
whichrelease:
echo $(RELEASE_VERSION)
release: $(BINRELEASE)
s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
s3cmd -P put $(SRCRELEASE)/bin/docker s3://get.docker.io/builds/`uname -s`/`uname -m`/docker
srcrelease: $(SRCRELEASE)
deps: $(DOCKER_DIR)
# A clean checkout of $RELEASE_VERSION, with vendored dependencies
$(SRCRELEASE):
rm -fr $(SRCRELEASE)
git clone $(GIT_ROOT) $(SRCRELEASE)
cd $(SRCRELEASE); git checkout -q $(RELEASE_VERSION)
# A binary release ready to be uploaded to a mirror
$(BINRELEASE): $(SRCRELEASE)
rm -f $(BINRELEASE)
cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
clean:
@rm -rf $(dir $(DOCKER_BIN))
ifeq ($(GOPATH), $(BUILD_DIR))
@rm -rf $(BUILD_DIR)
else ifneq ($(DOCKER_DIR), $(realpath $(DOCKER_DIR)))
@rm -f $(DOCKER_DIR)
endif
test:
# Copy docker source and dependencies for testing
rm -rf ${BUILD_SRC}; mkdir -p ${BUILD_PATH}
tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
GOPATH=${CURDIR}/${BUILD_SRC} go get -d
# Do the test
sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS}
testall: all
@(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS))
fmt:
@gofmt -s -l -w .
hack:
cd $(CURDIR)/hack && vagrant up
ssh-dev:
cd $(CURDIR)/hack && vagrant ssh

View file

@ -147,7 +147,7 @@ Quick install on Ubuntu 12.04 and 12.10
---------------------------------------
```bash
curl get.docker.io | sudo sh -x
curl https://get.docker.io | sudo sh -x
```
Binary installs
@ -166,8 +166,12 @@ supported.
Installing from source
----------------------
1. Make sure you have a [Go language](http://golang.org/doc/install)
compiler >= 1.1 and [git](http://git-scm.com) installed.
1. Install Dependencies
* [Go language 1.1.x](http://golang.org/doc/install)
* [git](http://git-scm.com)
* [lxc](http://lxc.sourceforge.net)
* [aufs-tools](http://aufs.sourceforge.net)
2. Checkout the source code
```bash

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.6.0

18
Vagrantfile vendored
View file

@ -20,8 +20,6 @@ Vagrant::Config.run do |config|
pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \
"add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \
"apt-get install -q -y lxc-docker; "
# Listen on all interfaces so that the daemon is accessible from the host
pkg_cmd << "sed -i -E 's| /usr/bin/docker -d| /usr/bin/docker -d -H 0.0.0.0|' /etc/init/docker.conf;"
# Add X.org Ubuntu backported 3.8 kernel
pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \
"apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
@ -84,15 +82,15 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
end
if !FORWARD_DOCKER_PORTS.nil?
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
(49000..49900).each do |port|
config.vm.forward_port port, port
end
Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config|
(49000..49900).each do |port|
config.vm.forward_port port, port
end
end
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
(49000..49900).each do |port|
config.vm.network :forwarded_port, :host => port, :guest => port
end
Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
(49000..49900).each do |port|
config.vm.network :forwarded_port, :host => port, :guest => port
end
end
end

233
api.go
View file

@ -1,6 +1,7 @@
package docker
import (
"code.google.com/p/go.net/websocket"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/auth"
@ -9,6 +10,7 @@ import (
"io"
"io/ioutil"
"log"
"mime"
"net"
"net/http"
"os"
@ -23,6 +25,8 @@ const DEFAULTHTTPHOST = "127.0.0.1"
const DEFAULTHTTPPORT = 4243
const DEFAULTUNIXSOCKET = "/var/run/docker.sock"
type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
@ -83,13 +87,21 @@ func getBoolParam(value string) (bool, error) {
return ret, nil
}
func matchesContentType(contentType, expectedType string) bool {
mimetype, _, err := mime.ParseMediaType(contentType)
if err != nil {
utils.Debugf("Error parsing media type: %s error: %s", contentType, err.Error())
}
return err == nil && mimetype == expectedType
}
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
authConfig := &auth.AuthConfig{}
err := json.NewDecoder(r.Body).Decode(authConfig)
if err != nil {
return err
}
status, err := auth.Login(authConfig)
status, err := auth.Login(authConfig, srv.HTTPRequestFactory())
if err != nil {
return err
}
@ -225,8 +237,7 @@ func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
}
}
}
for {
event := <-listener
for event := range listener {
err := sendEvent(wf, &event)
if err != nil && err.Error() == "JSON error" {
continue
@ -388,7 +399,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
}
sf := utils.NewStreamFormatter(version > 1.0)
if image != "" { //pull
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil {
if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}, version > 1.3); err != nil {
if sf.Used() {
w.Write(sf.FormatError(err))
return nil
@ -490,7 +501,12 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
return err
}
if len(config.Dns) == 0 && len(srv.runtime.Dns) == 0 && utils.CheckLocalDns() {
resolvConf, err := utils.GetResolvConf()
if err != nil {
return err
}
if len(config.Dns) == 0 && len(srv.runtime.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
}
@ -510,6 +526,11 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r
out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.")
}
if srv.runtime.capabilities.IPv4ForwardingDisabled {
log.Println("Warning: IPv4 forwarding is disabled.")
out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.")
}
b, err := json.Marshal(out)
if err != nil {
return err
@ -591,7 +612,7 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r
// allow a nil body for backwards compatibility
if r.Body != nil {
if r.Header.Get("Content-Type") == "application/json" {
if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
return err
}
@ -729,6 +750,53 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r
return nil
}
func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
}
logs, err := getBoolParam(r.Form.Get("logs"))
if err != nil {
return err
}
stream, err := getBoolParam(r.Form.Get("stream"))
if err != nil {
return err
}
stdin, err := getBoolParam(r.Form.Get("stdin"))
if err != nil {
return err
}
stdout, err := getBoolParam(r.Form.Get("stdout"))
if err != nil {
return err
}
stderr, err := getBoolParam(r.Form.Get("stderr"))
if err != nil {
return err
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if _, err := srv.ContainerInspect(name); err != nil {
return err
}
h := websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws); err != nil {
utils.Debugf("Error: %s", err)
}
})
h.ServeHTTP(w, r)
return nil
}
func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
@ -795,12 +863,8 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
remoteURL := r.FormValue("remote")
repoName := r.FormValue("t")
rawSuppressOutput := r.FormValue("q")
tag := ""
if strings.Contains(repoName, ":") {
remoteParts := strings.Split(repoName, ":")
tag = remoteParts[1]
repoName = remoteParts[0]
}
rawNoCache := r.FormValue("nocache")
repoName, tag := utils.ParseRepositoryTag(repoName)
var context io.Reader
@ -846,8 +910,12 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
if err != nil {
return err
}
noCache, err := getBoolParam(rawNoCache)
if err != nil {
return err
}
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput)
b := NewBuildFile(srv, utils.NewWriteFlusher(w), !suppressOutput, !noCache)
id, err := b.Build(context)
if err != nil {
fmt.Fprintf(w, "Error build: %s\n", err)
@ -859,6 +927,36 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
return nil
}
func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
copyData := &APICopy{}
contentType := r.Header.Get("Content-Type")
if contentType == "application/json" {
if err := json.NewDecoder(r.Body).Decode(copyData); err != nil {
return err
}
} else {
return fmt.Errorf("Content-Type not supported: %s", contentType)
}
if copyData.Resource == "" {
return fmt.Errorf("Resource cannot be empty")
}
if copyData.Resource[0] == '/' {
copyData.Resource = copyData.Resource[1:]
}
if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil {
utils.Debugf("%s", err.Error())
return err
}
return nil
}
func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
w.WriteHeader(http.StatusOK)
return nil
@ -869,25 +967,61 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
}
func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// log the request
utils.Debugf("Calling %s %s", localMethod, localRoute)
if logging {
log.Println(r.Method, r.RequestURI)
}
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
if len(userAgent) == 2 && userAgent[1] != VERSION {
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
}
}
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
if err != nil {
version = APIVERSION
}
if srv.enableCors {
writeCorsHeaders(w, r)
}
if version == 0 || version > APIVERSION {
w.WriteHeader(http.StatusNotFound)
return
}
if err := handlerFunc(srv, version, w, r, mux.Vars(r)); err != nil {
utils.Debugf("Error: %s", err)
httpError(w, err)
}
}
}
func createRouter(srv *Server, logging bool) (*mux.Router, error) {
r := mux.NewRouter()
m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
m := map[string]map[string]HttpApiFunc{
"GET": {
"/events": getEvents,
"/info": getInfo,
"/version": getVersion,
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJSON,
"/containers/json": getContainersJSON,
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
"/events": getEvents,
"/info": getInfo,
"/version": getVersion,
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJSON,
"/containers/json": getContainersJSON,
"/containers/{name:.*}/export": getContainersExport,
"/containers/{name:.*}/changes": getContainersChanges,
"/containers/{name:.*}/json": getContainersByName,
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
},
"POST": {
"/auth": postAuth,
@ -906,6 +1040,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/containers/{name:.*}/wait": postContainersWait,
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,
@ -921,37 +1056,13 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
utils.Debugf("Registering %s, %s", method, route)
// NOTE: scope issue, make sure the variables are local and won't be changed
localRoute := route
localMethod := method
localFct := fct
f := func(w http.ResponseWriter, r *http.Request) {
utils.Debugf("Calling %s %s from %s", localMethod, localRoute, r.RemoteAddr)
localMethod := method
if logging {
log.Println(r.Method, r.RequestURI)
}
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
if len(userAgent) == 2 && userAgent[1] != VERSION {
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
}
}
version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
if err != nil {
version = APIVERSION
}
if srv.enableCors {
writeCorsHeaders(w, r)
}
if version == 0 || version > APIVERSION {
w.WriteHeader(http.StatusNotFound)
return
}
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
httpError(w, err)
}
}
// build the handler function
f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct)
// add the new route
if localRoute == "" {
r.Methods(localMethod).HandlerFunc(f)
} else {
@ -960,6 +1071,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
}
}
}
return r, nil
}
@ -975,7 +1087,10 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
return e
}
if proto == "unix" {
os.Chmod(addr, 0660)
if err := os.Chmod(addr, 0660); err != nil {
return err
}
groups, err := ioutil.ReadFile("/etc/group")
if err != nil {
return err
@ -987,7 +1102,9 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
return err
}
utils.Debugf("docker group found. gid: %d", gid)
os.Chown(addr, 0, gid)
if err := os.Chown(addr, 0, gid); err != nil {
return err
}
}
}
httpSrv := http.Server{Addr: addr, Handler: r}

View file

@ -17,16 +17,18 @@ type APIImages struct {
}
type APIInfo struct {
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
Debug bool
Containers int
Images int
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
}
type APITop struct {
@ -86,3 +88,8 @@ type APIImageConfig struct {
ID string `json:"Id"`
*Config
}
type APICopy struct {
Resource string
HostPath string
}

View file

@ -97,8 +97,8 @@ func TestGetEvents(t *testing.T) {
listeners: make(map[string]chan utils.JSONMessage),
}
srv.LogEvent("fakeaction", "fakeid")
srv.LogEvent("fakeaction2", "fakeid")
srv.LogEvent("fakeaction", "fakeid", "fakeimage")
srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
req, err := http.NewRequest("GET", "/events?since=1", nil)
if err != nil {
@ -461,26 +461,48 @@ func TestGetContainersTop(t *testing.T) {
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "sleep 2"},
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/sh", "-c", "cat"},
OpenStdin: true,
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
defer func() {
// Make sure the process dies before destroying runtime
container.stdin.Close()
container.WaitTimeout(2 * time.Second)
}()
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
// Give some time to the process to start
container.WaitTimeout(500 * time.Millisecond)
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
for {
if container.State.Running {
break
}
time.Sleep(10 * time.Millisecond)
}
})
if !container.State.Running {
t.Errorf("Container should be running")
t.Fatalf("Container should be running")
}
// Make sure sh spawn up cat
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
in, _ := container.StdinPipe()
out, _ := container.StdoutPipe()
if err := assertPipe("hello\n", "hello", out, in, 15); err != nil {
t.Fatal(err)
}
})
r := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/"+container.ID+"/top?ps_args=u", bytes.NewReader([]byte{}))
if err != nil {
@ -504,11 +526,11 @@ func TestGetContainersTop(t *testing.T) {
if len(procs.Processes) != 2 {
t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes))
}
if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "sleep" {
t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[0][10])
if procs.Processes[0][10] != "/bin/sh" && procs.Processes[0][10] != "cat" {
t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[0][10])
}
if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "sleep" {
t.Fatalf("Expected `sleep` or `/bin/sh`, found %s.", procs.Processes[1][10])
if procs.Processes[1][10] != "/bin/sh" && procs.Processes[1][10] != "cat" {
t.Fatalf("Expected `cat` or `/bin/sh`, found %s.", procs.Processes[1][10])
}
}
@ -541,7 +563,7 @@ func TestGetContainersByName(t *testing.T) {
t.Fatal(err)
}
if outContainer.ID != container.ID {
t.Fatalf("Wrong containers retrieved. Expected %s, recieved %s", container.ID, outContainer.ID)
t.Fatalf("Wrong containers retrieved. Expected %s, received %s", container.ID, outContainer.ID)
}
}
@ -780,7 +802,7 @@ func TestPostContainersStart(t *testing.T) {
r = httptest.NewRecorder()
if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
t.Fatalf("A running containter should be able to be started")
t.Fatalf("A running container should be able to be started")
}
if err := container.Kill(); err != nil {
@ -904,7 +926,7 @@ func TestPostContainersAttach(t *testing.T) {
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
// Try to avoid the timeoout in destroy. Best effort, don't check error
// Try to avoid the timeout in destroy. Best effort, don't check error
defer func() {
closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
container.Kill()
@ -960,7 +982,7 @@ func TestPostContainersAttach(t *testing.T) {
t.Fatalf("/bin/cat is not running after closing stdin")
}
// Try to avoid the timeoout in destroy. Best effort, don't check error
// Try to avoid the timeout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe()
cStdin.Close()
container.Wait()
@ -1142,6 +1164,84 @@ func TestDeleteImages(t *testing.T) {
} */
}
func TestJsonContentType(t *testing.T) {
if !matchesContentType("application/json", "application/json") {
t.Fail()
}
if !matchesContentType("application/json; charset=utf-8", "application/json") {
t.Fail()
}
if matchesContentType("dockerapplication/json", "application/json") {
t.Fail()
}
}
func TestPostContainersCopy(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
srv := &Server{runtime: runtime}
builder := NewBuilder(runtime)
// Create a container and remove a file
container, err := builder.Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/test.txt"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
if err := container.Run(); err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
copyData := APICopy{HostPath: ".", Resource: "/test.txt"}
jsonData, err := json.Marshal(copyData)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/copy", bytes.NewReader(jsonData))
if err != nil {
t.Fatal(err)
}
req.Header.Add("Content-Type", "application/json")
if err = postContainersCopy(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusOK {
t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
}
found := false
for tarReader := tar.NewReader(r.Body); ; {
h, err := tarReader.Next()
if err != nil {
if err == io.EOF {
break
}
t.Fatal(err)
}
if h.Name == "test.txt" {
found = true
break
}
}
if !found {
t.Fatalf("The created test file has not been found in the copied output")
}
}
// Mocked types for tests
type NopConn struct {
io.ReadCloser

View file

@ -98,7 +98,7 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
// and unpacks it into the directory at `path`.
// The archive may be compressed with one of the following algorithgms:
// The archive may be compressed with one of the following algorithms:
// identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string) error {
@ -173,7 +173,7 @@ func CopyWithTar(src, dst string) error {
}
// Create dst, copy src's content into it
utils.Debugf("Creating dest directory: %s", dst)
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
return err
}
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"io/ioutil"
"net/http"
"os"
@ -18,7 +19,7 @@ const CONFIGFILE = ".dockercfg"
// Only used for user auth + account creation
const INDEXSERVER = "https://index.docker.io/v1/"
//const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/"
var (
ErrConfigFileMissing = errors.New("The Auth config file is missing")
@ -75,23 +76,23 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil {
return &configFile, ErrConfigFileMissing
return &configFile, nil //missing file is not an error
}
b, err := ioutil.ReadFile(confFile)
if err != nil {
return nil, err
return &configFile, err
}
if err := json.Unmarshal(b, &configFile.Configs); err != nil {
arr := strings.Split(string(b), "\n")
if len(arr) < 2 {
return nil, fmt.Errorf("The Auth config file is empty")
return &configFile, fmt.Errorf("The Auth config file is empty")
}
authConfig := AuthConfig{}
origAuth := strings.Split(arr[0], " = ")
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
if err != nil {
return nil, err
return &configFile, err
}
origEmail := strings.Split(arr[1], " = ")
authConfig.Email = origEmail[1]
@ -100,7 +101,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
for k, authConfig := range configFile.Configs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
if err != nil {
return nil, err
return &configFile, err
}
authConfig.Auth = ""
configFile.Configs[k] = authConfig
@ -140,7 +141,7 @@ func SaveConfig(configFile *ConfigFile) error {
}
// try to register/login to the registry server
func Login(authConfig *AuthConfig) (string, error) {
func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) {
client := &http.Client{}
reqStatusCode := 0
var status string
@ -171,7 +172,7 @@ func Login(authConfig *AuthConfig) (string, error) {
"Please check your e-mail for a confirmation link.")
} else if reqStatusCode == 400 {
if string(reqBody) == "\"Username or email already exists\"" {
req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil)
req, err := factory.NewRequest("GET", IndexServerAddress()+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := client.Do(req)
if err != nil {

View file

@ -33,7 +33,7 @@ func TestLogin(t *testing.T) {
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
defer os.Setenv("DOCKER_INDEX_URL", "")
authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
status, err := Login(authConfig)
status, err := Login(authConfig, nil)
if err != nil {
t.Fatal(err)
}
@ -53,7 +53,7 @@ func TestCreateAccount(t *testing.T) {
token := hex.EncodeToString(tokenBuffer)[:12]
username := "ut" + token
authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
status, err := Login(authConfig)
status, err := Login(authConfig, nil)
if err != nil {
t.Fatal(err)
}
@ -63,7 +63,7 @@ func TestCreateAccount(t *testing.T) {
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
}
status, err = Login(authConfig)
status, err = Login(authConfig, nil)
if err == nil {
t.Fatalf("Expected error but found nil instead")
}

View file

@ -38,7 +38,9 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
MergeConfig(config, img.Config)
}
if config.Cmd == nil || len(config.Cmd) == 0 {
if len(config.Entrypoint) != 0 && config.Cmd == nil {
config.Cmd = []string{}
} else if config.Cmd == nil || len(config.Cmd) == 0 {
return nil, fmt.Errorf("No command specified")
}
@ -80,7 +82,12 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
return nil, err
}
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns() {
resolvConf, err := utils.GetResolvConf()
if err != nil {
return nil, err
}
if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) {
//"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns
builder.runtime.Dns = defaultDns
}

View file

@ -26,11 +26,12 @@ type buildFile struct {
builder *Builder
srv *Server
image string
maintainer string
config *Config
context string
verbose bool
image string
maintainer string
config *Config
context string
verbose bool
utilizeCache bool
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
@ -55,7 +56,7 @@ func (b *buildFile) CmdFrom(name string) error {
if err != nil {
if b.runtime.graph.IsNotExist(err) {
remote, tag := utils.ParseRepositoryTag(name)
if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil {
if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil, true); err != nil {
return err
}
image, err = b.runtime.repositories.LookupImage(name)
@ -92,17 +93,21 @@ func (b *buildFile) CmdRun(args string) error {
b.config.Cmd = nil
MergeConfig(b.config, config)
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
utils.Debugf("Command to be executed: %v", b.config.Cmd)
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
if b.utilizeCache {
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
}
cid, err := b.run()
@ -112,7 +117,7 @@ func (b *buildFile) CmdRun(args string) error {
if err := b.commit(cid, cmd, "run"); err != nil {
return err
}
b.config.Cmd = cmd
return nil
}
@ -192,6 +197,11 @@ func (b *buildFile) CmdExpose(args string) error {
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
}
func (b *buildFile) CmdUser(args string) error {
b.config.User = args
return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
}
func (b *buildFile) CmdInsert(args string) error {
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}
@ -217,6 +227,11 @@ func (b *buildFile) CmdEntrypoint(args string) error {
return nil
}
func (b *buildFile) CmdWorkdir(workdir string) error {
b.config.WorkingDir = workdir
return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
}
func (b *buildFile) CmdVolume(args string) error {
if args == "" {
return fmt.Errorf("Volume cannot be empty")
@ -400,16 +415,19 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
if b.utilizeCache {
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil {
fmt.Fprintf(b.out, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
}
container, err := b.builder.Create(b.config)
if err != nil {
return err
@ -480,15 +498,16 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ")
stepN += 1
// FIXME: only count known instructions as build steps
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists {
fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
continue
}
stepN += 1
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
@ -500,10 +519,10 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")
return "", fmt.Errorf("An error occurred during the build\n")
}
func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache bool) BuildFile {
return &buildFile{
builder: NewBuilder(srv.runtime),
runtime: srv.runtime,
@ -513,5 +532,6 @@ func NewBuildFile(srv *Server, out io.Writer, verbose bool) BuildFile {
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
verbose: verbose,
utilizeCache: utilizeCache,
}
}

View file

@ -195,21 +195,23 @@ func mkTestingFileServer(files [][2]string) (*httptest.Server, error) {
func TestBuild(t *testing.T) {
for _, ctx := range testContexts {
buildImage(ctx, t)
buildImage(ctx, t, nil, true)
}
}
func buildImage(context testContextTemplate, t *testing.T) *Image {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image {
if srv == nil {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
srv = &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
}
httpServer, err := mkTestingFileServer(context.remoteFiles)
@ -224,10 +226,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image {
}
port := httpServer.URL[idx+1:]
ip := runtime.networkManager.bridgeNetwork.IP
ip := srv.runtime.networkManager.bridgeNetwork.IP
dockerfile := constructDockerfile(context.dockerfile, ip, port)
buildfile := NewBuildFile(srv, ioutil.Discard, false)
buildfile := NewBuildFile(srv, ioutil.Discard, false, useCache)
id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t))
if err != nil {
t.Fatal(err)
@ -245,7 +247,7 @@ func TestVolume(t *testing.T) {
from {IMAGE}
volume /test
cmd Hello world
`, nil, nil}, t)
`, nil, nil}, t, nil, true)
if len(img.Config.Volumes) == 0 {
t.Fail()
@ -261,19 +263,30 @@ func TestBuildMaintainer(t *testing.T) {
img := buildImage(testContextTemplate{`
from {IMAGE}
maintainer dockerio
`, nil, nil}, t)
`, nil, nil}, t, nil, true)
if img.Author != "dockerio" {
t.Fail()
}
}
func TestBuildUser(t *testing.T) {
img := buildImage(testContextTemplate{`
from {IMAGE}
user dockerio
`, nil, nil}, t, nil, true)
if img.Config.User != "dockerio" {
t.Fail()
}
}
func TestBuildEnv(t *testing.T) {
img := buildImage(testContextTemplate{`
from {IMAGE}
env port 4243
`,
nil, nil}, t)
nil, nil}, t, nil, true)
hasEnv := false
for _, envVar := range img.Config.Env {
if envVar == "port=4243" {
@ -291,7 +304,7 @@ func TestBuildCmd(t *testing.T) {
from {IMAGE}
cmd ["/bin/echo", "Hello World"]
`,
nil, nil}, t)
nil, nil}, t, nil, true)
if img.Config.Cmd[0] != "/bin/echo" {
t.Log(img.Config.Cmd[0])
@ -308,7 +321,7 @@ func TestBuildExpose(t *testing.T) {
from {IMAGE}
expose 4243
`,
nil, nil}, t)
nil, nil}, t, nil, true)
if img.Config.PortSpecs[0] != "4243" {
t.Fail()
@ -320,12 +333,108 @@ func TestBuildEntrypoint(t *testing.T) {
from {IMAGE}
entrypoint ["/bin/echo"]
`,
nil, nil}, t)
nil, nil}, t, nil, true)
if img.Config.Entrypoint[0] != "/bin/echo" {
}
}
// testing #1405 - config.Cmd does not get cleaned up if
// utilizing cache
func TestBuildEntrypointRunCleanup(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
img := buildImage(testContextTemplate{`
from {IMAGE}
run echo "hello"
`,
nil, nil}, t, srv, true)
img = buildImage(testContextTemplate{`
from {IMAGE}
run echo "hello"
add foo /foo
entrypoint ["/bin/echo"]
`,
[][2]string{{"foo", "HEYO"}}, nil}, t, srv, true)
if len(img.Config.Cmd) != 0 {
t.Fail()
}
}
func TestBuildImageWithCache(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
template := testContextTemplate{`
from {IMAGE}
maintainer dockerio
`,
nil, nil}
img := buildImage(template, t, srv, true)
imageId := img.ID
img = nil
img = buildImage(template, t, srv, true)
if imageId != img.ID {
t.Logf("Image ids should match: %s != %s", imageId, img.ID)
t.Fail()
}
}
func TestBuildImageWithoutCache(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
}
template := testContextTemplate{`
from {IMAGE}
maintainer dockerio
`,
nil, nil}
img := buildImage(template, t, srv, true)
imageId := img.ID
img = nil
img = buildImage(template, t, srv, false)
if imageId == img.ID {
t.Logf("Image ids should not match: %s == %s", imageId, img.ID)
t.Fail()
}
}
func TestForbiddenContextPath(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
@ -361,7 +470,7 @@ func TestForbiddenContextPath(t *testing.T) {
ip := srv.runtime.networkManager.bridgeNetwork.IP
dockerfile := constructDockerfile(context.dockerfile, ip, port)
buildfile := NewBuildFile(srv, ioutil.Discard, false)
buildfile := NewBuildFile(srv, ioutil.Discard, false, true)
_, err = buildfile.Build(mkTestContext(dockerfile, context.files, t))
if err == nil {

View file

@ -27,10 +27,9 @@ import (
"unicode"
)
const VERSION = "0.5.3"
var (
GITCOMMIT string
VERSION string
)
func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
@ -72,11 +71,12 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil
}
}
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTUNIXSOCKET)
for _, command := range [][]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"},
{"commit", "Create a new image from a container's changes"},
{"cp", "Copy files/folders from the containers filesystem to the host path"},
{"diff", "Inspect changes on a container's filesystem"},
{"events", "Get real time events from the server"},
{"export", "Stream the contents of a container as a tar archive"},
@ -158,9 +158,9 @@ func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
tag := cmd.String("t", "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image")
if err := cmd.Parse(args); err != nil {
return nil
}
@ -193,10 +193,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
var body io.Reader
// Setup an upload progress bar
// FIXME: ProgressReader shouldn't be this annoyning to use
// FIXME: ProgressReader shouldn't be this annoying to use
if context != nil {
sf := utils.NewStreamFormatter(false)
body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
body = utils.ProgressReader(ioutil.NopCloser(context), 0, cli.err, sf.FormatProgress("", "Uploading context", "%v bytes%0.0s%0.0s"), sf, true)
}
// Upload the build context
v := &url.Values{}
@ -208,6 +208,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
if isRemote {
v.Set("remote", cmd.Arg(0))
}
if *noCache {
v.Set("nocache", "1")
}
req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
if err != nil {
return err
@ -299,6 +302,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return nil
}
cli.LoadConfigFile()
var oldState *term.State
if *flUsername == "" || *flPassword == "" || *flEmail == "" {
oldState, err = term.SetRawTerminal(cli.terminalFd)
@ -448,6 +453,15 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
if out.GoVersion != "" {
fmt.Fprintf(cli.out, "Go version: %s\n", out.GoVersion)
}
release := utils.GetReleaseVersion()
if release != "" {
fmt.Fprintf(cli.out, "Last stable version: %s", release)
if strings.Trim(VERSION, "-dev") != release || strings.Trim(out.Version, "-dev") != release {
fmt.Fprintf(cli.out, ", please update docker")
}
fmt.Fprintf(cli.out, "\n")
}
return nil
}
@ -483,12 +497,24 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion)
}
if len(out.IndexServerAddress) != 0 {
cli.LoadConfigFile()
u := cli.configFile.Configs[out.IndexServerAddress].Username
if len(u) > 0 {
fmt.Fprintf(cli.out, "Username: %v\n", u)
fmt.Fprintf(cli.out, "Registry: %v\n", out.IndexServerAddress)
}
}
if !out.MemoryLimit {
fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
}
if !out.SwapLimit {
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
}
if !out.IPv4Forwarding {
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
}
return nil
}
@ -814,25 +840,38 @@ func (cli *DockerCli) CmdPush(args ...string) error {
return nil
}
if err := cli.checkIfLogged("push"); err != nil {
return err
}
cli.LoadConfigFile()
// If we're not using a custom registry, we know the restrictions
// applied to repository names and can warn the user in advance.
// Custom repositories can have different rules, and we must also
// allow pushing by image ID.
if len(strings.SplitN(name, "/", 2)) == 1 {
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name)
}
buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
if err != nil {
return err
username := cli.configFile.Configs[auth.IndexServerAddress()].Username
if username == "" {
username = "<user>"
}
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
}
v := url.Values{}
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
push := func() error {
buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
if err != nil {
return err
}
return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out)
}
if err := push(); err != nil {
if err.Error() == "Authentication is required." {
fmt.Fprintln(cli.out, "\nPlease login prior to push:")
if err := cli.CmdLogin(""); err != nil {
return err
}
return push()
}
return err
}
return nil
@ -1371,6 +1410,13 @@ func (cli *DockerCli) CmdRun(args ...string) error {
body, statusCode, err := cli.call("POST", "/containers/create", config)
//if image not found try to pull it
if statusCode == 404 {
_, tag := utils.ParseRepositoryTag(config.Image)
if tag == "" {
tag = DEFAULTTAG
}
fmt.Printf("Unable to find image '%s' (tag: %s) locally\n", config.Image, tag)
v := url.Values{}
repos, tag := utils.ParseRepositoryTag(config.Image)
v.Set("fromImage", repos)
@ -1429,18 +1475,32 @@ func (cli *DockerCli) CmdRun(args ...string) error {
v := url.Values{}
v.Set("logs", "1")
v.Set("stream", "1")
var out io.Writer
if config.AttachStdin {
v.Set("stdin", "1")
}
if config.AttachStdout {
v.Set("stdout", "1")
out = cli.out
}
if config.AttachStderr {
v.Set("stderr", "1")
out = cli.out
}
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
go func() {
for sig := range signals {
fmt.Printf("\nReceived signal: %s; cleaning up\n", sig)
if err := cli.CmdStop("-t", "4", runResult.ID); err != nil {
fmt.Printf("failed to stop container: %v", err)
}
}
}()
if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, out); err != nil {
utils.Debugf("Error hijack: %s", err)
return err
}
@ -1452,15 +1512,33 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return nil
}
func (cli *DockerCli) checkIfLogged(action string) error {
// If condition AND the login failed
if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
if err := cli.CmdLogin(""); err != nil {
func (cli *DockerCli) CmdCp(args ...string) error {
cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH")
if err := cmd.Parse(args); err != nil {
return nil
}
if cmd.NArg() != 2 {
cmd.Usage()
return nil
}
var copyData APICopy
info := strings.Split(cmd.Arg(0), ":")
copyData.Resource = info[1]
copyData.HostPath = cmd.Arg(1)
data, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData)
if err != nil {
return err
}
if statusCode == 200 {
r := bytes.NewReader(data)
if err := Untar(r, copyData.HostPath); err != nil {
return err
}
if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
return fmt.Errorf("Please login prior to %s. ('docker login')", action)
}
}
return nil
}
@ -1480,6 +1558,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
return nil, -1, err
}
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
req.Host = cli.addr
if data != nil {
req.Header.Set("Content-Type", "application/json")
} else if method == "POST" {
@ -1487,6 +1566,9 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
}
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
}
return nil, -1, err
}
clientconn := httputil.NewClientConn(dial, nil)
@ -1521,11 +1603,15 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
return err
}
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
req.Host = cli.addr
if method == "POST" {
req.Header.Set("Content-Type", "plain/text")
}
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
}
return err
}
clientconn := httputil.NewClientConn(dial, nil)
@ -1550,17 +1636,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
return fmt.Errorf("Error: %s", body)
}
if resp.Header.Get("Content-Type") == "application/json" {
dec := json.NewDecoder(resp.Body)
for {
var jm utils.JSONMessage
if err := dec.Decode(&jm); err == io.EOF {
break
} else if err != nil {
return err
}
jm.Display(out)
}
if matchesContentType(resp.Header.Get("Content-Type"), "application/json") {
return utils.DisplayJSONMessagesStream(resp.Body, out)
} else {
if _, err := io.Copy(out, resp.Body); err != nil {
return err
@ -1577,9 +1654,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
}
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
req.Header.Set("Content-Type", "plain/text")
req.Host = cli.addr
dial, err := net.Dial(cli.proto, cli.addr)
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
}
return err
}
clientconn := httputil.NewClientConn(dial, nil)
@ -1591,11 +1672,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
rwc, br := clientconn.Hijack()
defer rwc.Close()
receiveStdout := utils.Go(func() error {
_, err := io.Copy(out, br)
utils.Debugf("[hijack] End of stdout")
return err
})
var receiveStdout (chan error)
if out != nil {
receiveStdout = utils.Go(func() error {
_, err := io.Copy(out, br)
utils.Debugf("[hijack] End of stdout")
return err
})
}
if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
oldState, err := term.SetRawTerminal(cli.terminalFd)
@ -1623,9 +1707,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
return nil
})
if err := <-receiveStdout; err != nil {
utils.Debugf("Error receiveStdout: %s", err)
return err
if out != nil {
if err := <-receiveStdout; err != nil {
utils.Debugf("Error receiveStdout: %s", err)
return err
}
}
if !cli.isTerminal {
@ -1674,8 +1760,7 @@ func (cli *DockerCli) monitorTtySize(id string) error {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, syscall.SIGWINCH)
go func() {
for {
<-sigchan
for _ = range sigchan {
cli.resizeTty(id)
}
}()
@ -1692,6 +1777,14 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
return flags
}
func (cli *DockerCli) LoadConfigFile() (err error) {
cli.configFile, err = auth.LoadConfig(os.Getenv("HOME"))
if err != nil {
fmt.Fprintf(cli.err, "WARNING: %s\n", err)
}
return err
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
var (
isTerminal = false
@ -1708,12 +1801,9 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
if err == nil {
err = out
}
configFile, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{
proto: proto,
addr: addr,
configFile: configFile,
in: in,
out: out,
err: err,

View file

@ -90,6 +90,69 @@ func TestRunHostname(t *testing.T) {
}
// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
func TestRunWorkdir(t *testing.T) {
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
defer close(c)
if err := cli.CmdRun("-w", "/foo/bar", unitTestImageID, "pwd"); err != nil {
t.Fatal(err)
}
}()
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 cmdOutput != "/foo/bar\n" {
t.Fatalf("'pwd' should display '%s', not '%s'", "/foo/bar\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
}
// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
func TestRunWorkdirExists(t *testing.T) {
stdout, stdoutPipe := io.Pipe()
cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
defer cleanup(globalRuntime)
c := make(chan struct{})
go func() {
defer close(c)
if err := cli.CmdRun("-w", "/proc", unitTestImageID, "pwd"); err != nil {
t.Fatal(err)
}
}()
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 cmdOutput != "/proc\n" {
t.Fatalf("'pwd' should display '%s', not '%s'", "/proc\n", cmdOutput)
}
})
setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
<-c
})
}
func TestRunExit(t *testing.T) {
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
@ -255,7 +318,7 @@ func TestRunAttachStdin(t *testing.T) {
ch := make(chan struct{})
go func() {
defer close(ch)
cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat")
cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat && sleep 5")
}()
// Send input to the command, close stdin
@ -283,12 +346,10 @@ func TestRunAttachStdin(t *testing.T) {
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
// Unblock hijack end
stdout.Read([]byte{})
<-ch
})
setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() {
setTimeout(t, "Waiting for command to exit timed out", 10*time.Second, func() {
container.Wait()
})
@ -373,7 +434,7 @@ func TestAttachDisconnect(t *testing.T) {
t.Fatalf("/bin/cat is not running after closing stdin")
}
// Try to avoid the timeoout in destroy. Best effort, don't check error
// Try to avoid the timeout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe()
cStdin.Close()
container.Wait()

View file

@ -2,6 +2,7 @@ package docker
import (
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/term"
@ -76,13 +77,16 @@ type Config struct {
Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{}
VolumesFrom string
WorkingDir string
Entrypoint []string
NetworkDisabled bool
Privileged bool
}
type HostConfig struct {
Binds []string
ContainerIDFile string
LxcConf []KeyValuePair
}
type BindMap struct {
@ -91,6 +95,15 @@ type BindMap struct {
Mode string
}
var (
ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
)
type KeyValuePair struct {
Key string
Value string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
@ -99,6 +112,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
}
flHostname := cmd.String("h", "", "Container host name")
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
flAttach := NewAttachOpts()
@ -108,6 +122,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
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")
if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
@ -131,12 +146,18 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
var flLxcOpts ListOpts
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvaidWorikingDirectory
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
@ -175,6 +196,12 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
entrypoint = []string{*flEntrypoint}
}
var lxcConf []KeyValuePair
lxcConf, err := parseLxcConfOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
}
config := &Config{
Hostname: *flHostname,
PortSpecs: flPorts,
@ -194,10 +221,13 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
Volumes: flVolumes,
VolumesFrom: *flVolumesFrom,
Entrypoint: entrypoint,
Privileged: *flPrivileged,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@ -266,7 +296,8 @@ func (container *Container) FromDisk() error {
return err
}
// Load container settings
if err := json.Unmarshal(data, container); err != nil {
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
if err := json.Unmarshal(data, container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
return err
}
return nil
@ -300,7 +331,7 @@ func (container *Container) SaveHostConfig(hostConfig *HostConfig) (err error) {
return ioutil.WriteFile(container.hostConfigPath(), data, 0666)
}
func (container *Container) generateLXCConfig() error {
func (container *Container) generateLXCConfig(hostConfig *HostConfig) error {
fo, err := os.Create(container.lxcConfigPath())
if err != nil {
return err
@ -309,6 +340,11 @@ func (container *Container) generateLXCConfig() error {
if err := LxcTemplateCompiled.Execute(fo, container); err != nil {
return err
}
if hostConfig != nil {
if err := LxcHostConfigTemplateCompiled.Execute(fo, hostConfig); err != nil {
return err
}
}
return nil
}
@ -505,7 +541,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
container.State.Lock()
defer container.State.Unlock()
if len(hostConfig.Binds) == 0 {
if len(hostConfig.Binds) == 0 && len(hostConfig.LxcConf) == 0 {
hostConfig, _ = container.ReadHostConfig()
}
@ -533,6 +569,10 @@ func (container *Container) Start(hostConfig *HostConfig) error {
container.Config.MemorySwap = -1
}
if container.runtime.capabilities.IPv4ForwardingDisabled {
log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work")
}
// Create the requested bind mounts
binds := make(map[string]BindMap)
// Define illegal container destinations
@ -569,40 +609,12 @@ func (container *Container) Start(hostConfig *HostConfig) error {
binds[path.Clean(dst)] = bindMap
}
// FIXME: evaluate volumes-from before individual volumes, so that the latter can override the former.
// Create the requested volumes volumes
if container.Volumes == nil || len(container.Volumes) == 0 {
container.Volumes = make(map[string]string)
container.VolumesRW = make(map[string]bool)
for volPath := range container.Config.Volumes {
volPath = path.Clean(volPath)
// If an external bind is defined for this volume, use that as a source
if bindMap, exists := binds[volPath]; exists {
container.Volumes[volPath] = bindMap.SrcPath
if strings.ToLower(bindMap.Mode) == "rw" {
container.VolumesRW[volPath] = true
}
// Otherwise create an directory in $ROOT/volumes/ and use that
} else {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
}
srcPath, err := c.layer()
if err != nil {
return err
}
container.Volumes[volPath] = srcPath
container.VolumesRW[volPath] = true // RW by default
}
// Create the mountpoint
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
}
}
// Apply volumes from another container if requested
if container.Config.VolumesFrom != "" {
c := container.runtime.Get(container.Config.VolumesFrom)
if c == nil {
@ -610,7 +622,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
}
for volPath, id := range c.Volumes {
if _, exists := container.Volumes[volPath]; exists {
return fmt.Errorf("The requested volume %s overlap one of the volume of the container %s", volPath, c.ID)
continue
}
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
@ -622,7 +634,39 @@ func (container *Container) Start(hostConfig *HostConfig) error {
}
}
if err := container.generateLXCConfig(); err != nil {
// Create the requested volumes if they don't exist
for volPath := range container.Config.Volumes {
volPath = path.Clean(volPath)
// Skip existing volumes
if _, exists := container.Volumes[volPath]; exists {
continue
}
// If an external bind is defined for this volume, use that as a source
if bindMap, exists := binds[volPath]; exists {
container.Volumes[volPath] = bindMap.SrcPath
if strings.ToLower(bindMap.Mode) == "rw" {
container.VolumesRW[volPath] = true
}
// Otherwise create an directory in $ROOT/volumes/ and use that
} else {
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
}
srcPath, err := c.layer()
if err != nil {
return err
}
container.Volumes[volPath] = srcPath
container.VolumesRW[volPath] = true // RW by default
}
// Create the mountpoint
if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil {
return nil
}
}
if err := container.generateLXCConfig(hostConfig); err != nil {
return err
}
@ -630,7 +674,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
"-n", container.ID,
"-f", container.lxcConfigPath(),
"--",
"/sbin/init",
"/.dockerinit",
}
// Networking
@ -654,6 +698,18 @@ func (container *Container) Start(hostConfig *HostConfig) error {
"-e", "container=lxc",
"-e", "HOSTNAME="+container.Config.Hostname,
)
if container.Config.WorkingDir != "" {
workingDir := path.Clean(container.Config.WorkingDir)
utils.Debugf("[working dir] working dir is %s", workingDir)
if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil {
return nil
}
params = append(params,
"-w", workingDir,
)
}
for _, elem := range container.Config.Env {
params = append(params, "-e", elem)
@ -808,7 +864,7 @@ func (container *Container) monitor() {
}
utils.Debugf("Process finished")
if container.runtime != nil && container.runtime.srv != nil {
container.runtime.srv.LogEvent("die", container.ShortID())
container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image))
}
exitCode := -1
if container.cmd != nil {
@ -1088,3 +1144,24 @@ func (container *Container) GetSize() (int64, int64) {
}
return sizeRw, sizeRootfs
}
func (container *Container) Copy(resource string) (Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
var filter []string
basePath := path.Join(container.RootfsPath(), resource)
stat, err := os.Stat(basePath)
if err != nil {
return nil, err
}
if !stat.IsDir() {
d, f := path.Split(basePath)
basePath = d
filter = []string{f}
} else {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
return TarFilter(basePath, Uncompressed, filter)
}

View file

@ -186,7 +186,7 @@ func TestDiff(t *testing.T) {
}
}
// Create a new containere
// Create a new container
container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t)
defer runtime.Destroy(container3)
@ -351,10 +351,10 @@ func TestStart(t *testing.T) {
t.Errorf("Container should be running")
}
if err := container.Start(hostConfig); err == nil {
t.Fatalf("A running containter should be able to be started")
t.Fatalf("A running container should be able to be started")
}
// Try to avoid the timeoout in destroy. Best effort, don't check error
// Try to avoid the timeout in destroy. Best effort, don't check error
cStdin.Close()
container.WaitTimeout(2 * time.Second)
}
@ -401,22 +401,24 @@ func TestOutput(t *testing.T) {
func TestKillDifferentUser(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"tail", "-f", "/etc/resolv.conf"},
User: "daemon",
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat"},
OpenStdin: true,
User: "daemon",
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
defer container.stdin.Close()
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if err := container.Start(&HostConfig{}); err != nil {
t.Fatal(err)
}
@ -426,8 +428,13 @@ func TestKillDifferentUser(t *testing.T) {
}
})
// Even if the state is running, lets give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond)
setTimeout(t, "read/write assertion timed out", 2*time.Second, func() {
out, _ := container.StdoutPipe()
in, _ := container.StdinPipe()
if err := assertPipe("hello\n", "hello", out, in, 15); err != nil {
t.Fatal(err)
}
})
if err := container.Kill(); err != nil {
t.Fatal(err)
@ -764,7 +771,7 @@ func TestUser(t *testing.T) {
Image: GetTestImage(runtime).ID,
Cmd: []string{"id"},
User: "unkownuser",
User: "unknownuser",
},
)
if err != nil {
@ -996,6 +1003,28 @@ func TestEntrypoint(t *testing.T) {
}
}
func TestEntrypointNoCmd(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).ID,
Entrypoint: []string{"/bin/echo", "foobar"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err := container.Output()
if err != nil {
t.Fatal(err)
}
if strings.Trim(string(output), "\r\n") != "foobar" {
t.Error(string(output))
}
}
func grepFile(t *testing.T, path string, pattern string) {
f, err := os.Open(path)
if err != nil {
@ -1041,7 +1070,7 @@ func TestLXCConfig(t *testing.T) {
t.Fatal(err)
}
defer runtime.Destroy(container)
container.generateLXCConfig()
container.generateLXCConfig(nil)
grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar")
grepFile(t, container.lxcConfigPath(),
fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem))
@ -1049,6 +1078,36 @@ func TestLXCConfig(t *testing.T) {
fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2))
}
func TestCustomLxcConfig(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true"},
Hostname: "foobar",
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
hostConfig := &HostConfig{LxcConf: []KeyValuePair{
{
Key: "lxc.utsname",
Value: "docker",
},
{
Key: "lxc.cgroup.cpuset.cpus",
Value: "0,1",
},
}}
container.generateLXCConfig(hostConfig)
grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker")
grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1")
}
func BenchmarkRunSequencial(b *testing.B) {
runtime := mkRuntime(b)
defer nuke(runtime)
@ -1152,7 +1211,7 @@ func TestBindMounts(t *testing.T) {
readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist
// test mounting to an illegal destination directory
if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "ls", "."}, nil); err == nil {
if _, err := runContainer(r, []string{"-v", fmt.Sprintf("%s:.", tmpDir), "_", "ls", "."}, nil); err == nil {
t.Fatal("Container bind mounted illegal directory")
}
}
@ -1254,6 +1313,71 @@ func TestRestartWithVolumes(t *testing.T) {
}
}
// Test for #1351
func TestVolumesFromWithVolumes(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"},
Volumes: map[string]struct{}{"/test": {}},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
for key := range container.Config.Volumes {
if key != "/test" {
t.Fail()
}
}
_, err = container.Output()
if err != nil {
t.Fatal(err)
}
expected := container.Volumes["/test"]
if expected == "" {
t.Fail()
}
container2, err := NewBuilder(runtime).Create(
&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"cat", "/test/foo"},
VolumesFrom: container.ID,
Volumes: map[string]struct{}{"/test": {}},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
output, err := container2.Output()
if err != nil {
t.Fatal(err)
}
if string(output) != "bar" {
t.Fail()
}
if container.Volumes["/test"] != container2.Volumes["/test"] {
t.Fail()
}
// Ensure it restarts successfully
_, err = container2.Output()
if err != nil {
t.Fatal(err)
}
}
func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
@ -1284,10 +1408,42 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1)
if len(interfaces) != 1 {
t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces)
t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces)
}
if interfaces[0] != "1: lo" {
t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces)
if !strings.HasSuffix(interfaces[0], ": lo") {
t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces)
}
}
func TestPrivilegedCanMknod(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
if output, _ := runContainer(runtime, []string{"-privileged", "_", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok"}, t); output != "ok\n" {
t.Fatal("Could not mknod into privileged container")
}
}
func TestPrivilegedCanMount(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
if output, _ := runContainer(runtime, []string{"-privileged", "_", "sh", "-c", "mount -t tmpfs none /tmp && echo ok"}, t); output != "ok\n" {
t.Fatal("Could not mount into privileged container")
}
}
func TestPrivilegedCannotMknod(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
if output, _ := runContainer(runtime, []string{"_", "sh", "-c", "mknod /tmp/sda b 8 0 || echo ok"}, t); output != "ok\n" {
t.Fatal("Could mknod into secure container")
}
}
func TestPrivilegedCannotMount(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
if output, _ := runContainer(runtime, []string{"_", "sh", "-c", "mount -t tmpfs none /tmp || echo ok"}, t); output != "ok\n" {
t.Fatal("Could mount into secure container")
}
}

1
contrib/brew/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pyc

78
contrib/brew/README.md Normal file
View file

@ -0,0 +1,78 @@
# docker-brew
docker-brew is a command-line tool used to build the docker standard library.
## Install instructions
1. Install python if it isn't already available on your OS of choice
1. Install the easy_install tool (`sudo apt-get install python-setuptools`
for Debian)
1. Install the python package manager, `pip` (`easy_install pip`)
1. Run the following command: `sudo pip install -r requirements.txt`
1. You should now be able to use the `docker-brew` script as such.
## Basics
./docker-brew -h
Display usage and help.
./docker-brew
Default build from the default repo/branch. Images will be created under the
`library/` namespace. Does not perform a remote push.
./docker-brew -n mycorp.com -b stable --push git://github.com/mycorp/docker
Will fetch the library definition files in the `stable` branch of the
`git://github.com/mycorp/docker` repository and create images under the
`mycorp.com` namespace (e.g. `mycorp.com/ubuntu`). Created images will then
be pushed to the official docker repository (pending: support for private
repositories)
## Library definition files
The library definition files are plain text files found in the `library/`
subfolder of the docker repository.
### File names
The name of a definition file will determine the name of the image(s) it
creates. For example, the `library/ubuntu` file will create images in the
`<namespace>/ubuntu` repository. If multiple instructions are present in
a single file, all images are expected to be created under a different tag.
### Instruction format
Each line represents a build instruction.
There are different formats that `docker-brew` is able to parse.
<git-url>
git://github.com/dotcloud/hipache
https://github.com/dotcloud/docker.git
The simplest format. `docker-brew` will fetch data from the provided git
repository from the `HEAD`of its `master` branch. Generated image will be
tagged as `latest`. Use of this format is discouraged because there is no
way to ensure stability.
<docker-tag> <git-url>
bleeding-edge git://github.com/dotcloud/docker
unstable https://github.com/dotcloud/docker-redis.git
A more advanced format. `docker-brew` will fetch data from the provided git
repository from the `HEAD`of its `master` branch. Generated image will be
tagged as `<docker-tag>`. Recommended if we always want to provide a snapshot
of the latest development. Again, no way to ensure stability.
<docker-tag> <git-url> T:<git-tag>
2.4.0 git://github.com/dotcloud/docker-redis T:2.4.0
<docker-tag> <git-url> B:<git-branch>
zfs git://github.com/dotcloud/docker B:zfs-support
<docker-tag> <git-url> C:<git-commit-id>
2.2.0 https://github.com/dotcloud/docker-redis.git C:a4bf8923ee4ec566d3ddc212
The most complete format. `docker-brew` will fetch data from the provided git
repository from the provided reference (if it's a branch, brew will fetch its
`HEAD`). Generated image will be tagged as `<docker-tag>`. Recommended whenever
possible.

View file

@ -0,0 +1 @@
from brew import build_library, DEFAULT_REPOSITORY, DEFAULT_BRANCH

185
contrib/brew/brew/brew.py Normal file
View file

@ -0,0 +1,185 @@
import os
import logging
from shutil import rmtree
import docker
import git
DEFAULT_REPOSITORY = 'git://github.com/dotcloud/docker'
DEFAULT_BRANCH = 'master'
logger = logging.getLogger(__name__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
level='INFO')
client = docker.Client()
processed = {}
processed_folders = []
def build_library(repository=None, branch=None, namespace=None, push=False,
debug=False, prefill=True, registry=None):
dst_folder = None
summary = Summary()
if repository is None:
repository = DEFAULT_REPOSITORY
if branch is None:
branch = DEFAULT_BRANCH
if debug:
logger.setLevel('DEBUG')
if not (repository.startswith('https://') or repository.startswith('git://')):
logger.info('Repository provided assumed to be a local path')
dst_folder = repository
try:
client.version()
except Exception as e:
logger.error('Could not reach the docker daemon. Please make sure it '
'is running.')
logger.warning('Also make sure you have access to the docker UNIX '
'socket (use sudo)')
return
#FIXME: set destination folder and only pull latest changes instead of
# cloning the whole repo everytime
if not dst_folder:
logger.info('Cloning docker repo from {0}, branch: {1}'.format(
repository, branch))
try:
rep, dst_folder = git.clone_branch(repository, branch)
except Exception as e:
logger.exception(e)
logger.error('Source repository could not be fetched. Check '
'that the address is correct and the branch exists.')
return
try:
dirlist = os.listdir(os.path.join(dst_folder, 'library'))
except OSError as e:
logger.error('The path provided ({0}) could not be found or didn\'t'
'contain a library/ folder.'.format(dst_folder))
return
for buildfile in dirlist:
if buildfile == 'MAINTAINERS':
continue
f = open(os.path.join(dst_folder, 'library', buildfile))
linecnt = 0
for line in f:
linecnt = linecnt + 1
logger.debug('{0} ---> {1}'.format(buildfile, line))
args = line.split()
try:
if len(args) > 3:
raise RuntimeError('Incorrect line format, '
'please refer to the docs')
url = None
ref = 'refs/heads/master'
tag = None
if len(args) == 1: # Just a URL, simple mode
url = args[0]
elif len(args) == 2 or len(args) == 3: # docker-tag url
url = args[1]
tag = args[0]
if len(args) == 3: # docker-tag url B:branch or T:tag
ref = None
if args[2].startswith('B:'):
ref = 'refs/heads/' + args[2][2:]
elif args[2].startswith('T:'):
ref = 'refs/tags/' + args[2][2:]
elif args[2].startswith('C:'):
ref = args[2][2:]
else:
raise RuntimeError('Incorrect line format, '
'please refer to the docs')
if prefill:
logger.debug('Pulling {0} from official repository (cache '
'fill)'.format(buildfile))
client.pull(buildfile)
img = build_repo(url, ref, buildfile, tag, namespace, push,
registry)
summary.add_success(buildfile, (linecnt, line), img)
processed['{0}@{1}'.format(url, ref)] = img
except Exception as e:
logger.exception(e)
summary.add_exception(buildfile, (linecnt, line), e)
f.close()
if dst_folder != repository:
rmtree(dst_folder, True)
for d in processed_folders:
rmtree(d, True)
summary.print_summary(logger)
def build_repo(repository, ref, docker_repo, docker_tag, namespace, push, registry):
docker_repo = '{0}/{1}'.format(namespace or 'library', docker_repo)
img_id = None
dst_folder = None
if '{0}@{1}'.format(repository, ref) not in processed.keys():
logger.info('Cloning {0} (ref: {1})'.format(repository, ref))
if repository not in processed:
rep, dst_folder = git.clone(repository, ref)
processed[repository] = rep
processed_folders.append(dst_folder)
else:
dst_folder = git.checkout(processed[repository], ref)
if not 'Dockerfile' in os.listdir(dst_folder):
raise RuntimeError('Dockerfile not found in cloned repository')
logger.info('Building using dockerfile...')
img_id, logs = client.build(path=dst_folder, quiet=True)
else:
img_id = processed['{0}@{1}'.format(repository, ref)]
logger.info('Committing to {0}:{1}'.format(docker_repo,
docker_tag or 'latest'))
client.tag(img_id, docker_repo, docker_tag)
if push:
logger.info('Pushing result to registry {0}'.format(
registry or "default"))
if registry is not None:
docker_repo = '{0}/{1}'.format(registry, docker_repo)
logger.info('Also tagging {0}'.format(docker_repo))
client.tag(img_id, docker_repo, docker_tag)
client.push(docker_repo)
return img_id
class Summary(object):
def __init__(self):
self._summary = {}
self._has_exc = False
def _add_data(self, image, linestr, data):
if image not in self._summary:
self._summary[image] = { linestr: data }
else:
self._summary[image][linestr] = data
def add_exception(self, image, line, exc):
lineno, linestr = line
self._add_data(image, linestr, { 'line': lineno, 'exc': str(exc) })
self._has_exc = True
def add_success(self, image, line, img_id):
lineno, linestr = line
self._add_data(image, linestr, { 'line': lineno, 'id': img_id })
def print_summary(self, logger=None):
linesep = ''.center(61, '-') + '\n'
s = 'BREW BUILD SUMMARY\n' + linesep
success = 'OVERALL SUCCESS: {}\n'.format(not self._has_exc)
details = linesep
for image, lines in self._summary.iteritems():
details = details + '{}\n{}'.format(image, linesep)
for linestr, data in lines.iteritems():
details = details + '{0:2} | {1} | {2:50}\n'.format(
data['line'],
'KO' if 'exc' in data else 'OK',
data['exc'] if 'exc' in data else data['id']
)
details = details + linesep
if logger:
logger.info(s + success + details)
else:
print s, success, details

63
contrib/brew/brew/git.py Normal file
View file

@ -0,0 +1,63 @@
import tempfile
import logging
from dulwich import index
from dulwich.client import get_transport_and_path
from dulwich.repo import Repo
logger = logging.getLogger(__name__)
def clone_branch(repo_url, branch="master", folder=None):
return clone(repo_url, 'refs/heads/' + branch, folder)
def clone_tag(repo_url, tag, folder=None):
return clone(repo_url, 'refs/tags/' + tag, folder)
def checkout(rep, ref=None):
is_commit = False
if ref is None:
ref = 'refs/heads/master'
elif not ref.startswith('refs/'):
is_commit = True
if is_commit:
rep['HEAD'] = rep.commit(ref)
else:
rep['HEAD'] = rep.refs[ref]
indexfile = rep.index_path()
tree = rep["HEAD"].tree
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
return rep.path
def clone(repo_url, ref=None, folder=None):
is_commit = False
if ref is None:
ref = 'refs/heads/master'
elif not ref.startswith('refs/'):
is_commit = True
logger.debug("clone repo_url={0}, ref={1}".format(repo_url, ref))
if folder is None:
folder = tempfile.mkdtemp()
logger.debug("folder = {0}".format(folder))
rep = Repo.init(folder)
client, relative_path = get_transport_and_path(repo_url)
logger.debug("client={0}".format(client))
remote_refs = client.fetch(relative_path, rep)
for k, v in remote_refs.iteritems():
try:
rep.refs.add_if_new(k, v)
except:
pass
if is_commit:
rep['HEAD'] = rep.commit(ref)
else:
rep['HEAD'] = remote_refs[ref]
indexfile = rep.index_path()
tree = rep["HEAD"].tree
index.build_index_from_tree(rep.path, indexfile, rep.object_store, tree)
logger.debug("done")
return rep, folder

35
contrib/brew/docker-brew Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env python
import argparse
import sys
try:
import brew
except ImportError as e:
print str(e)
print 'Please install the required dependencies first'
print 'sudo pip install -r requirements.txt'
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser('Build the docker standard library')
parser.add_argument('--push', action='store_true', default=False,
help='Push generated repositories')
parser.add_argument('--debug', default=False, action='store_true',
help='Enable debugging output')
parser.add_argument('--noprefill', default=True, action='store_false',
dest='prefill', help='Disable cache prefill')
parser.add_argument('-n', metavar='NAMESPACE', default='library',
help='Namespace used for generated repositories.'
' Default is library')
parser.add_argument('-b', metavar='BRANCH', default=brew.DEFAULT_BRANCH,
help='Branch in the repository where the library definition'
' files will be fetched. Default is ' + brew.DEFAULT_BRANCH)
parser.add_argument('repository', default=brew.DEFAULT_REPOSITORY,
nargs='?', help='git repository containing the library definition'
' files. Default is ' + brew.DEFAULT_REPOSITORY)
parser.add_argument('--reg', default=None, help='Registry address to'
' push build results to. Also sets push to true.')
args = parser.parse_args()
brew.build_library(args.repository, args.b, args.n,
args.push or args.reg is not None, args.debug, args.prefill, args.reg)

View file

@ -0,0 +1,2 @@
dulwich==0.9.0
-e git://github.com/dotcloud/docker-py.git#egg=docker-py

22
contrib/brew/setup.py Normal file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
import os
from setuptools import setup
ROOT_DIR = os.path.dirname(__file__)
SOURCE_DIR = os.path.join(ROOT_DIR)
test_requirements = []
setup(
name="docker-brew",
version='0.0.1',
description="-",
packages=['dockerbrew'],
install_requires=['dulwich', 'docker'] + test_requirements,
zip_safe=False,
classifiers=['Development Status :: 3 - Alpha',
'Environment :: Other Environment',
'Intended Audience :: Developers',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Utilities'],
)

469
contrib/docker.bash Normal file
View file

@ -0,0 +1,469 @@
#!bash
#
# bash completion file for core docker commands
#
# This script provides supports completion of:
# - commands and their options
# - container ids
# - image repos and tags
# - filepaths
#
# To enable the completions either:
# - place this file in /etc/bash_completion.d
# or
# - copy this file and add the line below to your .bashrc after
# bash completion features are loaded
# . docker.bash
#
# Note:
# Currently, the completions will not work if the docker daemon is not
# bound to the default communication port/socket
# If the docker daemon is using a unix socket for communication your user
# must have access to the socket for the completions to function correctly
have docker && {
__docker_containers_all()
{
local containers
containers="$( docker ps -a -q )"
COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) )
}
__docker_containers_running()
{
local containers
containers="$( docker ps -q )"
COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) )
}
__docker_containers_stopped()
{
local containers
containers="$( comm -13 <(docker ps -q | sort -u) <(docker ps -a -q | sort -u) )"
COMPREPLY=( $( compgen -W "$containers" -- "$cur" ) )
}
__docker_image_repos()
{
local repos
repos="$( docker images | awk 'NR>1{print $1}' )"
COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) )
}
__docker_images()
{
local images
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
COMPREPLY=( $( compgen -W "$images" -- "$cur" ) )
__ltrim_colon_completions "$cur"
}
__docker_image_repos_and_tags()
{
local repos images
repos="$( docker images | awk 'NR>1{print $1}' )"
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) )
__ltrim_colon_completions "$cur"
}
__docker_containers_and_images()
{
local containers images
containers="$( docker ps -a -q )"
images="$( docker images | awk 'NR>1{print $1":"$2}' )"
COMPREPLY=( $( compgen -W "$images $containers" -- "$cur" ) )
__ltrim_colon_completions "$cur"
}
_docker_docker()
{
case "$prev" in
-H)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-H" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "$commands help" -- "$cur" ) )
;;
esac
}
_docker_attach()
{
if [ $cpos -eq $cword ]; then
__docker_containers_running
fi
}
_docker_build()
{
case "$prev" in
-t)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-t -q" -- "$cur" ) )
;;
*)
_filedir
;;
esac
}
_docker_commit()
{
case "$prev" in
-author|-m|-run)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-author -m -run" -- "$cur" ) )
;;
*)
__docker_containers_all
;;
esac
}
_docker_diff()
{
if [ $cpos -eq $cword ]; then
__docker_containers_all
fi
}
_docker_events()
{
COMPREPLY=( $( compgen -W "-since" -- "$cur" ) )
}
_docker_export()
{
if [ $cpos -eq $cword ]; then
__docker_containers_all
fi
}
_docker_help()
{
if [ $cpos -eq $cword ]; then
COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) )
fi
}
_docker_history()
{
if [ $cpos -eq $cword ]; then
__docker_image_repos_and_tags
fi
}
_docker_images()
{
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) )
;;
*)
local counter=$cpos
while [ $counter -le $cword ]; do
case "${words[$counter]}" in
-*)
;;
*)
break
;;
esac
(( counter++ ))
done
if [ $counter -eq $cword ]; then
__docker_image_repos
fi
;;
esac
}
_docker_import()
{
return
}
_docker_info()
{
return
}
_docker_insert()
{
if [ $cpos -eq $cword ]; then
__docker_image_repos_and_tags
fi
}
_docker_inspect()
{
__docker_containers_and_images
}
_docker_kill()
{
__docker_containers_running
}
_docker_login()
{
COMPREPLY=( $( compgen -W "-e -p -u" -- "$cur" ) )
}
_docker_logs()
{
if [ $cpos -eq $cword ]; then
__docker_containers_all
fi
}
_docker_port()
{
if [ $cpos -eq $cword ]; then
__docker_containers_all
fi
}
_docker_ps()
{
COMPREPLY=( $( compgen -W "-a -beforeId -l -n -notrunc -q -s -sinceId" -- "$cur" ) )
}
_docker_pull()
{
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
}
_docker_push()
{
return
}
_docker_restart()
{
case "$prev" in
-t)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
;;
*)
__docker_containers_all
;;
esac
}
_docker_rm()
{
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-v" -- "$cur" ) )
;;
*)
__docker_containers_stopped
;;
esac
}
_docker_rmi()
{
__docker_image_repos_and_tags
}
_docker_run()
{
case "$prev" in
-cidfile)
_filedir
;;
-volumes-from)
__docker_containers_all
;;
-a|-c|-dns|-e|-entrypoint|-h|-m|-p|-u|-v)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-a -c -cidfile -d -dns -e -entrypoint -h -i -m -n -p -t -u -v -volumes-from" -- "$cur" ) )
;;
*)
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) )
;;
*)
local counter=$cpos
while [ $counter -le $cword ]; do
case "${words[$counter]}" in
-a|-c|-cidfile|-dns|-e|-entrypoint|-h|-m|-p|-u|-v|-volumes-from)
(( counter++ ))
;;
-*)
;;
*)
break
;;
esac
(( counter++ ))
done
if [ $counter -eq $cword ]; then
__docker_image_repos_and_tags
fi
;;
esac
;;
esac
}
_docker_search()
{
COMPREPLY=( $( compgen -W "-notrunc" -- "$cur" ) )
}
_docker_start()
{
__docker_containers_stopped
}
_docker_stop()
{
case "$prev" in
-t)
return
;;
*)
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "-t" -- "$cur" ) )
;;
*)
__docker_containers_running
;;
esac
}
_docker_tag()
{
COMPREPLY=( $( compgen -W "-f" -- "$cur" ) )
}
_docker_top()
{
if [ $cpos -eq $cword ]; then
__docker_containers_running
fi
}
_docker_version()
{
return
}
_docker_wait()
{
__docker_containers_all
}
_docker()
{
local cur prev words cword command="docker" counter=1 word cpos
local commands="
attach
build
commit
diff
events
export
history
images
import
info
insert
inspect
kill
login
logs
port
ps
pull
push
restart
rm
rmi
run
search
start
stop
tag
top
version
wait
"
COMPREPLY=()
_get_comp_words_by_ref -n : cur prev words cword
while [ $counter -lt $cword ]; do
word="${words[$counter]}"
case "$word" in
-H)
(( counter++ ))
;;
-*)
;;
*)
command="$word"
cpos=$counter
(( cpos++ ))
break
;;
esac
(( counter++ ))
done
local completions_func=_docker_${command}
declare -F $completions_func >/dev/null && $completions_func
return 0
}
complete -F _docker docker
}

View file

@ -3,7 +3,7 @@
# Original version by Jeff Lindsay <progrium@gmail.com>
# Revamped by Jerome Petazzoni <jerome@dotcloud.com>
#
# This script canonical location is http://get.docker.io/; to update it, run:
# This script canonical location is https://get.docker.io/; to update it, run:
# s3cmd put -m text/x-shellscript -P install.sh s3://get.docker.io/index
echo "Ensuring basic dependencies are installed..."
@ -35,17 +35,23 @@ else
fi
fi
echo "Downloading docker binary and uncompressing into /usr/local/bin..."
curl -s http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz |
tar -C /usr/local/bin --strip-components=1 -zxf- \
docker-latest/docker
echo "Downloading docker binary to /usr/local/bin..."
curl -s https://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest \
> /usr/local/bin/docker
chmod +x /usr/local/bin/docker
if [ -f /etc/init/dockerd.conf ]
then
echo "Upstart script already exists."
else
echo "Creating /etc/init/dockerd.conf..."
echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf
cat >/etc/init/dockerd.conf <<EOF
description "Docker daemon"
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
respawn
exec /usr/local/bin/docker -d
EOF
fi
echo "Starting dockerd..."

View file

@ -16,15 +16,17 @@ import (
var (
GITCOMMIT string
VERSION string
)
func main() {
if utils.SelfPath() == "/sbin/init" {
if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
// Running in init mode
docker.SysInit()
return
}
// FIXME: Switch d and D ? (to be more sshd like)
flVersion := flag.Bool("v", false, "Print version information and quit")
flDaemon := flag.Bool("d", false, "Daemon mode")
flDebug := flag.Bool("D", false, "Debug mode")
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
@ -36,8 +38,12 @@ func main() {
flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
flag.Parse()
if *flVersion {
showVersion()
return
}
if len(flHosts) > 1 {
flHosts = flHosts[1:] //trick to display a nice defaul value in the usage
flHosts = flHosts[1:] //trick to display a nice default value in the usage
}
for i, flHost := range flHosts {
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
@ -52,6 +58,7 @@ func main() {
os.Setenv("DEBUG", "1")
}
docker.GITCOMMIT = GITCOMMIT
docker.VERSION = VERSION
if *flDaemon {
if flag.NArg() != 0 {
flag.Usage()
@ -74,6 +81,10 @@ func main() {
}
}
func showVersion() {
fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
}
func createPidFile(pidfile string) error {
if pidString, err := ioutil.ReadFile(pidfile); err == nil {
pid, err := strconv.Atoi(string(pidString))

View file

@ -1,2 +1,2 @@
Andy Rothfusz <andy@dotcloud.com>
Ken Cochrane <ken@dotcloud.com>
Andy Rothfusz <andy@dotcloud.com> (@metalivedev)
Ken Cochrane <ken@dotcloud.com> (@kencochrane)

View file

@ -1,14 +1,12 @@
Docker documentation and website
================================
Docker Documentation
====================
Documentation
-------------
This is your definite place to contribute to the docker documentation. The documentation is generated from the
.rst files under sources.
The folder also contains the other files to create the http://docker.io website, but you can generally ignore
most of those.
This is your definite place to contribute to the docker documentation. After each push to master the documentation
is automatically generated and made available on [docs.docker.io](http://docs.docker.io)
Each of the .rst files under sources reflects a page on the documentation.
Installation
------------
@ -25,26 +23,22 @@ Usage
* Change the `.rst` files with your favorite editor to your liking.
* Run `make docs` to clean up old files and generate new ones.
* Your static website can now be found in the `_build` directory.
* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
* To preview what you have generated run `make server` and open http://localhost:8000/ in your favorite browser.
Working using GitHub's file editor
----------------------------------
Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
you to preview your changes right online. Just be carefull not to create many commits.
you to preview your changes right online. Just be careful not to create many commits.
Images
------
When you need to add images, try to make them as small as possible (e.g. as gif).
Notes
-----
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
So changes to those pages should be made directly in html
* For the template the css is compiled from less. When changes are needed they can be compiled using
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
Guides on using sphinx
----------------------
* To make links to certain pages create a link target like so:
@ -75,3 +69,12 @@ Guides on using sphinx
* Code examples
Start without $, so it's easy to copy and paste.
Manpages
--------
* To make the manpages, simply run 'make man'. Please note there is a bug in spinx 1.1.3 which makes this fail.
Upgrade to the latest version of sphinx.
* Then preview the manpage by running `man _build/man/docker.1`, where _build/man/docker.1 is the path to the generated
manfile
* The manpages are also autogenerated by our hosted readthedocs here: http://docs-docker.dotcloud.com/projects/docker/downloads/

View file

@ -1 +1 @@
Solomon Hykes <solomon@dotcloud.com>
Solomon Hykes <solomon@dotcloud.com> (@shykes)

View file

@ -16,6 +16,7 @@ Docker Remote API
- The Remote API is replacing rcli
- By default the Docker daemon listens on unix:///var/run/docker.sock and the client must have root access to interact with the daemon
- If a group named *docker* exists on your system, docker will apply ownership of the socket to the group
- The API tends to be REST, but for some complex commands, like attach
or pull, the HTTP connection is hijacked to transport stdout stdin
and stderr
@ -26,7 +27,7 @@ Docker Remote API
2. Versions
===========
The current verson of the API is 1.4
The current version of the API is 1.4
Calling /images/<name>/insert is the same as calling
/v1.4/images/<name>/insert
@ -40,10 +41,18 @@ You can still call an old version of the api using
What's new
----------
.. http:post:: /images/create
**New!** When pull a repo, all images are now downloaded in parallel.
.. http:get:: /containers/(id)/top
**New!** You can now use ps args with docker top, like `docker top <container_id> aux`
.. http:get:: /events:
**New!** Image's name added in the events
:doc:`docker_remote_api_v1.3`
*****************************
@ -103,7 +112,7 @@ The client should send it's authConfig as POST on each call of
Only checks the configuration but doesn't store it on the server
Deleting an image is now improved, will only untag the image if it
has chidren and remove all the untagged parents if has any.
has children and remove all the untagged parents if has any.
.. http:post:: /images/<name>/delete

View file

@ -17,7 +17,7 @@ Docker Remote API v1.0
=====================
- The Remote API is replacing rcli
- Default port in the docker deamon is 4243
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
@ -832,7 +832,7 @@ Build an image from Dockerfile via stdin
{{ STREAM }}
:query t: tag to be applied to the resulting image in case of success
:query t: repository name to be applied to the resulting image in case of success
:statuscode 200: no error
:statuscode 500: server error

View file

@ -17,7 +17,7 @@ Docker Remote API v1.1
=====================
- The Remote API is replacing rcli
- Default port in the docker deamon is 4243
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
@ -305,8 +305,8 @@ Start a container
:statuscode 500: server error
Stop a contaier
***************
Stop a container
****************
.. http:post:: /containers/(id)/stop

View file

@ -17,7 +17,7 @@ Docker Remote API v1.2
=====================
- The Remote API is replacing rcli
- Default port in the docker deamon is 4243
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
@ -317,8 +317,8 @@ Start a container
:statuscode 500: server error
Stop a contaier
***************
Stop a container
****************
.. http:post:: /containers/(id)/stop
@ -870,7 +870,7 @@ Build an image from Dockerfile via stdin
{{ STREAM }}
:query t: tag to be applied to the resulting image in case of success
:query t: repository name to be applied to the resulting image in case of success
:query remote: resource to fetch, as URI
:statuscode 200: no error
:statuscode 500: server error

View file

@ -17,7 +17,7 @@ Docker Remote API v1.3
=====================
- The Remote API is replacing rcli
- Default port in the docker deamon is 4243
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
@ -365,8 +365,8 @@ Start a container
:statuscode 500: server error
Stop a contaier
***************
Stop a container
****************
.. http:post:: /containers/(id)/stop
@ -925,7 +925,7 @@ Build an image from Dockerfile via stdin
The Content-type header should be set to "application/tar".
:query t: tag to be applied to the resulting image in case of success
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
:query q: suppress verbose build output
:statuscode 200: no error
:statuscode 500: server error

View file

@ -2,6 +2,8 @@
:description: API Documentation for Docker
:keywords: API, Docker, rcli, REST, documentation
:orphan:
======================
Docker Remote API v1.4
======================
@ -12,7 +14,7 @@ Docker Remote API v1.4
=====================
- The Remote API is replacing rcli
- Default port in the docker deamon is 4243
- Default port in the docker daemon is 4243
- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
2. Endpoints
@ -127,7 +129,9 @@ Create a container
"Dns":null,
"Image":"base",
"Volumes":{},
"VolumesFrom":""
"VolumesFrom":"",
"WorkingDir":""
}
**Example response**:
@ -193,7 +197,9 @@ Inspect a container
"Dns": null,
"Image": "base",
"Volumes": {},
"VolumesFrom": ""
"VolumesFrom": "",
"WorkingDir":""
},
"State": {
"Running": false,
@ -350,7 +356,8 @@ Start a container
Content-Type: application/json
{
"Binds":["/tmp:/tmp"]
"Binds":["/tmp:/tmp"],
"LxcConf":{"lxc.utsname":"docker"}
}
**Example response**:
@ -366,8 +373,8 @@ Start a container
:statuscode 500: server error
Stop a contaier
***************
Stop a container
****************
.. http:post:: /containers/(id)/stop
@ -526,6 +533,38 @@ Remove a container
:statuscode 500: server error
Copy files or folders from a container
**************************************
.. http:post:: /containers/(id)/copy
Copy files or folders of container ``id``
**Example request**:
.. sourcecode:: http
POST /containers/4fa6e0f0c678/copy HTTP/1.1
Content-Type: application/json
{
"Resource":"test.txt"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/octet-stream
{{ STREAM }}
:statuscode 200: no error
:statuscode 404: no such container
:statuscode 500: server error
2.2 Images
----------
@ -712,7 +751,8 @@ Inspect an image
,"Dns":null,
"Image":"base",
"Volumes":null,
"VolumesFrom":""
"VolumesFrom":"",
"WorkingDir":""
},
"Size": 6824592
}
@ -765,29 +805,29 @@ Push an image on the registry
.. http:post:: /images/(name)/push
Push the image ``name`` on the registry
Push the image ``name`` on the registry
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /images/test/push HTTP/1.1
{{ authConfig }}
POST /images/test/push HTTP/1.1
{{ authConfig }}
**Example response**:
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"Pushing..."}
{"status":"Pushing", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
{"status":"Pushing..."}
{"status":"Pushing", "progress":"1/? (n/a)"}
{"error":"Invalid..."}
...
:query registry: the registry you wan to push, optional
:statuscode 200: no error
:query registry: the registry you wan to push, optional
:statuscode 200: no error
:statuscode 404: no such image
:statuscode 500: server error
@ -900,36 +940,37 @@ Build an image from Dockerfile via stdin
.. http:post:: /build
Build an image from Dockerfile via stdin
Build an image from Dockerfile via stdin
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /build HTTP/1.1
{{ STREAM }}
POST /build HTTP/1.1
**Example response**:
{{ STREAM }}
.. sourcecode:: http
**Example response**:
HTTP/1.1 200 OK
{{ STREAM }}
.. sourcecode:: http
HTTP/1.1 200 OK
{{ STREAM }}
The stream must be a tar archive compressed with one of the following algorithms:
identity (no compression), gzip, bzip2, xz. The archive must include a file called
`Dockerfile` at its root. It may include any number of other files, which will be
accessible in the build context (See the ADD build command).
The stream must be a tar archive compressed with one of the following algorithms:
identity (no compression), gzip, bzip2, xz. The archive must include a file called
`Dockerfile` at its root. It may include any number of other files, which will be
accessible in the build context (See the ADD build command).
The Content-type header should be set to "application/tar".
The Content-type header should be set to "application/tar".
:query t: tag to be applied to the resulting image in case of success
:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
:query q: suppress verbose build output
:query nocache: do not use the cache when building the image
:statuscode 200: no error
:statuscode 500: server error
:statuscode 500: server error
Check auth configuration
@ -990,7 +1031,8 @@ Display system-wide information
"NFd": 11,
"NGoroutines":21,
"MemoryLimit":true,
"SwapLimit":false
"SwapLimit":false,
"IPv4Forwarding":true
}
:statuscode 200: no error
@ -1032,22 +1074,22 @@ Create a new image from a container's changes
.. http:post:: /commit
Create a new image from a container's changes
Create a new image from a container's changes
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
**Example response**:
.. sourcecode:: http
.. sourcecode:: http
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
HTTP/1.1 201 OK
Content-Type: application/vnd.docker.raw-stream
{"Id":"596069db4bf5"}
{"Id":"596069db4bf5"}
:query container: source container
:query repo: repository
@ -1060,6 +1102,36 @@ Create a new image from a container's changes
:statuscode 500: server error
Monitor Docker's events
***********************
.. http:get:: /events
Get events from docker, either in real time via streaming, or via polling (using `since`)
**Example request**:
.. sourcecode:: http
POST /events?since=1374067924
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
{"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
{"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
{"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
:query since: timestamp used for polling
:statuscode 200: no error
:statuscode 500: server error
3. Going further
================
@ -1088,6 +1160,8 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
-----------------
To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
docker -d -H="192.168.1.9:4243" -api-enable-cors
.. code-block:: bash
docker -d -H="192.168.1.9:4243" -api-enable-cors

View file

@ -2,9 +2,10 @@
:description: Documentation for docker Registry and Registry API
:keywords: docker, registry, api, index
.. _registryindexspec:
=====================
Registry & index Spec
Registry & Index Spec
=====================
.. contents:: Table of Contents
@ -154,7 +155,7 @@ API (pulling repository foo/bar):
.. note::
**Its possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authentified and the security is not guaranteed.
**Its possible not to use the Index at all!** In this case, a deployed version of the Registry is deployed to store and serve images. Those images are not authenticated and the security is not guaranteed.
.. note::

View file

@ -13,9 +13,9 @@ Docker Usage
To list available commands, either run ``docker`` with no parameters or execute
``docker help``::
$ docker
$ sudo docker
Usage: docker [OPTIONS] COMMAND [arg...]
-H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
-H=[unix:///var/run/docker.sock]: tcp://host:port to bind/connect to or unix://path/to/socket to use
A self-sufficient runtime for linux containers.
@ -30,6 +30,7 @@ Available Commands
command/attach
command/build
command/commit
command/cp
command/diff
command/export
command/history

View file

@ -10,4 +10,50 @@
Usage: docker attach CONTAINER
Attach to a running container
Attach to a running container.
You can detach from the container again (and leave it running) with
``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of
the Docker client when it quits.
To stop a container, use ``docker stop``
To kill the container, use ``docker kill``
Examples:
---------
.. code-block:: bash
$ ID=$(sudo docker run -d ubuntu /usr/bin/top -b)
$ sudo docker attach $ID
top - 02:05:52 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.1%us, 0.2%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 373572k total, 355560k used, 18012k free, 27872k buffers
Swap: 786428k total, 0k used, 786428k free, 221740k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17200 1116 912 R 0 0.3 0:00.03 top
top - 02:05:55 up 3:05, 0 users, load average: 0.01, 0.02, 0.05
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.0%us, 0.2%sy, 0.0%ni, 99.8%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 373572k total, 355244k used, 18328k free, 27872k buffers
Swap: 786428k total, 0k used, 786428k free, 221776k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
top - 02:05:58 up 3:06, 0 users, load average: 0.01, 0.02, 0.05
Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie
Cpu(s): 0.2%us, 0.3%sy, 0.0%ni, 99.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 373572k total, 355780k used, 17792k free, 27880k buffers
Swap: 786428k total, 0k used, 786428k free, 221776k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 17208 1144 932 R 0 0.3 0:00.03 top
^C$
$ sudo docker stop $ID

View file

@ -10,8 +10,9 @@
Usage: docker build [OPTIONS] PATH | URL | -
Build a new container image from the source code at PATH
-t="": Tag to be applied to the resulting image in case of success.
-t="": Repository name (and optionally a tag) to be applied to the resulting image in case of success.
-q=false: Suppress verbose build output.
-no-cache: Do not use the cache when building the image.
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
@ -20,25 +21,44 @@ Examples
.. code-block:: bash
docker build .
sudo docker build .
| This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon.
| The contents of this directory would be used by ADD commands found within the Dockerfile.
| This will send a lot of data to the docker daemon if the current directory contains a lot of data.
| If the absolute path is provided instead of '.', only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the docker daemon.
|
This will read the ``Dockerfile`` from the current directory. It will
also send any other files and directories found in the current
directory to the ``docker`` daemon.
The contents of this directory would be used by ``ADD`` commands found
within the ``Dockerfile``. This will send a lot of data to the
``docker`` daemon if the current directory contains a lot of data. If
the absolute path is provided instead of ``.`` then only the files and
directories required by the ADD commands from the ``Dockerfile`` will be
added to the context and transferred to the ``docker`` daemon.
.. code-block:: bash
docker build - < Dockerfile
sudo docker build -t vieux/apache:2.0 .
| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
This will build like the previous example, but it will then tag the
resulting image. The repository name will be ``vieux/apache`` and the
tag will be ``2.0``
.. code-block:: bash
docker build github.com/creack/docker-firefox
sudo docker build - < Dockerfile
| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile.
| Note that you can specify an arbitrary git repository by using the 'git://' schema.
This will read a ``Dockerfile`` from *stdin* without context. Due to
the lack of a context, no contents of any local directory will be sent
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
mode because the absence of the context provides no source files to
copy to the container.
.. code-block:: bash
sudo docker build github.com/creack/docker-firefox
This will clone the Github repository and use it as context. The
``Dockerfile`` at the root of the repository is used as
``Dockerfile``. Note that you can specify an arbitrary git repository
by using the ``git://`` schema.

View file

@ -14,7 +14,8 @@
-m="": Commit message
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
-run="": Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
-run="": Config automatically applied when the image is
run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
Full -run example::

View file

@ -0,0 +1,13 @@
:title: Cp Command
:description: Copy files/folders from the containers filesystem to the host path
:keywords: cp, docker, container, documentation, copy
===========================================================
``cp`` -- Copy files/folders from the containers filesystem to the host path
===========================================================
::
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
Copy files/folders from the containers filesystem to the host path. Paths are relative to the root of the filesystem.

View file

@ -21,6 +21,6 @@ Displaying images visually
::
docker images -viz | dot -Tpng -o docker.png
sudo docker images -viz | dot -Tpng -o docker.png
.. image:: images/docker_images.gif

View file

@ -12,10 +12,11 @@
Create a new filesystem image from the contents of a tarball
At this time, the URL must start with ``http`` and point to a single file archive
(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz)
containing a root filesystem. If you would like to import from a local directory or archive,
you can use the ``-`` parameter to take the data from standard in.
At this time, the URL must start with ``http`` and point to a single
file archive (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) containing a
root filesystem. If you would like to import from a local directory or
archive, you can use the ``-`` parameter to take the data from
standard in.
Examples
--------
@ -23,19 +24,21 @@ Examples
Import from a remote location
.............................
``$ docker import http://example.com/exampleimage.tgz exampleimagerepo``
``$ sudo docker import http://example.com/exampleimage.tgz exampleimagerepo``
Import from a local file
........................
Import to docker via pipe and standard in
``$ cat exampleimage.tgz | docker import - exampleimagelocal``
``$ cat exampleimage.tgz | sudo docker import - exampleimagelocal``
Import from a local directory
.............................
``$ sudo tar -c . | docker import - exampleimagedir``
Note the ``sudo`` in this example -- you must preserve the ownership of the files (especially root ownership)
during the archiving with tar. If you are not root (or sudo) when you tar, then the ownerships might not get preserved.
Note the ``sudo`` in this example -- you must preserve the ownership
of the files (especially root ownership) during the archiving with
tar. If you are not root (or sudo) when you tar, then the ownerships
might not get preserved.

View file

@ -8,7 +8,7 @@
::
Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Usage: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
Run a command in a new container
@ -19,22 +19,66 @@
-e=[]: Set environment variables
-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)
-n=true: Enable networking for this container
-p=[]: Map a network port to the container
-t=false: Allocate a pseudo-tty
-u="": Username or UID
-d=[]: Set custom dns servers for the container
-dns=[]: Set custom dns servers for the container
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume.
-volumes-from="": Mount all volumes from the given container.
-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"
Examples
--------
.. code-block:: bash
docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test"
This will create a container and print "test" to the console. The
``cidfile`` flag makes docker attempt to create a new file and write the
container ID to it. If the file exists already, docker will return an
error. Docker will close this file when docker run exits.
.. code-block:: bash
docker run mount -t tmpfs none /var/spool/squid
This will *not* work, because by default, most potentially dangerous
kernel capabilities are dropped; including ``cap_sys_admin`` (which is
required to mount filesystems). However, the ``-privileged`` flag will
allow it to run:
.. code-block:: bash
docker run -privileged mount -t tmpfs none /var/spool/squid
The ``-privileged`` flag gives *all* capabilities to the container,
and it also lifts all the limitations enforced by the ``device``
cgroup controller. In other words, the container can then do almost
everything that the host can do. This flag exists to allow special
use-cases, like running Docker within Docker.
.. code-block:: bash
docker run -w /path/to/dir/ -i -t ubuntu pwd
The ``-w`` lets the command beeing executed inside directory given,
here /path/to/dir/. If the path does not exists it is created inside the
container.
.. code-block:: bash
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
The ``-v`` flag mounts the current working directory into the container.
The ``-w`` lets the command beeing executed inside the current
working directory, by changeing into the directory to the value
returned by ``pwd``. So this combination executes the command
using the container, but inside the current working directory.
| This will create a container and print "test" to the console. The cidfile flag makes docker attempt to create a new file and write the container ID to it. If the file exists already, docker will return an error. Docker will close this file when docker run exits.

View file

@ -10,5 +10,5 @@
Usage: docker search TERM
Searches for the TERM parameter on the Docker index and prints out a list of repositories
that match.
Searches for the TERM parameter on the Docker index and prints out
a list of repositories that match.

View file

@ -15,6 +15,7 @@ Contents:
attach <command/attach>
build <command/build>
commit <command/commit>
cp <command/cp>
diff <command/diff>
export <command/export>
history <command/history>

View file

@ -51,9 +51,7 @@ source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
#disable the parmalinks on headers, I find them really annoying
html_add_permalinks = None
html_add_permalinks = u''
# The master toctree document.
master_doc = 'toctree'
@ -203,7 +201,7 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Docker.tex', u'Docker Documentation',
('toctree', 'Docker.tex', u'Docker Documentation',
u'Team Docker', 'manual'),
]
@ -233,7 +231,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'docker', u'Docker Documentation',
('toctree', 'docker', u'Docker Documentation',
[u'Team Docker'], 1)
]
@ -247,7 +245,7 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Docker', u'Docker Documentation',
('toctree', 'Docker', u'Docker Documentation',
u'Team Docker', 'Docker', 'One line description of project.',
'Miscellaneous'),
]

View file

@ -1,5 +1,5 @@
:title: Contribution Guidelines
:description: Contribution guidelines: create issues, convetions, pull requests
:description: Contribution guidelines: create issues, conventions, pull requests
:keywords: contributing, docker, documentation, help, guideline
Contributing to Docker

View file

@ -5,18 +5,23 @@
Setting Up a Dev Environment
============================
To make it easier to contribute to Docker, we provide a standard development environment. It is important that
the same environment be used for all tests, builds and releases. The standard development environment defines
all build dependencies: system libraries and binaries, go environment, go dependencies, etc.
To make it easier to contribute to Docker, we provide a standard
development environment. It is important that the same environment be
used for all tests, builds and releases. The standard development
environment defines all build dependencies: system libraries and
binaries, go environment, go dependencies, etc.
Step 1: install docker
----------------------
Docker's build environment itself is a docker container, so the first step is to install docker on your system.
Docker's build environment itself is a Docker container, so the first
step is to install docker on your system.
You can follow the `install instructions most relevant to your system <https://docs.docker.io/en/latest/installation/>`.
Make sure you have a working, up-to-date docker installation, then continue to the next step.
You can follow the `install instructions most relevant to your system
<https://docs.docker.io/en/latest/installation/>`_. Make sure you have
a working, up-to-date docker installation, then continue to the next
step.
Step 2: check out the source
@ -35,24 +40,24 @@ When you are ready to build docker, run this command:
::
docker build -t docker .
sudo docker build -t docker .
This will build the revision currently checked out in the repository. Feel free to check out the version
of your choice.
This will build the revision currently checked out in the
repository. Feel free to check out the version of your choice.
If the build is successful, congratulations! You have produced a clean build of docker, neatly encapsulated
in a standard build environment.
If the build is successful, congratulations! You have produced a clean
build of docker, neatly encapsulated in a standard build environment.
You can run an interactive session in the newly built container:
::
docker run -i -t docker bash
sudo docker run -i -t docker bash
To extract the binaries from the container:
::
docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
sudo docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build

View file

@ -9,27 +9,29 @@ CouchDB Service
.. include:: example_header.inc
Here's an example of using data volumes to share the same data between 2 couchdb containers.
This could be used for hot upgrades, testing different versions of couchdb on the same data, etc.
Here's an example of using data volumes to share the same data between
2 CouchDB containers. This could be used for hot upgrades, testing
different versions of CouchDB on the same data, etc.
Create first database
---------------------
Note that we're marking /var/lib/couchdb as a data volume.
Note that we're marking ``/var/lib/couchdb`` as a data volume.
.. code-block:: bash
COUCH1=$(docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
COUCH1=$(sudo docker run -d -v /var/lib/couchdb shykes/couchdb:2013-05-03)
Add data to the first database
------------------------------
We're assuming your docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your docker host.
We're assuming your docker host is reachable at `localhost`. If not,
replace `localhost` with the public IP of your docker host.
.. code-block:: bash
HOST=localhost
URL="http://$HOST:$(docker port $COUCH1 5984)/_utils/"
URL="http://$HOST:$(sudo docker port $COUCH1 5984)/_utils/"
echo "Navigate to $URL in your browser, and use the couch interface to add data"
Create second database
@ -39,7 +41,7 @@ This time, we're requesting shared access to $COUCH1's volumes.
.. code-block:: bash
COUCH2=$(docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
COUCH2=$(sudo docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
Browse data on the second database
----------------------------------
@ -47,7 +49,8 @@ Browse data on the second database
.. code-block:: bash
HOST=localhost
URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
URL="http://$HOST:$(sudo docker port $COUCH2 5984)/_utils/"
echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
Congratulations, you are running 2 Couchdb containers, completely
isolated from each other *except* for their data.

View file

@ -11,26 +11,28 @@ Hello World
This is the most basic example available for using Docker.
Download the base container
Download the base image (named "ubuntu"):
.. code-block:: bash
# Download a base image
docker pull base
# Download an ubuntu image
sudo docker pull ubuntu
The *base* image is a minimal *ubuntu* based container, alternatively you can select *busybox*, a bare
minimal linux system. The images are retrieved from the docker repository.
Alternatively to the *ubuntu* image, you can select *busybox*, a bare
minimal Linux system. The images are retrieved from the Docker
repository.
.. code-block:: bash
#run a simple echo command, that will echo hello world back to the console over standard out.
docker run base /bin/echo hello world
sudo docker run ubuntu /bin/echo hello world
**Explanation:**
- **"sudo"** execute the following commands as user *root*
- **"docker run"** run a command in a new container
- **"base"** is the image we want to run the command inside of.
- **"ubuntu"** is the image we want to run the command inside of.
- **"/bin/echo"** is the command we want to run in the container
- **"hello world"** is the input for the echo command
@ -47,4 +49,4 @@ See the example in action
</div>
Continue to the :ref:`hello_world_daemon` example.
Continue to the :ref:`hello_world_daemon` example.

View file

@ -11,27 +11,35 @@ Hello World Daemon
The most boring daemon ever written.
This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
We will use the base image to run a simple hello world daemon that will just print hello world to standard
out every second. It will continue to do this until we stop it.
This example assumes you have Docker installed and with the Ubuntu
image already imported ``docker pull ubuntu``. We will use the Ubuntu
image to run a simple hello world daemon that will just print hello
world to standard out every second. It will continue to do this until
we stop it.
**Steps:**
.. code-block:: bash
CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done")
We are going to run a simple hello world daemon in a new container made from the base image.
We are going to run a simple hello world daemon in a new container
made from the *ubuntu* image.
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
- **"base"** is the image we want to run the command inside of.
- **"docker run -d "** run a command in a new container. We pass "-d"
so it runs as a daemon.
- **"ubuntu"** is the image we want to run the command inside of.
- **"/bin/sh -c"** is the command we want to run in the container
- **"while true; do echo hello world; sleep 1; done"** is the mini script we want to run, that will just print hello world once a second until we stop it.
- **$CONTAINER_ID** the output of the run command will return a container id, we can use in future commands to see what is going on with this process.
- **"while true; do echo hello world; sleep 1; done"** is the mini
script we want to run, that will just print hello world once a
second until we stop it.
- **$CONTAINER_ID** the output of the run command will return a
container id, we can use in future commands to see what is going on
with this process.
.. code-block:: bash
docker logs $CONTAINER_ID
sudo docker logs $CONTAINER_ID
Check the logs make sure it is working correctly.
@ -40,16 +48,17 @@ Check the logs make sure it is working correctly.
.. code-block:: bash
docker attach $CONTAINER_ID
sudo docker attach $CONTAINER_ID
Attach to the container to see the results in realtime.
- **"docker attach**" This will allow us to attach to a background process to see what is going on.
- **"docker attach**" This will allow us to attach to a background
process to see what is going on.
- **$CONTAINER_ID** The Id of the container we want to attach too.
.. code-block:: bash
docker ps
sudo docker ps
Check the process list to make sure it is running.
@ -57,7 +66,7 @@ Check the process list to make sure it is running.
.. code-block:: bash
docker stop $CONTAINER_ID
sudo docker stop $CONTAINER_ID
Stop the container, since we don't need it anymore.
@ -66,7 +75,7 @@ Stop the container, since we don't need it anymore.
.. code-block:: bash
docker ps
sudo docker ps
Make sure it is really stopped.

View file

@ -1,6 +1,6 @@
:title: Docker Examples
:description: Examples on how to use Docker
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples
:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples, postgresql
@ -20,3 +20,5 @@ Contents:
running_redis_service
running_ssh_service
couchdb_data_volumes
postgresql_service
mongodb

View file

@ -0,0 +1,98 @@
:title: Building a Docker Image with MongoDB
:description: How to build a Docker image with MongoDB pre-installed
:keywords: docker, example, package installation, networking, mongodb
.. _mongodb_image:
Building an Image with MongoDB
==============================
.. include:: example_header.inc
The goal of this example is to show how you can build your own
docker images with MongoDB preinstalled. We will do that by
constructing a Dockerfile that downloads a base image, adds an
apt source and installs the database software on Ubuntu.
Creating a ``Dockerfile``
+++++++++++++++++++++++++
Create an empty file called ``Dockerfile``:
.. code-block:: bash
touch Dockerfile
Next, define the parent image you want to use to build your own image on top of.
Here, well use `CentOS <https://index.docker.io/_/ubuntu/>`_ (tag: ``latest``)
available on the `docker index`_:
.. code-block:: bash
FROM ubuntu:latest
Since we want to be running the latest version of MongoDB we'll need to add the
10gen repo to our apt sources list.
.. code-block:: bash
# Add 10gen official apt source to the sources list
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
Then, we don't want Ubuntu to complain about init not being available so we'll
divert /sbin/initctl to /bin/true so it thinks everything is working.
.. code-block:: bash
# Hack for initctl not being available in Ubuntu
RUN dpkg-divert --local --rename --add /sbin/initctl
RUN ln -s /bin/true /sbin/initctl
Afterwards we'll be able to update our apt repositories and install MongoDB
.. code-block:: bash
# Install MongoDB
RUN apt-get update
RUN apt-get install mongodb-10gen
To run MongoDB we'll have to create the default data directory (because we want it to
run without needing to provide a special configuration file)
.. code-block:: bash
# Create the MongoDB data directory
RUN mkdir -p /data/db
Finally, we'll expose the standard port that MongoDB runs on (27107)
.. code-block:: bash
EXPOSE 27017
Now, lets build the image which will go through the ``Dockerfile`` we made and
run all of the commands.
.. code-block:: bash
docker build -t <yourname>/mongodb .
Now you should be able to run ``mongod`` as a daemon and be able to connect on
the local port!
.. code-block:: bash
# Regular style
MONGO_ID=$(docker run -d <yourname>/mongodb mongod)
# Lean and mean
MONGO_ID=$(docker run -d <yourname>/mongodb mongod --noprealloc --smallfiles)
# Check the logs out
docker logs $MONGO_ID
# Connect and play around
mongo --port <port you get from `docker ps`>
Sweet!

View file

@ -9,10 +9,11 @@ Node.js Web App
.. include:: example_header.inc
The goal of this example is to show you how you can build your own docker images
from a parent image using a ``Dockerfile`` . We will do that by making a simple
Node.js hello world web application running on CentOS. You can get the full
source code at https://github.com/gasi/docker-node-hello.
The goal of this example is to show you how you can build your own
docker images from a parent image using a ``Dockerfile`` . We will do
that by making a simple Node.js hello world web application running on
CentOS. You can get the full source code at
https://github.com/gasi/docker-node-hello.
Create Node.js app
++++++++++++++++++
@ -109,16 +110,17 @@ Install your app dependencies using npm:
# Install app dependencies
RUN cd /src; npm install
Your app binds to port ``8080`` so youll use the ``EXPOSE`` command to have it
mapped by the docker daemon:
Your app binds to port ``8080`` so youll use the ``EXPOSE`` command
to have it mapped by the docker daemon:
.. code-block:: bash
EXPOSE 8080
Last but not least, define the command to run your app using ``CMD`` which
defines your runtime, i.e. ``node``, and the path to our app, i.e.
``src/index.js`` (see the step where we added the source to the container):
Last but not least, define the command to run your app using ``CMD``
which defines your runtime, i.e. ``node``, and the path to our app,
i.e. ``src/index.js`` (see the step where we added the source to the
container):
.. code-block:: bash
@ -149,19 +151,20 @@ Your ``Dockerfile`` should now look like this:
Building your image
+++++++++++++++++++
Go to the directory that has your ``Dockerfile`` and run the following command
to build a docker image. The ``-t`` flag lets you tag your image so its easier
to find later using the ``docker images`` command:
Go to the directory that has your ``Dockerfile`` and run the following
command to build a docker image. The ``-t`` flag lets you tag your
image so its easier to find later using the ``docker images``
command:
.. code-block:: bash
docker build -t <your username>/centos-node-hello .
sudo docker build -t <your username>/centos-node-hello .
Your image will now be listed by docker:
.. code-block:: bash
docker images
sudo docker images
> # Example
> REPOSITORY TAG ID CREATED
@ -177,17 +180,17 @@ container running in the background. Run the image you previously built:
.. code-block:: bash
docker run -d <your username>/centos-node-hello
sudo docker run -d <your username>/centos-node-hello
Print the output of your app:
.. code-block:: bash
# Get container ID
docker ps
sudo docker ps
# Print app output
docker logs <container id>
sudo docker logs <container id>
> # Example
> Running on http://localhost:8080
@ -225,8 +228,8 @@ Now you can call your app using ``curl`` (install if needed via:
>
> Hello World
We hope this tutorial helped you get up and running with Node.js and CentOS on
docker. You can get the full source code at
We hope this tutorial helped you get up and running with Node.js and
CentOS on docker. You can get the full source code at
https://github.com/gasi/docker-node-hello.
Continue to :ref:`running_redis_service`.

View file

@ -0,0 +1,158 @@
:title: PostgreSQL service How-To
:description: Running and installing a PostgreSQL service
:keywords: docker, example, package installation, postgresql
.. _postgresql_service:
PostgreSQL Service
==================
.. note::
A shorter version of `this blog post`_.
.. note::
As of version 0.5.2, docker requires root privileges to run.
You have to either manually adjust your system configuration (permissions on
/var/run/docker.sock or sudo config), or prefix `docker` with `sudo`. Check
`this thread`_ for details.
.. _this blog post: http://zaiste.net/2013/08/docker_postgresql_how_to/
.. _this thread: https://groups.google.com/forum/?fromgroups#!topic/docker-club/P3xDLqmLp0E
Installing PostgreSQL on Docker
-------------------------------
For clarity I won't be showing commands output.
Run an interactive shell in Docker container.
.. code-block:: bash
sudo docker run -i -t ubuntu /bin/bash
Update its dependencies.
.. code-block:: bash
apt-get update
Install ``python-software-properies``.
.. code-block:: bash
apt-get install python-software-properties
apt-get install software-properties-common
Add Pitti's PostgreSQL repository. It contains the most recent stable release
of PostgreSQL i.e. ``9.2``.
.. code-block:: bash
add-apt-repository ppa:pitti/postgresql
apt-get update
Finally, install PostgreSQL 9.2
.. code-block:: bash
apt-get -y install postgresql-9.2 postgresql-client-9.2 postgresql-contrib-9.2
Now, create a PostgreSQL superuser role that can create databases and
other roles. Following Vagrant's convention the role will be named
`docker` with `docker` password assigned to it.
.. code-block:: bash
sudo -u postgres createuser -P -d -r -s docker
Create a test database also named ``docker`` owned by previously created ``docker``
role.
.. code-block:: bash
sudo -u postgres createdb -O docker docker
Adjust PostgreSQL configuration so that remote connections to the
database are possible. Make sure that inside
``/etc/postgresql/9.2/main/pg_hba.conf`` you have following line:
.. code-block:: bash
host all all 0.0.0.0/0 md5
Additionaly, inside ``/etc/postgresql/9.2/main/postgresql.conf``
uncomment ``listen_addresses`` so it is as follows:
.. code-block:: bash
listen_addresses='*'
*Note:* this PostgreSQL setup is for development only purposes. Refer
to PostgreSQL documentation how to fine-tune these settings so that it
is enough secure.
Create an image and assign it a name. ``<container_id>`` is in the
Bash prompt; you can also locate it using ``docker ps -a``.
.. code-block:: bash
docker commit <container_id> <your username>/postgresql
Finally, run PostgreSQL server via ``docker``.
.. code-block:: bash
CONTAINER=$(sudo docker run -d -p 5432 \
-t <your username>/postgresql \
/bin/su postgres -c '/usr/lib/postgresql/9.2/bin/postgres \
-D /var/lib/postgresql/9.2/main \
-c config_file=/etc/postgresql/9.2/main/postgresql.conf')
Connect the PostgreSQL server using ``psql``.
.. code-block:: bash
CONTAINER_IP=$(sudo docker inspect $CONTAINER | grep IPAddress | awk '{ print $2 }' | tr -d ',"')
psql -h $CONTAINER_IP -p 5432 -d docker -U docker -W
As before, create roles or databases if needed.
.. code-block:: bash
psql (9.2.4)
Type "help" for help.
docker=# CREATE DATABASE foo OWNER=docker;
CREATE DATABASE
Additionally, publish there your newly created image on Docker Index.
.. code-block:: bash
sudo docker login
Username: <your username>
[...]
.. code-block:: bash
sudo docker push <your username>/postgresql
PostgreSQL service auto-launch
------------------------------
Running our image seems complicated. We have to specify the whole command with
``docker run``. Let's simplify it so the service starts automatically when the
container starts.
.. code-block:: bash
sudo docker commit <container_id> <your username>/postgresql -run='{"Cmd": \
["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.2/bin/postgres -D \
/var/lib/postgresql/9.2/main -c \
config_file=/etc/postgresql/9.2/main/postgresql.conf"], PortSpecs": ["5432"]}
From now on, just type ``docker run <your username>/postgresql`` and
PostgreSQL should automatically start.

View file

@ -9,13 +9,16 @@ Python Web App
.. include:: example_header.inc
The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
The goal of this example is to show you how you can author your own
docker images using a parent image, making changes to it, and then
saving the results as a new image. We will do that by making a simple
hello flask web application image.
**Steps:**
.. code-block:: bash
docker pull shykes/pybuilder
sudo docker pull shykes/pybuilder
We are downloading the "shykes/pybuilder" docker image
@ -27,46 +30,66 @@ We set a URL variable that points to a tarball of a simple helloflask web app
.. code-block:: bash
BUILD_JOB=$(docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
BUILD_JOB=$(sudo docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL)
Inside of the "shykes/pybuilder" image there is a command called buildapp, we are running that command and passing the $URL variable from step 2 to it, and running the whole thing inside of a new container. BUILD_JOB will be set with the new container_id.
Inside of the "shykes/pybuilder" image there is a command called
buildapp, we are running that command and passing the $URL variable
from step 2 to it, and running the whole thing inside of a new
container. BUILD_JOB will be set with the new container_id.
.. code-block:: bash
docker attach $BUILD_JOB
sudo docker attach $BUILD_JOB
[...]
We attach to the new container to see what is going on. Ctrl-C to disconnect
While this container is running, we can attach to the new container to
see what is going on. Ctrl-C to disconnect.
.. code-block:: bash
BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
sudo docker ps -a
List all docker containers. If this container has already finished
running, it will still be listed here.
.. code-block:: bash
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
BUILD_IMG=$(sudo docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
- **"-p 5000"** the web app is going to listen on this port, so it must be mapped from the container to the host system.
Save the changes we just made in the container to a new image called
``_/builds/github.com/hykes/helloflask/master`` and save the image id in
the BUILD_IMG variable name.
.. code-block:: bash
WEB_WORKER=$(sudo docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
- **"docker run -d "** run a command in a new container. We pass "-d"
so it runs as a daemon.
- **"-p 5000"** the web app is going to listen on this port, so it
must be mapped from the container to the host system.
- **"$BUILD_IMG"** is the image we want to run the command inside of.
- **/usr/local/bin/runapp** is the command which starts the web app.
Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
Use the new image we just created and create a new container with
network port 5000, and return the container id and store in the
WEB_WORKER variable.
.. code-block:: bash
docker logs $WEB_WORKER
sudo docker logs $WEB_WORKER
* Running on http://0.0.0.0:5000/
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
View the logs for the new container using the WEB_WORKER variable, and
if everything worked as planned you should see the line "Running on
http://0.0.0.0:5000/" in the log output.
.. code-block:: bash
WEB_PORT=$(docker port $WEB_WORKER 5000)
lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable.
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.
.. code-block:: bash
@ -74,7 +97,8 @@ lookup the public-facing port which is NAT-ed store the private port used by the
curl http://127.0.0.1:$WEB_PORT
Hello world!
access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
Access the web app using curl. If everything worked as planned you
should see the line "Hello world!" inside of your console.
**Video:**

View file

@ -7,16 +7,17 @@
Running the Examples
--------------------
All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
All the examples assume your machine is running the docker daemon. To
run the docker daemon in the background, simply type:
.. code-block:: bash
sudo docker -d &
Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client
can run from any account.
Now you can run docker in client mode: by defalt all commands will be
forwarded to the ``docker`` daemon via a protected Unix socket, so you
must run as root.
.. code-block:: bash
# now you can run docker commands from any account.
docker help
sudo docker help

View file

@ -16,12 +16,13 @@ Open a docker container
.. code-block:: bash
docker run -i -t base /bin/bash
sudo docker run -i -t ubuntu /bin/bash
Building your image
-------------------
Update your docker container, install the redis server. Once installed, exit out of docker.
Update your Docker container, install the Redis server. Once
installed, exit out of the Docker container.
.. code-block:: bash
@ -45,7 +46,7 @@ container running in the background. Use your snapshot.
.. code-block:: bash
docker run -d -p 6379 <your username>/redis /usr/bin/redis-server
sudo docker run -d -p 6379 <your username>/redis /usr/bin/redis-server
Test 1
++++++
@ -54,8 +55,8 @@ Connect to the container with the redis-cli.
.. code-block:: bash
docker ps # grab the new container id
docker inspect <container_id> # grab the ipaddress of the container
sudo docker ps # grab the new container id
sudo docker inspect <container_id> # grab the ipaddress of the container
redis-cli -h <ipaddress> -p 6379
redis 10.0.3.32:6379> set docker awesome
OK
@ -70,8 +71,8 @@ Connect to the host os with the redis-cli.
.. code-block:: bash
docker ps # grab the new container id
docker port <container_id> 6379 # grab the external port
sudo docker ps # grab the new container id
sudo docker port <container_id> 6379 # grab the external port
ip addr show # grab the host ip address
redis-cli -h <host ipaddress> -p <external port>
redis 192.168.0.1:49153> set docker awesome

View file

@ -12,19 +12,27 @@ SSH Daemon Service
**Video:**
I've create a little screencast to show how to create a sshd service and connect to it. It is something like 11
minutes and not entirely smooth, but gives you a good idea.
I've create a little screencast to show how to create a sshd service
and connect to it. It is something like 11 minutes and not entirely
smooth, but gives you a good idea.
.. note::
This screencast was created before ``docker`` version 0.5.2, so the
daemon is unprotected and available via a TCP port. When you run
through the same steps in a newer version of ``docker``, you will
need to add ``sudo`` in front of each ``docker`` command in order
to reach the daemon over its protected Unix socket.
.. raw:: html
<div style="margin-top:10px;">
<iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
</div>
You can also get this sshd container by using
::
docker pull dhrp/sshd
sudo docker pull dhrp/sshd
The password is 'screencast'
@ -33,47 +41,57 @@ The password is 'screencast'
.. code-block:: bash
# Hello! We are going to try and install openssh on a container and run it as a servic
# let's pull base to get a base ubuntu image.
$ docker pull base
# I had it so it was quick
# now let's connect using -i for interactive and with -t for terminal
# we execute /bin/bash to get a prompt.
$ docker run -i -t base /bin/bash
# now let's commit it
# which container was it?
$ docker ps -a |more
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
# I gave the name dhrp/sshd for the container
# now we can run it again
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
# is it running?
$ docker ps
# yes!
# let's stop it
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
$ docker ps
# and reconnect, but now open a port to it
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
# it has now given us a port to connect to
# we have to connect using a public ip of our host
$ hostname
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig
$ ssh root@192.168.33.10 -p 49153
# Ah! forgot to set root passwd
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
$ docker ps -a
$ docker run -i -t dhrp/sshd /bin/bash
$ passwd
$ exit
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig
$ ssh root@192.168.33.10 -p 49154
# Thanks for watching, Thatcher thatcher@dotcloud.com
# Hello! We are going to try and install openssh on a container and run it as a service
# let's pull ubuntu to get a base ubuntu image.
$ docker pull ubuntu
# I had it so it was quick
# now let's connect using -i for interactive and with -t for terminal
# we execute /bin/bash to get a prompt.
$ docker run -i -t base /bin/bash
# yes! we are in!
# now lets install openssh
$ apt-get update
$ apt-get install openssh-server
# ok. lets see if we can run it.
$ which sshd
# we need to create privilege separation directory
$ mkdir /var/run/sshd
$ /usr/sbin/sshd
$ exit
# now let's commit it
# which container was it?
$ docker ps -a |more
$ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd
# I gave the name dhrp/sshd for the container
# now we can run it again
$ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode
# is it running?
$ docker ps
# yes!
# let's stop it
$ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
$ docker ps
# and reconnect, but now open a port to it
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
# it has now given us a port to connect to
# we have to connect using a public ip of our host
$ hostname
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig
$ ssh root@192.168.33.10 -p 49153
# Ah! forgot to set root passwd
$ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd
$ docker ps -a
$ docker run -i -t dhrp/sshd /bin/bash
$ passwd
$ exit
$ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
$ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
$ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
# *ifconfig* is deprecated, better use *ip addr show* now
$ ifconfig
$ ssh root@192.168.33.10 -p 49154
# Thanks for watching, Thatcher thatcher@dotcloud.com

View file

@ -9,45 +9,145 @@ FAQ
Most frequently asked questions.
--------------------------------
1. **How much does Docker cost?**
How much does Docker cost?
..........................
Docker is 100% free, it is open source, so you can use it without paying.
2. **What open source license are you using?**
What open source license are you using?
.......................................
We are using the Apache License Version 2.0, see it here: https://github.com/dotcloud/docker/blob/master/LICENSE
We are using the Apache License Version 2.0, see it here:
https://github.com/dotcloud/docker/blob/master/LICENSE
3. **Does Docker run on Mac OS X or Windows?**
Does Docker run on Mac OS X or Windows?
.......................................
Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a
virtual machine on your box, and get the best of both worlds. Check out the :ref:`install_using_vagrant` and :ref:`windows` installation guides.
Not at this time, Docker currently only runs on Linux, but you can
use VirtualBox to run Docker in a virtual machine on your box, and
get the best of both worlds. Check out the
:ref:`install_using_vagrant` and :ref:`windows` installation
guides.
4. **How do containers compare to virtual machines?**
How do containers compare to virtual machines?
..............................................
They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery.
They are complementary. VMs are best used to allocate chunks of
hardware resources. Containers operate at the process level, which
makes them very lightweight and perfect as a unit of software
delivery.
5. **Can I help by adding some questions and answers?**
What does Docker add to just plain LXC?
.......................................
Docker is not a replacement for LXC. "LXC" refers to capabilities
of the Linux kernel (specifically namespaces and control groups)
which allow sandboxing processes from one another, and controlling
their resource allocations. On top of this low-level foundation of
kernel features, Docker offers a high-level tool with several
powerful functionalities:
* *Portable deployment across machines.*
Docker defines a format for bundling an application and all its
dependencies into a single object which can be transferred to
any Docker-enabled machine, and executed there with the
guarantee that the execution environment exposed to the
application will be the same. LXC implements process sandboxing,
which is an important pre-requisite for portable deployment, but
that alone is not enough for portable deployment. If you sent me
a copy of your application installed in a custom LXC
configuration, it would almost certainly not run on my machine
the way it does on yours, because it is tied to your machine's
specific configuration: networking, storage, logging, distro,
etc. Docker defines an abstraction for these machine-specific
settings, so that the exact same Docker container can run -
unchanged - on many different machines, with many different
configurations.
* *Application-centric.*
Docker is optimized for the deployment of applications, as
opposed to machines. This is reflected in its API, user
interface, design philosophy and documentation. By contrast, the
``lxc`` helper scripts focus on containers as lightweight
machines - basically servers that boot faster and need less
RAM. We think there's more to containers than just that.
* *Automatic build.*
Docker includes :ref:`a tool for developers to automatically
assemble a container from their source code <dockerbuilder>`,
with full control over application dependencies, build tools,
packaging etc. They are free to use ``make, maven, chef, puppet,
salt,`` Debian packages, RPMs, source tarballs, or any
combination of the above, regardless of the configuration of the
machines.
* *Versioning.*
Docker includes git-like capabilities for tracking successive
versions of a container, inspecting the diff between versions,
committing new versions, rolling back etc. The history also
includes how a container was assembled and by whom, so you get
full traceability from the production server all the way back to
the upstream developer. Docker also implements incremental
uploads and downloads, similar to ``git pull``, so new versions
of a container can be transferred by only sending diffs.
* *Component re-use.*
Any container can be used as a :ref:`"base image"
<base_image_def>` to create more specialized components. This
can be done manually or as part of an automated build. For
example you can prepare the ideal Python environment, and use it
as a base for 10 different applications. Your ideal Postgresql
setup can be re-used for all your future projects. And so on.
* *Sharing.*
Docker has access to a `public registry
<http://index.docker.io>`_ where thousands of people have
uploaded useful containers: anything from Redis, CouchDB,
Postgres to IRC bouncers to Rails app servers to Hadoop to base
images for various Linux distros. The :ref:`registry
<registryindexspec>` also includes an official "standard
library" of useful containers maintained by the Docker team. The
registry itself is open-source, so anyone can deploy their own
registry to store and transfer private containers, for internal
server deployments for example.
* *Tool ecosystem.*
Docker defines an API for automating and customizing the
creation and deployment of containers. There are a huge number
of tools integrating with Docker to extend its
capabilities. PaaS-like deployment (Dokku, Deis, Flynn),
multi-node orchestration (Maestro, Salt, Mesos, Openstack Nova),
management dashboards (docker-ui, Openstack Horizon, Shipyard),
configuration management (Chef, Puppet), continuous integration
(Jenkins, Strider, Travis), etc. Docker is rapidly establishing
itself as the standard for container-based tooling.
Can I help by adding some questions and answers?
................................................
Definitely! You can fork `the repo`_ and edit the documentation sources.
42. **Where can I find more answers?**
Where can I find more answers?
..............................
You can find more answers on:
* `Docker club mailinglist`_
* `Docker user mailinglist`_
* `Docker developer mailinglist`_
* `IRC, docker on freenode`_
* `Github`_
* `Ask questions on Stackoverflow`_
* `Join the conversation on Twitter`_
.. _Docker club mailinglist: https://groups.google.com/d/forum/docker-club
.. _Docker user mailinglist: https://groups.google.com/d/forum/docker-user
.. _Docker developer mailinglist: https://groups.google.com/d/forum/docker-dev
.. _the repo: http://www.github.com/dotcloud/docker
.. _IRC, docker on freenode: irc://chat.freenode.net#docker
.. _Github: http://www.github.com/dotcloud/docker
.. _Ask questions on Stackoverflow: http://stackoverflow.com/search?q=docker
.. _Join the conversation on Twitter: http://twitter.com/getdocker
.. _Join the conversation on Twitter: http://twitter.com/docker
Looking for something else to read? Checkout the :ref:`hello_world` example.

View file

@ -23,7 +23,7 @@ dependencies.
commit``).
Each use of ``docker`` is documented here. The features of Docker are
currently in active development, so this documention will change
currently in active development, so this documentation will change
frequently.
For an overview of Docker, please see the `Introduction

View file

@ -1,14 +1,18 @@
:title: Installation on Amazon EC2
:title: Installation on Amazon EC2
:description: Docker installation on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required.
:keywords: amazon ec2, virtualization, cloud, docker, documentation, installation
Amazon EC2
==========
Using Vagrant (Amazon EC2)
==========================
This page explains how to setup and run an Amazon EC2 instance from your local machine.
Vagrant is not necessary to run Docker on EC2. You can follow the :ref:`ubuntu_linux` instructions
installing Docker on any EC2 instance running Ubuntu
Please note this is a community contributed installation path. The only 'official' installation is using the
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
Installation
------------
@ -86,7 +90,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant
.. code-block:: bash
docker
sudo docker
Continue with the :ref:`hello_world` example.
Continue with the :ref:`hello_world` example.

View file

@ -56,10 +56,10 @@ Run your first container!
.. code-block:: bash
# check your docker version
./docker version
sudo ./docker version
# run a container and open an interactive shell in the container
./docker run -i -t ubuntu /bin/bash
sudo ./docker run -i -t ubuntu /bin/bash

View file

@ -15,12 +15,11 @@ In short, Docker has the following kernel requirements:
- Cgroups and namespaces must be enabled.
The officially supported kernel is the one recommended by the
:ref:`ubuntu_linux` installation path. It is the one that most developers
will use, and the one that receives the most attention from the core
contributors. If you decide to go with a different kernel and hit a bug,
please try to reproduce it with the official kernels first.
The officially supported kernel is the one recommended by the
:ref:`ubuntu_linux` installation path. It is the one that most developers
will use, and the one that receives the most attention from the core
contributors. If you decide to go with a different kernel and hit a bug,
please try to reproduce it with the official kernels first.
If you cannot or do not want to use the "official" kernels,
here is some technical background about the features (both optional and

View file

@ -10,7 +10,7 @@ Rackspace Cloud
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
Installing Docker on Ubuntu proviced by Rackspace is pretty straightforward, and you should mostly be able to follow the
Installing Docker on Ubuntu provided by Rackspace is pretty straightforward, and you should mostly be able to follow the
:ref:`ubuntu_linux` installation guide.
**However, there is one caveat:**

View file

@ -19,6 +19,8 @@ Docker has the following dependencies
* Linux kernel 3.8 (read more about :ref:`kernel`)
* AUFS file system support (we are working on BTRFS support as an alternative)
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated Firewall) <https://help.ubuntu.com/community/UFW>`_
.. _ubuntu_precise:
Ubuntu Precise 12.04 (LTS) (64-bit)
@ -32,13 +34,20 @@ Dependencies
**Linux kernel 3.8**
Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in.
Due to a bug in LXC, docker works best on the 3.8 kernel. Precise
comes with a 3.2 kernel, so we need to upgrade it. The kernel we
install comes with AUFS built in. We also include the generic headers
to enable packages that depend on them, like ZFS and the VirtualBox
guest additions. If you didn't install the headers for your "precise"
kernel, then you can skip these headers for the "raring" kernel. But
it is safer to include them if you're not sure.
.. code-block:: bash
# install the backported kernel
sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring
sudo apt-get update
sudo apt-get install linux-image-generic-lts-raring linux-headers-generic-lts-raring
# reboot
sudo reboot
@ -67,7 +76,7 @@ Verify it worked
.. code-block:: bash
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
docker run -i -t ubuntu /bin/bash
sudo docker run -i -t ubuntu /bin/bash
# type 'exit' to exit
@ -129,9 +138,41 @@ Verify it worked
.. code-block:: bash
# download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
docker run -i -t ubuntu /bin/bash
sudo docker run -i -t ubuntu /bin/bash
# type exit to exit
**Done!**, now continue with the :ref:`hello_world` example.
.. _ufw:
Docker and UFW
^^^^^^^^^^^^^^
Docker uses a bridge to manage containers networking, by default UFW drop all `forwarding`, a first step is to enable forwarding:
.. code-block:: bash
sudo nano /etc/default/ufw
----
# Change:
# DEFAULT_FORWARD_POLICY="DROP"
# to
DEFAULT_FORWARD_POLICY="ACCEPT"
Then reload UFW:
.. code-block:: bash
sudo ufw reload
UFW's default set of rules denied all `incoming`, so if you want to be able to reach your containers from another host,
you should allow incoming connections on the docker port (default 4243):
.. code-block:: bash
sudo ufw allow 4243/tcp

View file

@ -63,7 +63,7 @@ Now you are in the VM, run docker
.. code-block:: bash
docker
sudo docker
Continue with the :ref:`hello_world` example.

View file

@ -14,7 +14,7 @@ switches the whole rootfs volume to read-write mode.
Layer
.....
When Docker mounts the rootfs, it starts read-only, as in a tradtional
When Docker mounts the rootfs, it starts read-only, as in a traditional
Linux boot, but then, instead of changing the file system to
read-write mode, it takes advantage of a `union mount
<http://en.wikipedia.org/wiki/Union_mount>`_ to add a read-write file

View file

@ -9,11 +9,13 @@ The Basics
Starting Docker
---------------
If you have used one of the quick install paths', Docker may have been installed with upstart, Ubuntu's
system for starting processes at boot time. You should be able to run ``docker help`` and get output.
If you have used one of the quick install paths', Docker may have been
installed with upstart, Ubuntu's system for starting processes at boot
time. You should be able to run ``sudo docker help`` and get output.
If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied``
you will need to specify the path to it and manually start it.
If you get ``docker: command not found`` or something like
``/var/lib/docker/repositories: permission denied`` you will need to
specify the path to it and manually start it.
.. code-block:: bash
@ -26,46 +28,87 @@ Running an interactive shell
.. code-block:: bash
# Download a base image
docker pull base
# Download an ubuntu image
sudo docker pull ubuntu
# Run an interactive shell in the base image,
# Run an interactive shell in the ubuntu image,
# allocate a tty, attach stdin and stdout
docker run -i -t base /bin/bash
sudo docker run -i -t ubuntu /bin/bash
Bind Docker to another host/port or a unix socket
Why ``sudo``?
-------------
The ``docker`` daemon always runs as root, and since ``docker``
version 0.5.2, ``docker`` binds to a Unix socket instead of a TCP
port. By default that Unix socket is owned by the user *root*, and so,
by default, you can access it with ``sudo``.
Starting in version 0.5.3, if you create a Unix group called *docker*
and add users to it, then the ``docker`` daemon will make the
ownership of the Unix socket read/writable by the *docker* group when
the daemon starts. The ``docker`` daemon must always run as root, but
if you run the ``docker`` client as a user in the *docker* group then
you don't need to add ``sudo`` to all the client commands.
.. code-block:: bash
# Add the docker group
sudo groupadd docker
# Add the ubuntu user to the docker group
# You may have to logout and log back in again for
# this to take effect
sudo gpasswd -a ubuntu docker
# Restart the docker daemon
sudo service docker restart
Bind Docker to another host/port or a Unix socket
-------------------------------------------------
With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
.. DANGER:: Changing the default ``docker`` daemon binding to a TCP
port or Unix *docker* user group will increase your security risks
by allowing non-root users to potentially gain *root* access on the
host (`e.g. #1369
<https://github.com/dotcloud/docker/issues/1369>`_). Make sure you
control access to ``docker``.
Similarly, the Docker client can use -H to connect to a custom port.
With -H it is possible to make the Docker daemon to listen on a
specific ip and port. By default, it will listen on
``unix:///var/run/docker.sock`` to allow only local connections by the
*root* user. You *could* set it to 0.0.0.0:4243 or a specific host ip to
give access to everybody, but that is **not recommended** because then
it is trivial for someone to gain root access to the host where the
daemon is running.
Similarly, the Docker client can use ``-H`` to connect to a custom port.
``-H`` accepts host and port assignment in the following format:
``tcp://[host][:port]`` or ``unix://path``
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
For example:
* tcp://host -> tcp connection on host:4243
* tcp://host:port -> tcp connection on host:port
* tcp://:port -> tcp connection on 127.0.0.1:port
* unix://path/to/socket -> unix socket located at path/to/socket
* ``tcp://host:4243`` -> tcp connection on host:4243
* ``unix://path/to/socket`` -> unix socket located at ``path/to/socket``
.. code-block:: bash
# Run docker in daemon mode
sudo <path to>/docker -H 0.0.0.0:5555 -d &
# Download a base image
docker -H :5555 pull base
# Download an ubuntu image
sudo docker -H :5555 pull ubuntu
You can use multiple -H, for example, if you want to listen
on both tcp and a unix socket
You can use multiple ``-H``, for example, if you want to listen on
both TCP and a Unix socket
.. code-block:: bash
# Run docker in daemon mode
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d &
# Download a base image
docker pull base
# OR
docker -H unix:///var/run/docker.sock pull base
# Download an ubuntu image, use default Unix socket
sudo docker pull ubuntu
# OR use the TCP port
sudo docker -H tcp://127.0.0.1:4243 pull ubuntu
Starting a long-running worker process
--------------------------------------
@ -73,13 +116,13 @@ Starting a long-running worker process
.. code-block:: bash
# Start a very useful long-running process
JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
JOB=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done")
# Collect the output of the job so far
docker logs $JOB
sudo docker logs $JOB
# Kill the job
docker kill $JOB
sudo docker kill $JOB
Listing all running containers
@ -87,7 +130,7 @@ Listing all running containers
.. code-block:: bash
docker ps
sudo docker ps
Expose a service on a TCP port
------------------------------
@ -95,10 +138,10 @@ Expose a service on a TCP port
.. code-block:: bash
# Expose port 4444 of this container, and tell netcat to listen on it
JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
JOB=$(sudo docker run -d -p 4444 ubuntu /bin/nc -l -p 4444)
# Which public port is NATed to my container?
PORT=$(docker port $JOB 4444)
PORT=$(sudo docker port $JOB 4444)
# Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
@ -107,7 +150,7 @@ Expose a service on a TCP port
echo hello world | nc $IP $PORT
# Verify that the network connection worked
echo "Daemon received: $(docker logs $JOB)"
echo "Daemon received: $(sudo docker logs $JOB)"
Committing (saving) a container state
@ -115,21 +158,23 @@ Committing (saving) a container state
Save your containers state to a container image, so the state can be re-used.
When you commit your container only the differences between the image the container was created from
and the current state of the container will be stored (as a diff). See which images you already have
using ``docker images``
When you commit your container only the differences between the image
the container was created from and the current state of the container
will be stored (as a diff). See which images you already have using
``sudo docker images``
.. code-block:: bash
# Commit your container to a new named image
docker commit <container_id> <some_name>
sudo docker commit <container_id> <some_name>
# List your containers
docker images
sudo docker images
You now have a image state from which you can create new instances.
Read more about :ref:`working_with_the_repository` or continue to the complete :ref:`cli`
Read more about :ref:`working_with_the_repository` or continue to the
complete :ref:`cli`

View file

@ -2,6 +2,8 @@
:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image.
:keywords: builder, docker, Dockerfile, automation, image creation
.. _dockerbuilder:
==================
Dockerfile Builder
==================
@ -23,12 +25,12 @@ describe the steps to assemble the image.
Then call ``docker build`` with the path of your source repository as
argument:
``docker build .``
``sudo docker build .``
You can specify a repository and tag at which to save the new image if the
build succeeds:
``docker build -t shykes/myapp .``
``sudo docker build -t shykes/myapp .``
Docker will run your steps one-by-one, committing the result if necessary,
before finally outputting the ID of your new image.
@ -100,15 +102,54 @@ control.
3.4 CMD
-------
``CMD <command>``
CMD has three forms:
The ``CMD`` instruction sets the command to be executed when running
the image. This is functionally equivalent to running ``docker commit
-run '{"Cmd": <command>}'`` outside the builder.
* ``CMD ["executable","param1","param2"]`` (like an *exec*, preferred form)
* ``CMD ["param1","param2"]`` (as *default parameters to ENTRYPOINT*)
* ``CMD command param1 param2`` (as a *shell*)
There can only be one CMD in a Dockerfile. If you list more than one
CMD then only the last CMD will take effect.
**The main purpose of a CMD is to provide defaults for an executing
container.** These defaults can include an executable, or they can
omit the executable, in which case you must specify an ENTRYPOINT as
well.
When used in the shell or exec formats, the ``CMD`` instruction sets
the command to be executed when running the image. This is
functionally equivalent to running ``docker commit -run '{"Cmd":
<command>}'`` outside the builder.
If you use the *shell* form of the CMD, then the ``<command>`` will
execute in ``/bin/sh -c``:
.. code-block:: bash
FROM ubuntu
CMD echo "This is a test." | wc -
If you want to **run your** ``<command>`` **without a shell** then you
must express the command as a JSON array and give the full path to the
executable. **This array form is the preferred format of CMD.** Any
additional parameters must be individually expressed as strings in the
array:
.. code-block:: bash
FROM ubuntu
CMD ["/usr/bin/wc","--help"]
If you would like your container to run the same executable every
time, then you should consider using ``ENTRYPOINT`` in combination
with ``CMD``. See :ref:`entrypoint_def`.
If the user specifies arguments to ``docker run`` then they will
override the default specified in CMD.
.. note::
Don't confuse `RUN` with `CMD`. `RUN` actually runs a
command and commits the result; `CMD` does not execute anything at
Don't confuse ``RUN`` with ``CMD``. ``RUN`` actually runs a
command and commits the result; ``CMD`` does not execute anything at
build time, but specifies the intended command for the image.
3.5 EXPOSE
@ -182,18 +223,57 @@ The copy obeys the following rules:
written at ``<dst>``.
* If ``<dest>`` doesn't exist, it is created along with all missing
directories in its path. All new files and directories are created
with mode 0700, uid and gid 0.
with mode 0755, uid and gid 0.
.. _entrypoint_def:
3.8 ENTRYPOINT
--------------
``ENTRYPOINT ["/bin/echo"]``
ENTRYPOINT has two forms:
The ``ENTRYPOINT`` instruction adds an entry command that will not be
overwritten when arguments are passed to docker run, unlike the
* ``ENTRYPOINT ["executable", "param1", "param2"]`` (like an *exec*,
preferred form)
* ``ENTRYPOINT command param1 param2`` (as a *shell*)
There can only be one ``ENTRYPOINT`` in a Dockerfile. If you have more
than one ``ENTRYPOINT``, then only the last one in the Dockerfile will
have an effect.
An ``ENTRYPOINT`` helps you to configure a container that you can run
as an executable. That is, when you specify an ``ENTRYPOINT``, then
the whole container runs as if it was just that executable.
The ``ENTRYPOINT`` instruction adds an entry command that will **not**
be overwritten when arguments are passed to ``docker run``, unlike the
behavior of ``CMD``. This allows arguments to be passed to the
entrypoint. i.e. ``docker run <image> -d`` will pass the "-d" argument
to the entrypoint.
entrypoint. i.e. ``docker run <image> -d`` will pass the "-d"
argument to the ENTRYPOINT.
You can specify parameters either in the ENTRYPOINT JSON array (as in
"like an exec" above), or by using a CMD statement. Parameters in the
ENTRYPOINT will not be overridden by the ``docker run`` arguments, but
parameters specified via CMD will be overridden by ``docker run``
arguments.
Like a ``CMD``, you can specify a plain string for the ENTRYPOINT and
it will execute in ``/bin/sh -c``:
.. code-block:: bash
FROM ubuntu
ENTRYPOINT wc -l -
For example, that Dockerfile's image will *always* take stdin as input
("-") and print the number of lines ("-l"). If you wanted to make
this optional but default, you could use a CMD:
.. code-block:: bash
FROM ubuntu
CMD ["-l", "-"]
ENTRYPOINT ["/usr/bin/wc"]
3.9 VOLUME
----------
@ -203,6 +283,23 @@ to the entrypoint.
The ``VOLUME`` instruction will add one or more new volumes to any
container created from the image.
3.10 USER
---------
``USER daemon``
The ``USER`` instruction sets the username or UID to use when running
the image.
3.11 WORKDIR
------------
``WORKDIR /path/to/workdir``
The ``WORKDIR`` instruction sets the working directory in which
the command given by ``CMD`` is executed.
4. Dockerfile Examples
======================

View file

@ -6,20 +6,23 @@
Port redirection
================
Docker can redirect public tcp ports to your container, so it can be reached over the network.
Port redirection is done on ``docker run`` using the -p flag.
Docker can redirect public TCP ports to your container, so it can be
reached over the network. Port redirection is done on ``docker run``
using the -p flag.
A port redirect is specified as PUBLIC:PRIVATE, where tcp port PUBLIC will be redirected to
tcp port PRIVATE. As a special case, the public port can be omitted, in which case a random
public port will be allocated.
A port redirect is specified as *PUBLIC:PRIVATE*, where TCP port
*PUBLIC* will be redirected to TCP port *PRIVATE*. As a special case,
the public port can be omitted, in which case a random public port
will be allocated.
.. code-block:: bash
# A random PUBLIC port is redirected to PRIVATE port 80 on the container
docker run -p 80 <image> <cmd>
sudo docker run -p 80 <image> <cmd>
# PUBLIC port 80 is redirected to PRIVATE port 80
docker run -p 80:80 <image> <cmd>
sudo docker run -p 80:80 <image> <cmd>
Default port redirects can be built into a container with the EXPOSE build command.
Default port redirects can be built into a container with the
``EXPOSE`` build command.

View file

@ -9,28 +9,32 @@ Using Puppet
.. note::
Please note this is a community contributed installation path. The only 'official' installation is using the
:ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
Please note this is a community contributed installation path. The
only 'official' installation is using the :ref:`ubuntu_linux`
installation path. This version may sometimes be out of date.
Requirements
------------
To use this guide you'll need a working installation of Puppet from `Puppetlabs <https://www.puppetlabs.com>`_ .
To use this guide you'll need a working installation of Puppet from
`Puppetlabs <https://www.puppetlabs.com>`_ .
The module also currently uses the official PPA so only works with Ubuntu.
Installation
------------
The module is available on the `Puppet Forge <https://forge.puppetlabs.com/garethr/docker/>`_
and can be installed using the built-in module tool.
The module is available on the `Puppet Forge
<https://forge.puppetlabs.com/garethr/docker/>`_ and can be installed
using the built-in module tool.
.. code-block:: bash
puppet module install garethr/docker
It can also be found on `GitHub <https://www.github.com/garethr/garethr-docker>`_
if you would rather download the source.
It can also be found on `GitHub
<https://www.github.com/garethr/garethr-docker>`_ if you would rather
download the source.
Usage
-----
@ -53,13 +57,13 @@ defined type which can be used like so:
.. code-block:: ruby
docker::image { 'base': }
docker::image { 'ubuntu': }
This is equivalent to running:
.. code-block:: bash
docker pull base
docker pull ubuntu
Note that it will only if the image of that name does not already exist.
This is downloading a large binary so on first run can take a while.
@ -68,7 +72,7 @@ for exec. Note that you can also remove images you no longer need with:
.. code-block:: ruby
docker::image { 'base':
docker::image { 'ubuntu':
ensure => 'absent',
}
@ -81,7 +85,7 @@ docker.
.. code-block:: ruby
docker::run { 'helloworld':
image => 'base',
image => 'ubuntu',
command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
}
@ -89,14 +93,14 @@ This is equivalent to running the following command, but under upstart:
.. code-block:: bash
docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done"
docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
Run also contains a number of optional parameters:
.. code-block:: ruby
docker::run { 'helloworld':
image => 'base',
image => 'ubuntu',
command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
ports => ['4444', '4555'],
volumes => ['/var/lib/counchdb', '/var/log'],

View file

@ -1,6 +1,6 @@
:title: Working With Repositories
:description: Repositories allow users to share images.
:keywords: repo, repositiores, usage, pull image, push image, image, documentation
:keywords: repo, repositories, usage, pull image, push image, image, documentation
.. _working_with_the_repository:
@ -57,10 +57,10 @@ address of the registry's host, like this:
# Tag to create a repository with the full registry location.
# The location (e.g. localhost.localdomain:5000) becomes
# a permanent part of the repository name
docker tag 0u812deadbeef localhost.localdomain:5000/repo_name
sudo docker tag 0u812deadbeef localhost.localdomain:5000/repo_name
# Push the new repository to its home location on localhost
docker push localhost.localdomain:5000/repo_name
sudo docker push localhost.localdomain:5000/repo_name
Once a repository has your registry's host name as part of the tag,
you can push and pull it like any other repository, but it will
@ -71,18 +71,18 @@ function completely independently from the Central Index.
Find public images available on the Central Index
-------------------------------------------------
Seach by name, namespace or description
Search by name, namespace or description
.. code-block:: bash
docker search <value>
sudo docker search <value>
Download them simply by their name
.. code-block:: bash
docker pull <value>
sudo docker pull <value>
Very similarly you can search for and browse the index online on
@ -96,7 +96,7 @@ You can create a user on the central Docker Index online, or by running
.. code-block:: bash
docker login
sudo docker login
This will prompt you for a username, which will become a public
namespace for your public repositories.
@ -115,7 +115,7 @@ your container to an image within your username namespace.
.. code-block:: bash
# for example docker commit $CONTAINER_ID dhrp/kickassapp
docker commit <container_id> <username>/<repo_name>
sudo docker commit <container_id> <username>/<repo_name>
Pushing a container to its repository
@ -129,4 +129,4 @@ Now you can commit this image to the repository
.. code-block:: bash
# for example docker push dhrp/kickassapp
docker push <username>/<repo_name>
sudo docker push <username>/<repo_name>

View file

@ -1 +1 @@
Thatcher Peskens <thatcher@dotcloud.com>
Thatcher Peskens <thatcher@dotcloud.com> (@dhrp)

View file

@ -70,8 +70,8 @@
<ul class="nav">
<li id="nav-introduction"><a href="http://www.docker.io/" title="Docker Homepage">Home</a></li>
<li id="nav-about"><a href="http://www.docker.io/about/" title="About">About</a></li>
<li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
<li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
<li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
<li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
<li id="nav-blog"><a href="http://blog.docker.io/" title="Docker Blog">Blog</a></li>
<li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
@ -79,7 +79,7 @@
</div>
<div style="margin-left: -12px; float: left;">
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
<a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; width: 160px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
</div>
</div>
@ -93,7 +93,7 @@
<div class="span12 titlebar">
<!--<span class="pull-right" style="margin-left: 20px; font-size: 20px">{{version}}</span>-->
<div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
<a href="http://github.com/dotcloud/docker/"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Fork us on Github</a>
<a href="https://github.com/dotcloud/docker/blob/master/docs/sources/{{ pagename }}.rst"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Edit this page on Github</a>
</div>
<h1 class="pageheader"><a href="http://docs.docker.io/en/latest/" title="Documentation" style="color: white;">DOCUMENTATION</a></h1>
@ -130,7 +130,7 @@
<div class="span12 footer">
<div class="tbox textright forceleftmargin social links pull-right">
<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
<a class="twitter" href="http://twitter.com/docker">Twitter</a>
<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
</div>
@ -156,11 +156,6 @@
{# {%- endif %}#}
{##}
{# <div class="links" style="float: right;">#}
{# <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>#}
{# <a class="github" href="http://github.com/dotcloud/docker/">GitHub</a>#}
{# </div>#}
</div>
</div>
</div>

View file

@ -375,3 +375,19 @@ section.header {
table th {
text-align: left;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
.headerlink {
font-size: smaller;
color: #666;
font-weight: bold;
float: right;
visibility: hidden;
}

View file

@ -486,4 +486,22 @@ section.header {
/* Misc fixes */
table th {
text-align: left;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
.headerlink {
font-size: smaller;
color: #666;
font-weight: bold;
float: right;
visibility: hidden;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 1.4 KiB

132
graph.go
View file

@ -1,9 +1,7 @@
package docker
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@ -11,17 +9,13 @@ import (
"path"
"path/filepath"
"strings"
"sync"
"time"
)
// A Graph is a store for versioned filesystem images and the relationship between them.
type Graph struct {
Root string
idIndex *utils.TruncIndex
checksumLock map[string]*sync.Mutex
lockSumFile *sync.Mutex
lockSumMap *sync.Mutex
Root string
idIndex *utils.TruncIndex
}
// NewGraph instantiates a new graph at the given root path in the filesystem.
@ -36,11 +30,8 @@ func NewGraph(root string) (*Graph, error) {
return nil, err
}
graph := &Graph{
Root: abspath,
idIndex: utils.NewTruncIndex(),
checksumLock: make(map[string]*sync.Mutex),
lockSumFile: &sync.Mutex{},
lockSumMap: &sync.Mutex{},
Root: abspath,
idIndex: utils.NewTruncIndex(),
}
if err := graph.restore(); err != nil {
return nil, err
@ -99,11 +90,6 @@ func (graph *Graph) Get(name string) (*Image, error) {
return nil, err
}
}
graph.lockSumMap.Lock()
defer graph.lockSumMap.Unlock()
if _, exists := graph.checksumLock[img.ID]; !exists {
graph.checksumLock[img.ID] = &sync.Mutex{}
}
return img, nil
}
@ -123,16 +109,15 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
img.Container = container.ID
img.ContainerConfig = *container.Config
}
if err := graph.Register(layerData, layerData != nil, img); err != nil {
if err := graph.Register(nil, layerData, img); err != nil {
return nil, err
}
go img.Checksum()
return img, nil
}
// Register imports a pre-existing image into the graph.
// FIXME: pass img as first argument
func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
func (graph *Graph) Register(jsonData []byte, layerData Archive, img *Image) error {
if err := ValidateID(img.ID); err != nil {
return err
}
@ -145,7 +130,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
if err != nil {
return fmt.Errorf("Mktemp failed: %s", err)
}
if err := StoreImage(img, layerData, tmp, store); err != nil {
if err := StoreImage(img, jsonData, layerData, tmp); err != nil {
return err
}
// Commit
@ -154,7 +139,6 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error {
}
img.graph = graph
graph.idIndex.Add(img.ID)
graph.checksumLock[img.ID] = &sync.Mutex{}
return nil
}
@ -175,7 +159,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *uti
if err != nil {
return nil, err
}
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root)
}
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
@ -193,8 +177,65 @@ func (graph *Graph) Mktemp(id string) (string, error) {
return tmp.imageRoot(id), nil
}
// getDockerInitLayer returns the path of a layer containing a mountpoint suitable
// for bind-mounting dockerinit into the container. The mountpoint is simply an
// empty file at /.dockerinit
//
// This extra layer is used by all containers as the top-most ro layer. It protects
// the container from unwanted side-effects on the rw layer.
func (graph *Graph) getDockerInitLayer() (string, error) {
tmp, err := graph.tmp()
if err != nil {
return "", err
}
initLayer := tmp.imageRoot("_dockerinit")
if err := os.Mkdir(initLayer, 0755); err != nil && !os.IsExist(err) {
// If directory already existed, keep going.
// For all other errors, abort.
return "", err
}
for pth, typ := range map[string]string{
"/dev/pts": "dir",
"/dev/shm": "dir",
"/proc": "dir",
"/sys": "dir",
"/.dockerinit": "file",
"/etc/resolv.conf": "file",
// "var/run": "dir",
// "var/lock": "dir",
} {
if _, err := os.Stat(path.Join(initLayer, pth)); err != nil {
if os.IsNotExist(err) {
switch typ {
case "dir":
if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil {
return "", err
}
case "file":
if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil {
return "", err
}
if f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755); err != nil {
return "", err
} else {
f.Close()
}
}
} else {
return "", err
}
}
}
// Layer is ready to use, if it wasn't before.
return initLayer, nil
}
func (graph *Graph) tmp() (*Graph, error) {
return NewGraph(path.Join(graph.Root, ":tmp:"))
// Changed to _tmp from :tmp:, because it messed with ":" separators in aufs branch syntax...
return NewGraph(path.Join(graph.Root, "_tmp"))
}
// Check if given error is "not empty".
@ -282,9 +323,9 @@ func (graph *Graph) ByParent() (map[string][]*Image, error) {
return
}
if children, exists := byParent[parent.ID]; exists {
byParent[parent.ID] = []*Image{image}
} else {
byParent[parent.ID] = append(children, image)
} else {
byParent[parent.ID] = []*Image{image}
}
})
return byParent, err
@ -311,40 +352,3 @@ func (graph *Graph) Heads() (map[string]*Image, error) {
func (graph *Graph) imageRoot(id string) string {
return path.Join(graph.Root, id)
}
func (graph *Graph) getStoredChecksums() (map[string]string, error) {
checksums := make(map[string]string)
// FIXME: Store the checksum in memory
if checksumDict, err := ioutil.ReadFile(path.Join(graph.Root, "checksums")); err == nil {
if err := json.Unmarshal(checksumDict, &checksums); err != nil {
return nil, err
}
}
return checksums, nil
}
func (graph *Graph) storeChecksums(checksums map[string]string) error {
checksumJSON, err := json.Marshal(checksums)
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(graph.Root, "checksums"), checksumJSON, 0600); err != nil {
return err
}
return nil
}
func (graph *Graph) UpdateChecksums(newChecksums map[string]*registry.ImgData) error {
graph.lockSumFile.Lock()
defer graph.lockSumFile.Unlock()
localChecksums, err := graph.getStoredChecksums()
if err != nil {
return err
}
for id, elem := range newChecksums {
localChecksums[id] = elem.Checksum
}
return graph.storeChecksums(localChecksums)
}

View file

@ -38,7 +38,7 @@ func TestInterruptedRegister(t *testing.T) {
Comment: "testing",
Created: time.Now(),
}
go graph.Register(badArchive, false, image)
go graph.Register(nil, badArchive, image)
time.Sleep(200 * time.Millisecond)
w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
if _, err := graph.Get(image.ID); err == nil {
@ -49,7 +49,7 @@ func TestInterruptedRegister(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := graph.Register(goodArchive, false, image); err != nil {
if err := graph.Register(nil, goodArchive, image); err != nil {
t.Fatal(err)
}
}
@ -95,7 +95,7 @@ func TestRegister(t *testing.T) {
Comment: "testing",
Created: time.Now(),
}
err = graph.Register(archive, false, image)
err = graph.Register(nil, archive, image)
if err != nil {
t.Fatal(err)
}
@ -225,7 +225,7 @@ func TestDelete(t *testing.T) {
t.Fatal(err)
}
// Test delete twice (pull -> rm -> pull -> rm)
if err := graph.Register(archive, false, img1); err != nil {
if err := graph.Register(nil, archive, img1); err != nil {
t.Fatal(err)
}
if err := graph.Delete(img1.ID); err != nil {
@ -234,6 +234,45 @@ func TestDelete(t *testing.T) {
assertNImages(graph, t, 1)
}
func TestByParent(t *testing.T) {
archive1, _ := fakeTar()
archive2, _ := fakeTar()
archive3, _ := fakeTar()
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
parentImage := &Image{
ID: GenerateID(),
Comment: "parent",
Created: time.Now(),
Parent: "",
}
childImage1 := &Image{
ID: GenerateID(),
Comment: "child1",
Created: time.Now(),
Parent: parentImage.ID,
}
childImage2 := &Image{
ID: GenerateID(),
Comment: "child2",
Created: time.Now(),
Parent: parentImage.ID,
}
_ = graph.Register(nil, archive1, parentImage)
_ = graph.Register(nil, archive2, childImage1)
_ = graph.Register(nil, archive3, childImage2)
byParent, err := graph.ByParent()
if err != nil {
t.Fatal(err)
}
numChildren := len(byParent[parentImage.ID])
if numChildren != 2 {
t.Fatalf("Expected 2 children, found %d", numChildren)
}
}
func assertNImages(graph *Graph, t *testing.T, n int) {
if images, err := graph.All(); err != nil {
t.Fatal(err)

View file

@ -1,133 +0,0 @@
## A maintainer's guide to releasing Docker
So you're in charge of a docker release? Cool. Here's what to do.
If your experience deviates from this document, please document the changes to keep it
up-to-date.
### 1. Pull from master and create a release branch
```bash
$ git checkout master
$ git pull
$ git checkout -b bump_$VERSION
```
### 2. Update CHANGELOG.md
You can run this command for reference:
```bash
LAST_VERSION=$(git tag | grep -E "v[0-9\.]+$" | sort -nr | head -n 1)
git log $LAST_VERSION..HEAD
```
Each change should be formatted as ```BULLET CATEGORY: DESCRIPTION```
* BULLET is either ```-```, ```+``` or ```*```, to indicate a bugfix,
new feature or upgrade, respectively.
* CATEGORY should describe which part of the project is affected.
Valid categories are:
* Builder
* Documentation
* Hack
* Packaging
* Remote API
* Runtime
* DESCRIPTION: a concise description of the change that is relevant to the end-user,
using the present tense.
Changes should be described in terms of how they affect the user, for example "new feature
X which allows Y", "fixed bug which caused X", "increased performance of Y".
EXAMPLES:
```
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
* Runtime: improve detection of kernel version
- Remote API: fix a bug in the optional unix socket transport
```
### 3. Change VERSION in commands.go
### 4. Run all tests
```bash
$ make test
```
### 5. Commit and create a pull request
```bash
$ git add commands.go CHANGELOG.md
$ git commit -m "Bump version to $VERSION"
$ git push origin bump_$VERSION
```
### 6. Get 2 other maintainers to validate the pull request
### 7. Merge the pull request and apply tags
```bash
$ git checkout master
$ git merge bump_$VERSION
$ git tag -a v$VERSION # Don't forget the v!
$ git tag -f -a latest
$ git push
$ git push --tags
```
### 8. Publish binaries
To run this you will need access to the release credentials.
Get them from [the infrastructure maintainers](https://github.com/dotcloud/docker/blob/master/hack/infrastructure/MAINTAINERS).
```bash
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
$ BUILD=$(docker run -d -e RELEASE_PPA=0 $RELEASE_IMAGE)
```
This will do 2 things:
* It will build and upload the binaries on http://get.docker.io
* It will *test* the release on our Ubuntu PPA (a PPA is a community repository for ubuntu packages)
Wait for the build to complete.
```bash
$ docker wait $BUILD # This should print 0. If it doesn't, your build failed.
```
Check that the output looks OK. Here's an example of a correct output:
```bash
$ docker logs 2>&1 b4e7c8299d73 | grep -e 'Public URL' -e 'Successfully uploaded'
Public URL of the object is: http://get.docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-v0.4.7.tgz
Public URL of the object is: http://get.docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-latest.tgz
Successfully uploaded packages.
```
If you don't see 3 lines similar to this, something might be wrong. Check the full logs and try again.
### 9. Publish Ubuntu packages
If everything went well in the previous step, you can finalize the release by submitting the Ubuntu
packages.
```bash
$ RELEASE_IMAGE=image_provided_by_infrastructure_maintainers
$ docker run -e RELEASE_PPA=1 $RELEASE_IMAGE
```
If that goes well, Ubuntu Precise package is in its way. It will take anywhere from 0.5 to 30 hours
for the builders to complete their job depending on builder demand at this time. At this point, Quantal
and Raring packages need to be created using the Launchpad interface:
https://launchpad.net/~dotcloud/+archive/lxc-docker/+packages
Notify [the packager maintainers](https://github.com/dotcloud/docker/blob/master/packaging/MAINTAINERS)
who will ensure PPA is ready.
Congratulations! You're done

View file

@ -1,36 +0,0 @@
# DESCRIPTION Build a container capable of producing official binary and
# PPA packages and uploading them to S3 and Launchpad
# VERSION 1.2
# DOCKER_VERSION 0.4
# AUTHOR Solomon Hykes <solomon@dotcloud.com>
# Daniel Mizyrycki <daniel@dotcloud.net>
# BUILD_CMD docker build -t dockerbuilder .
# RUN_CMD docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" -e PUBLISH_PPA="$PUBLISH_PPA" dockerbuilder
#
# ENV_VARIABLES AWS_ID, AWS_KEY: S3 credentials for uploading Docker binary and tarball
# GPG_KEY: Signing key for docker package
# PUBLISH_PPA: 0 for staging release, 1 for production release
#
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
# Workaround the upstart issue
run dpkg-divert --local --rename --add /sbin/initctl
run ln -s /bin/true /sbin/initctl
# Enable universe and gophers PPA
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
run apt-get update
# Packages required to checkout, build and upload docker
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
run tar -C /usr/local -xzf /go.tar.gz
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
# Packages required to build an ubuntu package
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
# Copy dockerbuilder files into the container
add . /src
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
cmd ["dockerbuilder"]

View file

@ -1 +0,0 @@
Daniel Mizyrycki <daniel@dotcloud.com>

View file

@ -1,42 +0,0 @@
#!/bin/sh
set -x
set -e
export PATH=/usr/local/go/bin:$PATH
PACKAGE=github.com/dotcloud/docker
if [ $# -gt 1 ]; then
echo "Usage: $0 [REVISION]"
exit 1
fi
export REVISION=$1
if [ -z "$AWS_ID" -o -z "$AWS_KEY" ]; then
echo "Warning: either AWS_ID or AWS_KEY environment variable not set. Won't upload to S3."
else
/bin/echo -e "[default]\naccess_key = $AWS_ID\nsecret_key = $AWS_KEY\n" > /.s3cfg
fi
if [ -z "$GPG_KEY" ]; then
echo "Warning: environment variable GPG_KEY is not set. Ubuntu package upload will not succeed."
NO_UBUNTU=1
fi
rm -fr docker-release
git clone https://github.com/dotcloud/docker docker-release
cd docker-release
if [ -z "$REVISION" ]; then
make release
else
make release RELEASE_VERSION=$REVISION
fi
# Remove credentials from container
rm -f /.s3cfg
if [ -z "$NO_UBUNTU" ]; then
export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
(cd packaging/ubuntu && make ubuntu)
fi

View file

@ -1,2 +1,2 @@
Ken Cochrane <ken@dotcloud.com>
Jerome Petazzoni <jerome@dotcloud.com>
Ken Cochrane <ken@dotcloud.com> (@kencochrane)
Jerome Petazzoni <jerome@dotcloud.com> (@jpetazzo)

106
hack/release/README.md Normal file
View file

@ -0,0 +1,106 @@
## A maintainer's guide to releasing Docker
So you're in charge of a Docker release? Cool. Here's what to do.
If your experience deviates from this document, please document the changes
to keep it up-to-date.
### 1. Pull from master and create a release branch
```bash
git checkout master
git pull
git checkout -b bump_$VERSION
```
### 2. Update CHANGELOG.md
You can run this command for reference:
```bash
LAST_VERSION=$(git tag | grep -E "v[0-9\.]+$" | sort -nr | head -n 1)
git log $LAST_VERSION..HEAD
```
Each change should be formatted as ```BULLET CATEGORY: DESCRIPTION```
* BULLET is either ```-```, ```+``` or ```*```, to indicate a bugfix,
new feature or upgrade, respectively.
* CATEGORY should describe which part of the project is affected.
Valid categories are:
* Builder
* Documentation
* Hack
* Packaging
* Remote API
* Runtime
* DESCRIPTION: a concise description of the change that is relevant to the
end-user, using the present tense. Changes should be described in terms
of how they affect the user, for example "new feature X which allows Y",
"fixed bug which caused X", "increased performance of Y".
EXAMPLES:
```
+ Builder: 'docker build -t FOO' applies the tag FOO to the newly built
container.
* Runtime: improve detection of kernel version
- Remote API: fix a bug in the optional unix socket transport
```
### 3. Change the contents of the VERSION file
### 4. Run all tests
```bash
go test
```
### 5. Commit and create a pull request
```bash
git add CHANGELOG.md
git commit -m "Bump version to $VERSION"
git push origin bump_$VERSION
```
### 6. Get 2 other maintainers to validate the pull request
### 7. Merge the pull request and apply tags
```bash
git checkout master
git merge bump_$VERSION
git tag -a v$VERSION # Don't forget the v!
git tag -f -a latest
git push
git push --tags
```
### 8. Publish binaries
To run this you will need access to the release credentials.
Get them from [the infrastructure maintainers](
https://github.com/dotcloud/docker/blob/master/hack/infrastructure/MAINTAINERS).
```bash
docker build -t releasedocker .
docker run \
-e AWS_S3_BUCKET=get-nightly.docker.io \
-e AWS_ACCESS_KEY=$(cat ~/.aws/access_key) \
-e AWS_SECRET_KEY=$(cat ~/.aws/secret_key) \
-e GPG_PASSPHRASE=supersecretsesame \
releasedocker
```
It will build and upload the binaries on the specified bucket (you should
use get-nightly.docker.io for general testing, and once everything is fine,
switch to get.docker.io).
### 9. Rejoice!
Congratulations! You're done.

177
hack/release/make.sh Executable file
View file

@ -0,0 +1,177 @@
#!/bin/sh
# This script builds various binary artifacts from a checkout of the docker
# source code.
#
# Requirements:
# - The current directory should be a checkout of the docker source code
# (http://github.com/dotcloud/docker). Whatever version is checked out
# will be built.
# - The VERSION file, at the root of the repository, should exist, and
# will be used as Docker binary version and package version.
# - The hash of the git commit will also be included in the Docker binary,
# with the suffix -dirty if the repository isn't clean.
# - The script is intented to be run as part of a docker build, as defined
# in the Dockerfile at the root of the source. In other words:
# DO NOT CALL THIS SCRIPT DIRECTLY.
# - The right way to call this script is to invoke "docker build ." from
# your checkout of the Docker repository.
#
set -e
# We're a nice, sexy, little shell script, and people might try to run us;
# but really, they shouldn't. We want to be in a container!
RESOLVCONF=$(readlink --canonicalize /etc/resolv.conf)
grep -q "$RESOLVCONF" /proc/mounts || {
echo "# I will only run within a container."
echo "# Try this instead:"
echo "docker build ."
exit 1
}
VERSION=$(cat ./VERSION)
PKGVERSION="$VERSION"
GITCOMMIT=$(git rev-parse --short HEAD)
if test -n "$(git status --porcelain)"
then
GITCOMMIT="$GITCOMMIT-dirty"
PKGVERSION="$PKGVERSION-$(date +%Y%m%d%H%M%S)-$GITCOMMIT"
fi
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
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.
Docker is a great building block for automating distributed systems:
large-scale web deployments, database clusters, continuous deployment systems,
private PaaS, service-oriented architectures, etc."
UPSTART_SCRIPT='description "Docker daemon"
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
respawn
exec docker -d
'
# Each "bundle" is a different type of build artefact: static binary, Ubuntu
# package, etc.
# Build Docker as a static binary file
bundle_binary() {
mkdir -p bundles/$VERSION/binary
go build -o bundles/$VERSION/binary/docker-$VERSION \
-ldflags "-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" \
./docker
}
# Build Docker's test suite as a collection of binary files (one per
# sub-package to test)
bundle_test() {
mkdir -p bundles/$VERSION/test
for test_dir in $(find_test_dirs); do
test_binary=$(
cd $test_dir
go test -c -v -ldflags "-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" >&2
find . -maxdepth 1 -type f -name '*.test' -executable
)
cp $test_dir/$test_binary bundles/$VERSION/test/
done
}
# Build docker as an ubuntu package using FPM and REPREPRO (sue me).
# bundle_binary must be called first.
bundle_ubuntu() {
mkdir -p bundles/$VERSION/ubuntu
DIR=$(pwd)/bundles/$VERSION/ubuntu/build
# Generate an upstart config file (ubuntu-specific)
mkdir -p $DIR/etc/init
echo "$UPSTART_SCRIPT" > $DIR/etc/init/docker.conf
# Copy the binary
mkdir -p $DIR/usr/bin
cp bundles/$VERSION/binary/docker-$VERSION $DIR/usr/bin/docker
# Generate postinstall/prerm scripts
cat >/tmp/postinstall <<EOF
#!/bin/sh
/sbin/stop docker || true
/sbin/start docker
EOF
cat >/tmp/prerm <<EOF
#!/bin/sh
/sbin/stop docker || true
EOF
chmod +x /tmp/postinstall /tmp/prerm
(
cd bundles/$VERSION/ubuntu
fpm -s dir -C $DIR \
--name lxc-docker-$VERSION --version $PKGVERSION \
--after-install /tmp/postinstall \
--before-remove /tmp/prerm \
--architecture "$PACKAGE_ARCHITECTURE" \
--prefix / \
--depends lxc --depends aufs-tools \
--description "$PACKAGE_DESCRIPTION" \
--maintainer "$PACKAGE_MAINTAINER" \
--conflicts lxc-docker-virtual-package \
--provides lxc-docker \
--provides lxc-docker-virtual-package \
--replaces lxc-docker \
--replaces lxc-docker-virtual-package \
--url "$PACKAGE_URL" \
--vendor "$PACKAGE_VENDOR" \
-t deb .
mkdir empty
fpm -s dir -C empty \
--name lxc-docker --version $PKGVERSION \
--architecture "$PACKAGE_ARCHITECTURE" \
--depends lxc-docker-$VERSION \
--description "$PACKAGE_DESCRIPTION" \
--maintainer "$PACKAGE_MAINTAINER" \
--url "$PACKAGE_URL" \
--vendor "$PACKAGE_VENDOR" \
-t deb .
)
}
# This helper function walks the current directory looking for directories
# holding Go test files, and prints their paths on standard output, one per
# line.
find_test_dirs() {
find . -name '*_test.go' |
{ while read f; do dirname $f; done; } |
sort -u
}
main() {
bundle_binary
bundle_ubuntu
#bundle_test
cat <<EOF
###############################################################################
Now run the resulting image, making sure that you set AWS_S3_BUCKET,
AWS_ACCESS_KEY, and AWS_SECRET_KEY environment variables:
docker run -e AWS_S3_BUCKET=get-staging.docker.io \\
AWS_ACCESS_KEY=AKI1234... \\
AWS_SECRET_KEY=sEs3mE... \\
GPG_PASSPHRASE=sesame... \\
image_id_or_name
###############################################################################
EOF
}
main

175
hack/release/release.sh Executable file
View file

@ -0,0 +1,175 @@
#!/bin/sh
# This script looks for bundles built by make.sh, and releases them on a
# public S3 bucket.
#
# Bundles should be available for the VERSION string passed as argument.
#
# The correct way to call this script is inside a container built by the
# official Dockerfile at the root of the Docker source code. The Dockerfile,
# make.sh and release.sh should all be from the same source code revision.
set -e
# Print a usage message and exit.
usage() {
cat <<EOF
To run, I need:
- to be in a container generated by the Dockerfile at the top of the Docker
repository;
- to be provided with the name of an S3 bucket, in environment variable
AWS_S3_BUCKET;
- to be provided with AWS credentials for this S3 bucket, in environment
variables AWS_ACCESS_KEY and AWS_SECRET_KEY;
- the passphrase to unlock the GPG key which will sign the deb packages
(passed as environment variable GPG_PASSPHRASE);
- a generous amount of good will and nice manners.
The canonical way to run me is to run the image produced by the Dockerfile: e.g.:"
docker run -e AWS_S3_BUCKET=get-staging.docker.io \\
AWS_ACCESS_KEY=AKI1234... \\
AWS_SECRET_KEY=sEs4mE... \\
GPG_PASSPHRASE=m0resEs4mE... \\
f0058411
EOF
exit 1
}
[ "$AWS_S3_BUCKET" ] || usage
[ "$AWS_ACCESS_KEY" ] || usage
[ "$AWS_SECRET_KEY" ] || usage
[ "$GPG_PASSPHRASE" ] || usage
[ -d /go/src/github.com/dotcloud/docker/ ] || usage
cd /go/src/github.com/dotcloud/docker/
VERSION=$(cat VERSION)
BUCKET=$AWS_S3_BUCKET
setup_s3() {
# Try creating the bucket. Ignore errors (it might already exist).
s3cmd mb s3://$BUCKET 2>/dev/null || true
# Check access to the bucket.
# s3cmd has no useful exit status, so we cannot check that.
# Instead, we check if it outputs anything on standard output.
# (When there are problems, it uses standard error instead.)
s3cmd info s3://$BUCKET | grep -q .
# Make the bucket accessible through website endpoints.
s3cmd ws-create --ws-index index --ws-error error s3://$BUCKET
}
# write_to_s3 uploads the contents of standard input to the specified S3 url.
write_to_s3() {
DEST=$1
F=`mktemp`
cat > $F
s3cmd --acl-public put $F $DEST
rm -f $F
}
s3_url() {
echo "http://$BUCKET.s3.amazonaws.com"
}
# 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
release_ubuntu() {
# Make sure that we have our keys
mkdir -p /.gnupg/
s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ /.gnupg/ || true
gpg --list-keys releasedocker >/dev/null || {
gpg --gen-key --batch <<EOF
Key-Type: RSA
Key-Length: 2048
Passphrase: $GPG_PASSPHRASE
Name-Real: Docker Release Tool
Name-Email: docker@dotcloud.com
Name-Comment: releasedocker
Expire-Date: 0
%commit
EOF
}
# Sign our packages
dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \
--sign builder bundles/$VERSION/ubuntu/*.deb
# Setup the APT repo
APTDIR=bundles/$VERSION/ubuntu/apt
mkdir -p $APTDIR/conf $APTDIR/db
s3cmd sync s3://$BUCKET/ubuntu/db/ $APTDIR/db/ || true
cat > $APTDIR/conf/distributions <<EOF
Codename: docker
Components: main
Architectures: amd64 i386
EOF
# Add the DEB package to the APT repo
DEBFILE=bundles/$VERSION/ubuntu/lxc-docker*.deb
reprepro -b $APTDIR includedeb docker $DEBFILE
# Sign
for F in $(find $APTDIR -name Release)
do
gpg -u releasedocker --passphrase $GPG_PASSPHRASE \
--armor --sign --detach-sign \
--output $F.gpg $F
done
# Upload keys
s3cmd sync /.gnupg/ s3://$BUCKET/ubuntu/.gnupg/
gpg --armor --export releasedocker > bundles/$VERSION/ubuntu/gpg
s3cmd --acl-public put bundles/$VERSION/ubuntu/gpg s3://$BUCKET/gpg
# Upload repo
s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/
cat <<EOF | write_to_s3 s3://$BUCKET/ubuntu/info
# Add the repository to your APT sources
echo deb $(s3_url $BUCKET)/ubuntu docker main > /etc/apt/sources.list.d/docker.list
# Then import the repository key
curl $(s3_url $BUCKET)/gpg | apt-key add -
# Install docker
apt-get update ; apt-get install -y lxc-docker
EOF
echo "APT repository uploaded. Instructions available at $(s3_url $BUCKET)/ubuntu/info"
}
# Upload a static binary to S3
release_binary() {
[ -e bundles/$VERSION ]
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
# To install, run the following command as root:
curl -O http://$BUCKET.s3.amazonaws.com/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
if [ -z "$NOLATEST" ]; then
echo "Copying docker-$VERSION to docker-latest"
s3cmd --acl-public cp $S3DIR/docker-$VERSION $S3DIR/docker-latest
echo "Advertising $VERSION on $BUCKET as most recent version"
echo $VERSION | write_to_s3 s3://$BUCKET/latest
fi
}
# Upload the index script
release_index() {
(
if [ "$BUCKET" != "get.docker.io" ]
then
sed s,https://get.docker.io/,http://$BUCKET.s3.amazonaws.com/, contrib/install.sh
else
cat contrib/install.sh
fi
) | write_to_s3 s3://$BUCKET/index
}
main() {
setup_s3
release_binary
release_ubuntu
release_index
}
main

156
image.go
View file

@ -2,7 +2,6 @@ package docker
import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
@ -14,6 +13,7 @@ import (
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
)
@ -47,6 +47,19 @@ func LoadImage(root string) (*Image, error) {
if err := ValidateID(img.ID); err != nil {
return nil, err
}
if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
if size, err := strconv.Atoi(string(buf)); err != nil {
return nil, err
} else {
img.Size = int64(size)
}
}
// Check that the filesystem layer exists
if stat, err := os.Stat(layerPath(root)); err != nil {
if os.IsNotExist(err) {
@ -59,7 +72,7 @@ func LoadImage(root string) (*Image, error) {
return img, nil
}
func StoreImage(img *Image, layerData Archive, root string, store bool) error {
func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) error {
// Check that root doesn't already exist
if _, err := os.Stat(root); err == nil {
return fmt.Errorf("Image %s already exists", img.ID)
@ -72,26 +85,6 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
return err
}
if store {
layerArchive := layerArchivePath(root)
file, err := os.OpenFile(layerArchive, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
// FIXME: Retrieve the image layer size from here?
if _, err := io.Copy(file, layerData); err != nil {
return err
}
// FIXME: Don't close/open, read/write instead of Copy
file.Close()
file, err = os.Open(layerArchive)
if err != nil {
return err
}
defer file.Close()
layerData = file
}
// If layerData is not nil, unpack it into the new layer
if layerData != nil {
start := time.Now()
@ -102,25 +95,36 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
}
// If raw json is provided, then use it
if jsonData != nil {
return ioutil.WriteFile(jsonPath(root), jsonData, 0600)
} else { // Otherwise, unmarshal the image
jsonData, err := json.Marshal(img)
if err != nil {
return err
}
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
return err
}
}
return StoreSize(img, root)
}
func StoreSize(img *Image, root string) error {
layer := layerPath(root)
var totalSize int64 = 0
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
img.Size += fileInfo.Size()
totalSize += fileInfo.Size()
return nil
})
img.Size = totalSize
// Store the json ball
jsonData, err := json.Marshal(img)
if err != nil {
return err
}
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
return err
if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil {
return nil
}
return nil
}
@ -128,10 +132,6 @@ func layerPath(root string) string {
return path.Join(root, "layer")
}
func layerArchivePath(root string) string {
return path.Join(root, "layer.tar.xz")
}
func jsonPath(root string) string {
return path.Join(root, "json")
}
@ -263,6 +263,13 @@ func (img *Image) layers() ([]string, error) {
if len(list) == 0 {
return nil, fmt.Errorf("No layer found for image %s\n", img.ID)
}
// Inject the dockerinit layer (empty place-holder for mount-binding dockerinit)
if dockerinitLayer, err := img.getDockerInitLayer(); err != nil {
return nil, err
} else {
list = append([]string{dockerinitLayer}, list...)
}
return list, nil
}
@ -292,6 +299,13 @@ func (img *Image) GetParent() (*Image, error) {
return img.graph.Get(img.Parent)
}
func (img *Image) getDockerInitLayer() (string, error) {
if img.graph == nil {
return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image")
}
return img.graph.getDockerInitLayer()
}
func (img *Image) root() (string, error) {
if img.graph == nil {
return "", fmt.Errorf("Can't lookup root of unregistered image")
@ -308,80 +322,6 @@ func (img *Image) layer() (string, error) {
return layerPath(root), nil
}
func (img *Image) Checksum() (string, error) {
img.graph.checksumLock[img.ID].Lock()
defer img.graph.checksumLock[img.ID].Unlock()
root, err := img.root()
if err != nil {
return "", err
}
checksums, err := img.graph.getStoredChecksums()
if err != nil {
return "", err
}
if checksum, ok := checksums[img.ID]; ok {
return checksum, nil
}
layer, err := img.layer()
if err != nil {
return "", err
}
jsonData, err := ioutil.ReadFile(jsonPath(root))
if err != nil {
return "", err
}
var layerData io.Reader
if file, err := os.Open(layerArchivePath(root)); err != nil {
if os.IsNotExist(err) {
layerData, err = Tar(layer, Xz)
if err != nil {
return "", err
}
} else {
return "", err
}
} else {
defer file.Close()
layerData = file
}
h := sha256.New()
if _, err := h.Write(jsonData); err != nil {
return "", err
}
if _, err := h.Write([]byte("\n")); err != nil {
return "", err
}
if _, err := io.Copy(h, layerData); err != nil {
return "", err
}
hash := "sha256:" + hex.EncodeToString(h.Sum(nil))
// Reload the json file to make sure not to overwrite faster sums
img.graph.lockSumFile.Lock()
defer img.graph.lockSumFile.Unlock()
checksums, err = img.graph.getStoredChecksums()
if err != nil {
return "", err
}
checksums[img.ID] = hash
// Dump the checksums to disc
if err := img.graph.storeChecksums(checksums); err != nil {
return hash, err
}
return hash, nil
}
func (img *Image) getParentsSize(size int64) int64 {
parentImage, err := img.GetParent()
if err != nil || parentImage == nil {

1
library/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Joffrey Fuhrer <joffrey@dotcloud.com> (@shin-)

1
library/busybox Normal file
View file

@ -0,0 +1 @@
latest git://github.com/dotcloud/docker-busybox

Some files were not shown because too many files have changed in this diff Show more