diff --git a/.gitignore b/.gitignore index d557404214..686ac83428 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .vagrant docker/docker -dockerd/dockerd .*.swp a.out *.orig diff --git a/README.md b/README.md index abfd268ad9..07449fa3fa 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,37 @@ Under the hood, Docker is built on the following components: * [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers. -Setup instructions +Install instructions ================== -Requirements ------------- +Installing on Ubuntu 12.04 and 12.10 +------------------------------------ + +1. Install dependencies: + +```bash + sudo apt-get install lxc wget bsdtar curl +``` + +2. Install the latest docker binary: + +```bash + wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz + tar -xf docker-master.tgz +``` + +3. Run your first container! + +```bash + cd docker-master + sudo ./docker import base + sudo ./docker run -a -i -t base /bin/bash +``` + +Consider adding docker to your `PATH` for simplicity. + +Installing on other Linux distributions +--------------------------------------- Right now, the officially supported distributions are: @@ -64,27 +90,68 @@ Right now, the officially supported distributions are: Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested. -Installation ---------------- +Usage examples +============== -1. Set up your host of choice on a physical / virtual machine -2. Assume root identity on your newly installed environment (`sudo -s`) -3. Type the following commands: +Running an interactive shell +---------------------------- - apt-get install lxc wget bsdtar curl +```bash + # Download a base image + docker import base -4. Download the latest docker binaries: `wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz` ([Or get the Linux/x86_64 binaries here](http://get.docker.io/builds/Linux/x86_64/docker-master.tgz) ) -5. Extract the contents of the tar file `tar -xf docker-master.tar.gz` -6. Launch the docker daemon in the background `./dockerd &` -7. Download a base image `./docker pull base` -8. Run your first container! `./docker run -i -a -t base /bin/bash` -9. Start exploring `./docker --help` + # Run an interactive shell in the base image, + # allocate a tty, attach stdin and stdout + docker run -a -i -t base /bin/bash +``` -Consider adding docker and dockerd to your `PATH` for simplicity. + +Starting a long-running worker process +-------------------------------------- + +```bash + # Run docker in daemon mode + (docker -d || echo "Docker daemon already running") & + + # Start a very useful long-running process + JOB=$(docker run /bin/sh -c "while true; do echo Hello world!; sleep 1; done") + + # Collect the output of the job so far + docker logs $JOB + + # Kill the job + docker kill $JOB +``` + + +Listing all running containers +------------------------------ + +```bash + docker ps +``` + + +Expose a service on a TCP port +------------------------------ + +```bash + # Expose port 4444 of this container, and tell netcat to listen on it + JOB=$(docker run -p 4444 base /bin/nc -l -p 4444) + + # Which public port is NATed to my container? + PORT=$(docker port $JOB 4444) + + # Connect to the public port via the host's public address + echo hello world | nc $(hostname) $PORT + + # Verify that the network connection worked + echo "Daemon received: $(docker logs $JOB)" +``` What is a Standard Container? ------------------------------ +============================= Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container. diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 814aed2f18..0000000000 --- a/client/client.go +++ /dev/null @@ -1,126 +0,0 @@ -package client - -import ( - "github.com/dotcloud/docker/future" - "github.com/dotcloud/docker/rcli" - "io" - "io/ioutil" - "log" - "os" - "os/exec" - "path" - "path/filepath" -) - -// Run docker in "simple mode": run a single command and return. -func SimpleMode(args []string) error { - var oldState *State - var err error - if IsTerminal(0) && os.Getenv("NORAW") == "" { - oldState, err = MakeRaw(0) - if err != nil { - return err - } - defer Restore(0, oldState) - } - // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose - // CloseWrite(), which we need to cleanly signal that stdin is closed without - // closing the connection. - // See http://code.google.com/p/go/issues/detail?id=3345 - conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...) - if err != nil { - return err - } - receive_stdout := future.Go(func() error { - _, err := io.Copy(os.Stdout, conn) - return err - }) - send_stdin := future.Go(func() error { - _, err := io.Copy(conn, os.Stdin) - if err := conn.CloseWrite(); err != nil { - log.Printf("Couldn't send EOF: " + err.Error()) - } - return err - }) - if err := <-receive_stdout; err != nil { - return err - } - if oldState != nil { - Restore(0, oldState) - } - if !IsTerminal(0) { - if err := <-send_stdin; err != nil { - return err - } - } - return nil -} - -// Run docker in "interactive mode": run a bash-compatible shell capable of running docker commands. -func InteractiveMode(scripts ...string) error { - // Determine path of current docker binary - dockerPath, err := exec.LookPath(os.Args[0]) - if err != nil { - return err - } - dockerPath, err = filepath.Abs(dockerPath) - if err != nil { - return err - } - - // Create a temp directory - tmp, err := ioutil.TempDir("", "docker-shell") - if err != nil { - return err - } - defer os.RemoveAll(tmp) - - // For each command, create an alias in temp directory - // FIXME: generate this list dynamically with introspection of some sort - // It might make sense to merge docker and dockerd to keep that introspection - // within a single binary. - for _, cmd := range []string{ - "help", - "run", - "ps", - "pull", - "put", - "rm", - "kill", - "wait", - "stop", - "start", - "restart", - "logs", - "diff", - "commit", - "attach", - "info", - "tar", - "web", - "images", - "docker", - } { - if err := os.Symlink(dockerPath, path.Join(tmp, cmd)); err != nil { - return err - } - } - - // Run $SHELL with PATH set to temp directory - rcfile, err := ioutil.TempFile("", "docker-shell-rc") - if err != nil { - return err - } - defer os.Remove(rcfile.Name()) - io.WriteString(rcfile, "enable -n help\n") - os.Setenv("PATH", tmp+":"+os.Getenv("PATH")) - os.Setenv("PS1", "\\h docker> ") - shell := exec.Command("/bin/bash", append([]string{"--rcfile", rcfile.Name()}, scripts...)...) - shell.Stdin = os.Stdin - shell.Stdout = os.Stdout - shell.Stderr = os.Stderr - if err := shell.Run(); err != nil { - return err - } - return nil -} diff --git a/server/server.go b/commands/commands.go similarity index 98% rename from server/server.go rename to commands/commands.go index 07b462d099..f8c97cdff0 100644 --- a/server/server.go +++ b/commands/commands.go @@ -1,4 +1,4 @@ -package server +package commands import ( "bufio" @@ -24,15 +24,6 @@ import ( const VERSION = "0.0.1" -func (srv *Server) ListenAndServe() error { - go rcli.ListenAndServeHTTP("127.0.0.1:8080", srv) - // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose - // CloseWrite(), which we need to cleanly signal that stdin is closed without - // closing the connection. - // See http://code.google.com/p/go/issues/detail?id=3345 - return rcli.ListenAndServe("tcp", "127.0.0.1:4242", srv) -} - func (srv *Server) Name() string { return "docker" } diff --git a/docker/docker.go b/docker/docker.go index fa9011defa..ad121a8f60 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -2,28 +2,92 @@ package main import ( "flag" - "github.com/dotcloud/docker/client" + "github.com/dotcloud/docker" + "github.com/dotcloud/docker/commands" + "github.com/dotcloud/docker/future" + "github.com/dotcloud/docker/rcli" + "github.com/dotcloud/docker/term" + "io" "log" "os" - "path" ) func main() { - if cmd := path.Base(os.Args[0]); cmd == "docker" { - fl_shell := flag.Bool("i", false, "Interactive mode") - flag.Parse() - if *fl_shell { - if err := client.InteractiveMode(flag.Args()...); err != nil { - log.Fatal(err) - } - } else { - if err := client.SimpleMode(os.Args[1:]); err != nil { - log.Fatal(err) - } + if docker.SelfPath() == "/sbin/init" { + // Running in init mode + docker.SysInit() + return + } + fl_daemon := flag.Bool("d", false, "Daemon mode") + flag.Parse() + if *fl_daemon { + if flag.NArg() != 0 { + flag.Usage() + return + } + if err := daemon(); err != nil { + log.Fatal(err) } } else { - if err := client.SimpleMode(append([]string{cmd}, os.Args[1:]...)); err != nil { + if err := runCommand(flag.Args()); err != nil { log.Fatal(err) } } } + +func daemon() error { + service, err := commands.New() + if err != nil { + return err + } + return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service) +} + +func runCommand(args []string) error { + var oldState *term.State + var err error + if term.IsTerminal(0) && os.Getenv("NORAW") == "" { + oldState, err = term.MakeRaw(0) + if err != nil { + return err + } + defer term.Restore(0, oldState) + } + // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose + // CloseWrite(), which we need to cleanly signal that stdin is closed without + // closing the connection. + // See http://code.google.com/p/go/issues/detail?id=3345 + if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { + receive_stdout := future.Go(func() error { + _, err := io.Copy(os.Stdout, conn) + return err + }) + send_stdin := future.Go(func() error { + _, err := io.Copy(conn, os.Stdin) + if err := conn.CloseWrite(); err != nil { + log.Printf("Couldn't send EOF: " + err.Error()) + } + return err + }) + if err := <-receive_stdout; err != nil { + return err + } + if !term.IsTerminal(0) { + if err := <-send_stdin; err != nil { + return err + } + } + } else { + service, err := commands.New() + if err != nil { + return err + } + if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil { + return err + } + } + if oldState != nil { + term.Restore(0, oldState) + } + return nil +} diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go deleted file mode 100644 index b2337d342c..0000000000 --- a/dockerd/dockerd.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "flag" - "github.com/dotcloud/docker" - "github.com/dotcloud/docker/server" - "log" -) - -func main() { - if docker.SelfPath() == "/sbin/init" { - // Running in init mode - docker.SysInit() - return - } - flag.Parse() - d, err := server.New() - if err != nil { - log.Fatal(err) - } - if err := d.ListenAndServe(); err != nil { - log.Fatal(err) - } -} diff --git a/rcli/types.go b/rcli/types.go index 52079291b6..2600fe240d 100644 --- a/rcli/types.go +++ b/rcli/types.go @@ -25,7 +25,12 @@ type Service interface { type Cmd func(io.ReadCloser, io.Writer, ...string) error type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error +// FIXME: For reverse compatibility func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { + return LocalCall(service, stdin, stdout, args...) +} + +func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { if len(args) == 0 { args = []string{"help"} } diff --git a/client/term.go b/term/term.go similarity index 99% rename from client/term.go rename to term/term.go index a988d0d796..fdbe3b9984 100644 --- a/client/term.go +++ b/term/term.go @@ -1,4 +1,4 @@ -package client +package term import ( "syscall" diff --git a/client/termios_darwin.go b/term/termios_darwin.go similarity index 85% rename from client/termios_darwin.go rename to term/termios_darwin.go index 185687920c..26d17b3bb8 100644 --- a/client/termios_darwin.go +++ b/term/termios_darwin.go @@ -1,4 +1,4 @@ -package client +package term import "syscall" diff --git a/client/termios_linux.go b/term/termios_linux.go similarity index 85% rename from client/termios_linux.go rename to term/termios_linux.go index 36957c44a1..1ea26fa5c4 100644 --- a/client/termios_linux.go +++ b/term/termios_linux.go @@ -1,4 +1,4 @@ -package client +package term import "syscall"