This makes it possible to simply wrap a command inside a container. This makes
it easier to use a container as an unified build environment.
Examples:
~/workspace/docker
$ docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu ls
AUTHORS Makefile archive.go changes.go docker
[...]
docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd
/home/marco/workspace/docker
| ... | ... |
@@ -90,6 +90,69 @@ func TestRunHostname(t *testing.T) {
|
| 90 | 90 |
|
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 |
+// TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory |
|
| 94 |
+func TestRunWorkdir(t *testing.T) {
|
|
| 95 |
+ stdout, stdoutPipe := io.Pipe() |
|
| 96 |
+ |
|
| 97 |
+ cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) |
|
| 98 |
+ defer cleanup(globalRuntime) |
|
| 99 |
+ |
|
| 100 |
+ c := make(chan struct{})
|
|
| 101 |
+ go func() {
|
|
| 102 |
+ defer close(c) |
|
| 103 |
+ if err := cli.CmdRun("-w", "/foo/bar", unitTestImageID, "pwd"); err != nil {
|
|
| 104 |
+ t.Fatal(err) |
|
| 105 |
+ } |
|
| 106 |
+ }() |
|
| 107 |
+ |
|
| 108 |
+ setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
|
| 109 |
+ cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
|
| 110 |
+ if err != nil {
|
|
| 111 |
+ t.Fatal(err) |
|
| 112 |
+ } |
|
| 113 |
+ if cmdOutput != "/foo/bar\n" {
|
|
| 114 |
+ t.Fatalf("'pwd' should display '%s', not '%s'", "/foo/bar\n", cmdOutput)
|
|
| 115 |
+ } |
|
| 116 |
+ }) |
|
| 117 |
+ |
|
| 118 |
+ setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
|
| 119 |
+ <-c |
|
| 120 |
+ }) |
|
| 121 |
+ |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+// TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists |
|
| 125 |
+func TestRunWorkdirExists(t *testing.T) {
|
|
| 126 |
+ stdout, stdoutPipe := io.Pipe() |
|
| 127 |
+ |
|
| 128 |
+ cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) |
|
| 129 |
+ defer cleanup(globalRuntime) |
|
| 130 |
+ |
|
| 131 |
+ c := make(chan struct{})
|
|
| 132 |
+ go func() {
|
|
| 133 |
+ defer close(c) |
|
| 134 |
+ if err := cli.CmdRun("-w", "/proc", unitTestImageID, "pwd"); err != nil {
|
|
| 135 |
+ t.Fatal(err) |
|
| 136 |
+ } |
|
| 137 |
+ }() |
|
| 138 |
+ |
|
| 139 |
+ setTimeout(t, "Reading command output time out", 2*time.Second, func() {
|
|
| 140 |
+ cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
|
| 141 |
+ if err != nil {
|
|
| 142 |
+ t.Fatal(err) |
|
| 143 |
+ } |
|
| 144 |
+ if cmdOutput != "/proc\n" {
|
|
| 145 |
+ t.Fatalf("'pwd' should display '%s', not '%s'", "/proc\n", cmdOutput)
|
|
| 146 |
+ } |
|
| 147 |
+ }) |
|
| 148 |
+ |
|
| 149 |
+ setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
|
|
| 150 |
+ <-c |
|
| 151 |
+ }) |
|
| 152 |
+ |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+ |
|
| 93 | 156 |
func TestRunExit(t *testing.T) {
|
| 94 | 157 |
stdin, stdinPipe := io.Pipe() |
| 95 | 158 |
stdout, stdoutPipe := io.Pipe() |
| ... | ... |
@@ -2,6 +2,7 @@ package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"encoding/json" |
| 5 |
+ "errors" |
|
| 5 | 6 |
"flag" |
| 6 | 7 |
"fmt" |
| 7 | 8 |
"github.com/dotcloud/docker/term" |
| ... | ... |
@@ -76,6 +77,7 @@ type Config struct {
|
| 76 | 76 |
Image string // Name of the image as it was passed by the operator (eg. could be symbolic) |
| 77 | 77 |
Volumes map[string]struct{}
|
| 78 | 78 |
VolumesFrom string |
| 79 |
+ WorkingDir string |
|
| 79 | 80 |
Entrypoint []string |
| 80 | 81 |
NetworkDisabled bool |
| 81 | 82 |
Privileged bool |
| ... | ... |
@@ -92,6 +94,10 @@ type BindMap struct {
|
| 92 | 92 |
Mode string |
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
+var ( |
|
| 96 |
+ ErrInvaidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.")
|
|
| 97 |
+) |
|
| 98 |
+ |
|
| 95 | 99 |
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
| 96 | 100 |
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
| 97 | 101 |
if len(args) > 0 && args[0] != "--help" {
|
| ... | ... |
@@ -100,6 +106,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
| 100 | 100 |
} |
| 101 | 101 |
|
| 102 | 102 |
flHostname := cmd.String("h", "", "Container host name")
|
| 103 |
+ flWorkingDir := cmd.String("w", "", "Working directory inside the container")
|
|
| 103 | 104 |
flUser := cmd.String("u", "", "Username or UID")
|
| 104 | 105 |
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
| 105 | 106 |
flAttach := NewAttachOpts() |
| ... | ... |
@@ -139,6 +146,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
| 139 | 139 |
if *flDetach && len(flAttach) > 0 {
|
| 140 | 140 |
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
|
| 141 | 141 |
} |
| 142 |
+ if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
|
| 143 |
+ return nil, nil, cmd, ErrInvaidWorikingDirectory |
|
| 144 |
+ } |
|
| 142 | 145 |
// If neither -d or -a are set, attach to everything by default |
| 143 | 146 |
if len(flAttach) == 0 && !*flDetach {
|
| 144 | 147 |
if !*flDetach {
|
| ... | ... |
@@ -197,6 +207,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, |
| 197 | 197 |
VolumesFrom: *flVolumesFrom, |
| 198 | 198 |
Entrypoint: entrypoint, |
| 199 | 199 |
Privileged: *flPrivileged, |
| 200 |
+ WorkingDir: *flWorkingDir, |
|
| 200 | 201 |
} |
| 201 | 202 |
hostConfig := &HostConfig{
|
| 202 | 203 |
Binds: binds, |
| ... | ... |
@@ -666,6 +677,18 @@ func (container *Container) Start(hostConfig *HostConfig) error {
|
| 666 | 666 |
"-e", "container=lxc", |
| 667 | 667 |
"-e", "HOSTNAME="+container.Config.Hostname, |
| 668 | 668 |
) |
| 669 |
+ if container.Config.WorkingDir != "" {
|
|
| 670 |
+ workingDir := path.Clean(container.Config.WorkingDir) |
|
| 671 |
+ utils.Debugf("[working dir] working dir is %s", workingDir)
|
|
| 672 |
+ |
|
| 673 |
+ if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil {
|
|
| 674 |
+ return nil |
|
| 675 |
+ } |
|
| 676 |
+ |
|
| 677 |
+ params = append(params, |
|
| 678 |
+ "-w", workingDir, |
|
| 679 |
+ ) |
|
| 680 |
+ } |
|
| 669 | 681 |
|
| 670 | 682 |
for _, elem := range container.Config.Env {
|
| 671 | 683 |
params = append(params, "-e", elem) |
| ... | ... |
@@ -129,7 +129,9 @@ Create a container |
| 129 | 129 |
"Dns":null, |
| 130 | 130 |
"Image":"base", |
| 131 | 131 |
"Volumes":{},
|
| 132 |
- "VolumesFrom":"" |
|
| 132 |
+ "VolumesFrom":"", |
|
| 133 |
+ "WorkingDir":"" |
|
| 134 |
+ |
|
| 133 | 135 |
} |
| 134 | 136 |
|
| 135 | 137 |
**Example response**: |
| ... | ... |
@@ -195,7 +197,9 @@ Inspect a container |
| 195 | 195 |
"Dns": null, |
| 196 | 196 |
"Image": "base", |
| 197 | 197 |
"Volumes": {},
|
| 198 |
- "VolumesFrom": "" |
|
| 198 |
+ "VolumesFrom": "", |
|
| 199 |
+ "WorkingDir":"" |
|
| 200 |
+ |
|
| 199 | 201 |
}, |
| 200 | 202 |
"State": {
|
| 201 | 203 |
"Running": false, |
| ... | ... |
@@ -746,7 +750,8 @@ Inspect an image |
| 746 | 746 |
,"Dns":null, |
| 747 | 747 |
"Image":"base", |
| 748 | 748 |
"Volumes":null, |
| 749 |
- "VolumesFrom":"" |
|
| 749 |
+ "VolumesFrom":"", |
|
| 750 |
+ "WorkingDir":"" |
|
| 750 | 751 |
}, |
| 751 | 752 |
"Size": 6824592 |
| 752 | 753 |
} |
| ... | ... |
@@ -29,6 +29,7 @@ |
| 29 | 29 |
-v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume. |
| 30 | 30 |
-volumes-from="": Mount all volumes from the given container. |
| 31 | 31 |
-entrypoint="": Overwrite the default entrypoint set by the image. |
| 32 |
+ -w="": Working directory inside the container |
|
| 32 | 33 |
|
| 33 | 34 |
|
| 34 | 35 |
Examples |
| ... | ... |
@@ -62,3 +63,22 @@ cgroup controller. In other words, the container can then do almost |
| 62 | 62 |
everything that the host can do. This flag exists to allow special |
| 63 | 63 |
use-cases, like running Docker within Docker. |
| 64 | 64 |
|
| 65 |
+.. code-block:: bash |
|
| 66 |
+ |
|
| 67 |
+ docker run -w /path/to/dir/ -i -t ubuntu pwd |
|
| 68 |
+ |
|
| 69 |
+The ``-w`` lets the command beeing executed inside directory given, |
|
| 70 |
+here /path/to/dir/. If the path does not exists it is created inside the |
|
| 71 |
+container. |
|
| 72 |
+ |
|
| 73 |
+.. code-block:: bash |
|
| 74 |
+ |
|
| 75 |
+ docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd |
|
| 76 |
+ |
|
| 77 |
+The ``-v`` flag mounts the current working directory into the container. |
|
| 78 |
+The ``-w`` lets the command beeing executed inside the current |
|
| 79 |
+working directory, by changeing into the directory to the value |
|
| 80 |
+returned by ``pwd``. So this combination executes the command |
|
| 81 |
+using the container, but inside the current working directory. |
|
| 82 |
+ |
|
| 83 |
+ |
| ... | ... |
@@ -22,6 +22,15 @@ func setupNetworking(gw string) {
|
| 22 | 22 |
} |
| 23 | 23 |
} |
| 24 | 24 |
|
| 25 |
+// Setup working directory |
|
| 26 |
+func setupWorkingDirectory(workdir string) {
|
|
| 27 |
+ if workdir == "" {
|
|
| 28 |
+ return |
|
| 29 |
+ } |
|
| 30 |
+ syscall.Chdir(workdir) |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+ |
|
| 25 | 34 |
// Takes care of dropping privileges to the desired user |
| 26 | 35 |
func changeUser(u string) {
|
| 27 | 36 |
if u == "" {
|
| ... | ... |
@@ -83,6 +92,7 @@ func SysInit() {
|
| 83 | 83 |
} |
| 84 | 84 |
var u = flag.String("u", "", "username or uid")
|
| 85 | 85 |
var gw = flag.String("g", "", "gateway address")
|
| 86 |
+ var workdir = flag.String("w", "", "workdir")
|
|
| 86 | 87 |
|
| 87 | 88 |
var flEnv ListOpts |
| 88 | 89 |
flag.Var(&flEnv, "e", "Set environment variables") |
| ... | ... |
@@ -91,6 +101,7 @@ func SysInit() {
|
| 91 | 91 |
|
| 92 | 92 |
cleanupEnv(flEnv) |
| 93 | 93 |
setupNetworking(*gw) |
| 94 |
+ setupWorkingDirectory(*workdir) |
|
| 94 | 95 |
changeUser(*u) |
| 95 | 96 |
executeProgram(flag.Arg(0), flag.Args()) |
| 96 | 97 |
} |