Configures and starts a 'Docker In Docker' (dind) server container with its own, empty local repository and spins up an associated docker client container. Once dind server and client have started, '''didx''' executes one or more scripts/programs within context of the dind client container.
Note: Although this script usually works, its still under development and hasn't been officially released. You've been warned!
Use didx to create a Docker test environment that's automatically destroyed after all tests successfully complete.
#####ToC
Options
    --sv,--cv
    --pull
    --cp[],-v[]
    --clean
    --storage-driver
    --cv-env
Examples
Installing
Testing
Security
Networking
Warning Label
Motivation
License
Usage: didx.sh [OPTIONS] {| COMMAND [COMMAND] ...}
COMMAND - Command executed within context of Docker in Docker (dind)
client container.
Use local Docker Engine to run a Docker Engine container cast as the "server".
Once started, run second Docker Engine container cast as the "client". After
the client starts, copy or mount zero or more files into the client's
file system then execute one or more COMMANDS within the client container.
The server container's local repository is encapsulated as a data volume
attached to only this server. This configuration isolates image and container
storage from the repository used by the Docker Engine server initiating
this script.
OPTIONS:
--sv Docker server version. Defaults to most recent
stable (public) Docker Engine version. Click
https://hub.docker.com/r/library/docker/tags/ to
view supported versions.
--cv Docker client version. Defaults to --sv value.
-p,--pull=false Perform explicit docker pull before running server/client.
--cp[] Copy files from source location into container running
Docker client. (Optional)
Format: <SourceSpec>:<AbsoluteClientContainerPath>
<SourceSpec>-><hostFilePath>
<SourceSpec>-><stream>->-
<SourceSpec>->{<containerName>|<UUID>}:<AbsoluteContainerPath>
<SourceSpec>->{<imageName>[:<tag>]|<UUID>}::<AbsoluteImagePath>
'docker cp' used when <SourceSpec> referrs to host file or
input stream. Otherwise, when source refers to
container or image, cp performed by 'dkrcp'.
-v[] Mount host file system references or create an anynomous
volume in the container running Docker client. (Optional)
Format: [{<HostFilePath>|<VolumeFilePath>}:]<AbsoluteClientContainerPath>
'docker run -v' option used to implement mount.
--clean=none After executing all COMMANDs, sanitize environment. Note
if an option value preserves the server's data volume,
you must manually delete it using the -v option when
removing the server's container.
none: Leave server & client containers running
in background. Preserve server data volume.
success: When all COMMANDs succeed, terminate and
remove server & client containers.
Delete server data volume.
failure: When at least one COMMAND fails, terminate
and remove server & client containers
Delete server data volume.
anycase: Regardless of COMMAND exit code, terminate
and remove server, client containers.
Delete server data volume.
all: Remove all server, client containers from
local repository. If necessary, terminate
running instances.
Delete all server data volumes.
-s,--storage-driver Docker storage driver name. Determines method
applied to physically represent and manage images and
containers stored locally by the Docker server container.
Value defaults to the one utilized by the Docker instance
managing the Docker server container.
--cv-env=DIND_CLIENT_NAME Environment variable name which contains the Docker client's
container name. Use in COMMAND to identify client container.
-h,--help=false Don't display this help message.
--version=false Don't display version info.
A detailed explaination of Docker in Docker (dind).
##Options
####--sv,--cv
--sv
Determines the Docker Engine dind image version to run as the server container. didx
converts the version specifier to the appropriate dind tag. The conversion simply appends -dind to the provided version specifier, except in the case of 'latest' which fetches the most recent dind version. The list of dind supported versions can be derived by removing the -dind suffix from dind tags.
--cv
Determines the Docker client image to run as the client container (separate from the server). When running, the client container is linked to the dind server container. Although the Docker client and server container versions are typically identical, they can differ. If --cv
is omitted, didx
will match the client version to the one specified by --sv
.
####--pull
--pull
directs didx
to perform an explicit docker pull
before executing docker run
to refresh the Docker Engine Host's local repository with the most recent version of both the dind server and client images. --pull
is typically unnecessary when specifying a particular version specifier, like 1.11. However, when describing the dind version using an adaptable lable, like 'latest' or in situations where Docker has updated a specific image version, the local repository image of the dind server and/or client offered by the Docker Engine Host may be stale. For example, 'latest' may refer to an older Docker Engine version, as one or more releases may have occurred since the initial docker pull
populated the local repository.
####--cp[],-v[]
--cp
adapts the client container's file system by adding/modifying any number of files to it. --cp
source files to can reside in the Docker Engine Host file system, container, or image. They can also be streamed as a tar. These various source types determine the copy method applied to transfer files from one or more sources to the targeted file system in the client container. Files sourced from the Docker Engine Host file system or a streamed tar employ docker cp
while sources types referencing container or image file systems use dkrcp.sh
. Due to its non Docker affliated status, dkrcp.sh
isn't immediately available, as opposed to docker cp
, therefore, it must be downloaded from Github and installed on the Docker Engine Host in a location accessible via the host's PATH or through an alias.
Zero to many --cp
instances may be specified as options to didx
. The source files are copied to the client container in left to right order. The productions below define the expected format.
--cp option format:
<SourceSpec>:<AbsoluteClientContainerPath>
<SourceSpec>-><HostFilePath>
<SourceSpec>-><Stream>
<SourceSpec>->{<ContainerName>|<UUID>}:<AbsoluteContainerPath>
<SourceSpec>->{<ImageName>|<UUID>}::<AbsoluteImagePath>
<HostFilePath>->{<AbsoluteFilePath>|<RelativeFilePath>}
<Stream>->-
<ContainerName>->^[a-zA-Z0-9][a-zA-Z0-9_.-]*
<ImageName>->[<NameSpace>/...]<RepositoryName>:[<TagName>]
<NameSpace>, <RepositoryName>->^[a-z0-9][a-z0-9._-]*
<TagName>->[A-Za-z0-9._-]+
<AbsoluteContainerPath>-><AbsolutePath>
<AbsoluteImagePath>-><AbsolutePath>
<AbsoluteClientContainerPath>-><AbsolutePath>
<AbsolutePath>->/.*
<RelativeFilePath>->./.*
Ex: /host/file:/client/container/target/ # <SourceSpec>-><HostFilePath>
Ex: -:/client/container/target/ # <SourceSpec>-><Stream>
Ex: dreamy_pare:/some/container/file:/client/container/target/ # <SourceSpec>-><ContainerName>
Ex: alpine:3.3::/tmp/file:/client/container/target/ # <SourceSpec>-><ImageName>
Ultimately, the format of <SourceSpec> conforms to the ones expected by docker cp and dkrcp.sh.
Use this copy mechanism to dynamically extend the client container's abilities by installing processes implemented as scripts/programs. Execution of added scripts/programs can be initiated by issuing a COMMAND to run them. Since the dind server and client Docker images include Alpine Linux in their derivation chains and this distro includes a package management feature, the client container can implement processes of arbitary complexity by combining the capabilities of --cp
, Apline's apk package manager and busybox's Almquist Shell (ash). If scripts require full bash compatibility, encode a process the runs apk to install bash before executing them.
Similar to --cp
, -v
(volume) command extends the client container contents, however, it uses Docker's volume feature to bind the desired files from some source, like Docker Engine Host file system or a named volume, to the client container's file system instead of physically copying the files as implemented by --cp
.
Since -v
supports the creation of anynomous volumes, didx
will destroy the client container's anynomous volumes if it has been directed to via the --clean
option value.
-v option format:
[<SourceSpec>:]<AbsoluteClientContainerPath>
<SourceSpec>-><HostFilePath>
<SourceSpec>-><NamedVolumeFilePath>
<HostFilePath>->{<AbsoluteFilePath>|<RelativeFilePath>}
<NamedVolumeFilePath>->? # not currently sure of its definition.
<AbsoluteClientContainerPath>-><AbsolutePath>
<AbsolutePath>->/.*
<RelativeFilePath>->./.*
Ex: /host/file:/client/container/target/ # <SourceSpec>-><HostFilePath>
Ex: named-volume:/named/volume/file:/client/container/target/ # <SourceSpec>-><ContainerName>
Ex: /client/container/target/volume # Creates an anonymous volume.
Ultimately, the format of -v conforms to -v option of docker run.
Use both --cp
and -v
to concurrently extend the client container. didx
applies the -v
option before considering -cp
. This ordering permits the creation of anynomous volumes associated to the client container enabling --cp
to target a file path within the anynomous volume.
#####--cp
VS -v
In general use --cp
:
- for smallish files,
- to physically encapsulate and couple the copied file's existance (life cycle) to the client container,
- to reduce the dynamics introduced by
-v
.
Use -v
:
- for large files,
- when the file's life cycle is independent of the client container.
####--clean
Represents the function that destroys dind server and client containers including any anynomous volumes associated to them. This option's value defines the triggering condition that causes the function's invocation. For example, a value of 'success' invokes the clean function if every COMMAND successfully (return code=0) completes. Since the option values besides 'all' are sufficiently explained by --help
, no further explaination will be provided for them. However, 'all' warrants mention.
'all' short circuits didx
's typical execution flow replacing it with behavior to search and destroy dind server and client containers. The search scours the local repository for container instances whose names regex match the naming pattern used by didx
to identify candidate instances for deletion. 'all' then further filters the candidate instances to ensure their Docker ENTRYPOINT signatures reflect those assigned to the dind server and client containers. A docker stop
is executed for containers satisfying both filters before being destroyed by docker rm -v
.
Use --clean=all
to rid the local repository of dind server and client containers that survived prior didx
invocations but are no longer needed. For example, a failure while running COMMAND test.sh
invoked by: didx --clean=success --cp ./test.sh:/ /test.sh
exits leaving both the dind server and client containers running. After debugging /test.sh
's failure, the invocation of didx --clean=all
will terminate and destroy these recently started dind containers, as well as any other leftover dind containers, started by didx
, known to the Docker Engine Host.
####--storage-driver
The dind server creates a repository as an anynomous volume bound inside its container and isolated from the Docker Engine Host repository. --storage-driver
option determines the file system/volume manager type employed by dind server to maintain its Docker repository. The selection of this value can reflect the constraints imposed by the file system or volume manager type implemented for the Docker Engine Host and the desire to control, at a physical level, the representation of image layers. Docker provides guidance selecting among its offered storage driver types, as well as describing how image layering operates which may also affect the storage driver decision.
didx
by default configures the dind server --storage-driver
value to reflect the one employed by the Docker Engine Host. Use didx
's --help
option to display this value. This convention aligns with aim of didx
: to offer a test environment within the Docker Engine Host, therefore, the dind server container inherits its --storage-driver
value from its Docker Engine Host. To circumvent this scheme, use --storage-driver
to specify the desired type.
####--cv-env
Specifies an environment variable name assigned the Docker container name of the dind client container. Use this option to change the variable name from DIND_CLIENT_NAME
to the desired one. Although its use is unlikely, this option supports the custom encoding of the docker exec [OPTIONS] CONTAINER
that must prefix every didx
COMMAND argument. A custom form of docker exec...
is necessary in situations when the "default prefix", explained below, doesn't specify the docker exec
option values necessary to execute its command portion. For example, if a command portion relied on user permissions different from root, a custom docker exec --user=1000:1000 $DIND_CLIENT_NAME...
would need to be encoded to override the default prefix.
Each COMMAND encoded to execute within the dind client container must begin with some flavor of docker exec [OPTIONS] CONTAINER...
. When the Docker Engine Host runs this command, it removes the docker exec [OPTIONS] CONTAINER
prefix and then forwards the command portion to the dind container for execution. If this forwarded command wishes to invoke a docker command requiring a response from the dind server, the forwarded command must begin docker [OPTIONS] COMMAND
. For example, to list all the images known to the dind server the implemented command would appear as: 'docker exec <DIND_CLIENT_NAME> docker images -a'
.
To avoid finger cramps resulting from typing the entire prefix for every COMMAND, didx
and the Docker provided dind client container ENTRYPOINT "docker-entrypoint.sh"
each contribute a portion of the prefix to construct a complete "default prefix". didx
will prepend docker exec <DIND_CLIENT_NAME> docker-entrypoint.sh
to the COMMAND when COMMAND begins with tokens other than docker exec
. After forming the cmplete command, didx
then executes it causing Docker Engine Host to process the prepared command: docker exec <DIND_CLIENT_NAME> docker-entrypoint.sh <COMMAND>
by forwarding the docker-entrypoint.sh <COMMAND>
portion to the running dind client container for execution. Once started in the dind client container docker-entrypoint.sh
processes the <COMMAND>
through a filter that attempts to determine the <COMMAND>
's type.
docker-entrypoint.sh
recognizes three <COMMAND>
types:
- a Docker command, like
run ...
, - a Docker command beginning with a Docker Engine option, like
-D images
, - and everything else (non-docker related)
For the first two types
docker-entrypoint.sh
will affixdocker
to the<COMMAND>
string before executing<COMMAND>
while for the third typedocker-entrypoint.sh
invokes<COMMAND>
without affecting it.
In addition to automatically prefixing Docker related commands, docker-entrypoint.sh
establishes the value of the DOCKER_HOST environment variable for itself and its child processes. This behavior is critical to the successful execution of scripts invoked by COMMAND containing Docker commands, like docker build ...
for without it, Docker commands will fail with messages indicating an inability to connect to the dind server.
For example, when given the COMMAND 'images -a'
as an argument, didx
generates the prefix docker exec <DIND_CLIENT_NAME> docker-entrypoint.sh
and then concantenates the COMMAND images -a
to it forming: 'docker exec <DIND_CLIENT_NAME> docker-entrypoint.sh images a'
. This generated command is then executed by the Docker Engine Host which removes docker exec <DIND_CLIENT_NAME>
and forwards the command portion docker-entrypoint.sh images a'
to the dind client container.
##Examples
#########################################################################################
# Ex 1 - start the latest version of the dind server & client and run them in the background
#########################################################################################
dockerHost:didx
Inform: dind server named: 'dind_22789_server_latest' successfully started.
Inform: dind client named: 'dind_22789_client_latest' successfully started.
Inform: dind server named: 'dind_22789_server_latest' remains running.
Inform: dind client named: 'dind_22789_client_latest' remains running.
#########################################################################################
# Ex 2 - start dind server version 1.10 & dind client 1.9 and run them in the background
#########################################################################################
dockerHost:didx --sv 1.10 --cv 1.9
Inform: dind server named: 'dind_23563_server_1.10' successfully started.
Inform: dind client named: 'dind_23563_client_1.9' successfully started.
Inform: dind server named: 'dind_23563_server_1.10' remains running.
Inform: dind client named: 'dind_23563_client_1.9' remains running.
#########################################################################################
# Ex 2.1 - attach to the client and run 'docker version'
#########################################################################################
dockerHost:docker attach dind_23563_client_1.9
/ # docker version
Client:
Version: 1.9.1
API version: 1.21
Go version: go1.4.3
Git commit: a34a1d5
Built: Fri Nov 20 17:56:04 UTC 2015
OS/Arch: linux/amd64
Server:
Version: 1.10.3
API version: 1.22
Go version: go1.5.3
Git commit: 20f81dd
Built: 2016-03-10T21:49:11.235199091+00:00
OS/Arch: linux/amd64
/ #
#########################################################################################
# Ex 3 - Terminate & destroy the dind servers and clients initiated by Ex 1 & Ex 2
#########################################################################################
dockerHost:didx./didx.sh --clean all
Inform: dind client named: 'dind_23563_client_1.9' terminated & destroyed.
Inform: dind server named: 'dind_23563_server_1.10' terminated & destroyed.
Inform: dind client named: 'dind_22789_client_latest' terminated & destroyed.
Inform: dind server named: 'dind_22789_server_latest' terminated & destroyed.
#########################################################################################
# Ex 4 - Run a series of commands to pull, report on, run, and stop an alpine
# image via latest version of dind. Once complete remove the dind
# server and client without regard to success/failure.
#########################################################################################
dockerHost:didx --clean anycase 'pull alpine' 'images' 'run -dit --name alpine_container alpine sh' 'ps' 'stop alpine_container'
Inform: dind server named: 'dind_1118_server_latest' successfully started.
Inform: dind client named: 'dind_1118_client_latest' successfully started.
Using default tag: latest
latest: Pulling from library/alpine
e110a4a17941: Pulling fs layer
e110a4a17941: Verifying Checksum
e110a4a17941: Download complete
e110a4a17941: Pull complete
Digest: sha256:3dcdb92d7432d56604d4545cbd324b14e647b313626d99b889d0626de158f73a
Status: Downloaded newer image for alpine:latest
Inform: Command: 'docker exec dind_1118_client_latest docker-entrypoint.sh pull alpine' successfully terminated.
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest 4e38e38c8ce0 9 days ago 4.799 MB
Inform: Command: 'docker exec dind_1118_client_latest docker-entrypoint.sh images' successfully terminated.
7d64a075f70d8a4476024add123d2011154c261de47e8905f204bb7c3dbb3911
Inform: Command: 'docker exec dind_1118_client_latest docker-entrypoint.sh run -dit --name alpine_container alpine sh' successfully terminated.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7d64a075f70d alpine "sh" 1 seconds ago Up Less than a second alpine_container
Inform: Command: 'docker exec dind_1118_client_latest docker-entrypoint.sh ps' successfully terminated.
alpine_container
Inform: Command: 'docker exec dind_1118_client_latest docker-entrypoint.sh stop alpine_container' successfully terminated.
Inform: dind server named: 'dind_1118_server_latest' terminated & destroyed.
Inform: dind client named: 'dind_1118_client_latest' terminated & destroyed.
#########################################################################################
# Ex 5 - Build and run a simple golang "goodbye" image within the context of the dind
# client container. The input Docker context required to create the
# image along with the Bash script directing its build and execution is mounted
# into the dind client's file system. After running this bash script, terminate
# and destroy the dind server and client containers.
#
# Options explained:
# "--clean anycase" Apply 'docker stop' then 'docker rm -v' to dind server and
# client containers without regard to the Bash script's outcome.
# "-v /mount/golang" Create an anonymous volume in dind client to mount build
# context from host file system.
# "-v /home/secure/Desktop/project/didx/test:/mount/golang" Mount host file
# directory containing golang build context and Bash script. Bash script
# directs the construction of the "goodbye" image and executes it as a container.
# "/mount/golang/goodbyedind.sh" Command to execute the build and then run
# the "goodbye" container.
#
# Local Docker Engine context:
# ls -l /home/secure/Desktop/project/didx/test
# drwxrwxr-x 2 secure secure 4096 Jul 3 22:33 build
# -rwxrwxr-x 1 secure secure 65 Jul 3 22:46 goodbyedind.sh
# cat goodbyedind.sh
# docker build -t goodbye /mount/golang/build
# docker run goodbye
# ls -l /home/secure/Desktop/project/didx/test/build
# -rw-rw-r-- 1 secure secure 25 Jul 3 22:24 Dockerfile
# -rw-rw-r-- 1 secure secure 144 Jul 3 22:33 goodbye.go
# cat Dockerfile
# FROM golang:1.6-onbuild
# cat goodby.go
# package main
# import "fmt"
# func main() {
# fmt.Println("Goodbye cruel world I'm leaving you today. Goodbye. Goodbye. Goodbye. - Pink Floyd")
# }
#
#########################################################################################
didx.sh --clean anycase -v /mount/golang -v /home/secure/Desktop/project/didx/test:/mount/golang /mount/golang/goodbyedind.sh
Inform: dind server named: 'dind_10407_server_latest' successfully started.
Inform: dind client named: 'dind_10407_client_latest' successfully started.
Sending build context to Docker daemon 3.072 kB
Step 1 : FROM golang:1.6-onbuild
1.6-onbuild: Pulling from library/golang
5c90d4a2d1a8: Pulling fs layer
ab30c63719b1: Pulling fs layer
...
...
...
79618f9f23cb: Pull complete
Digest: sha256:4a5e529c26e40e3a4aa0c615603078987b9b53f1ee42c98aa96e296c34fb38e8
Status: Downloaded newer image for golang:1.6-onbuild
# Executing 3 build triggers...
Step 1 : COPY . /go/src/app
Step 1 : RUN go-wrapper download
---> Running in d49c7d9c3bdc
+ exec go get -v -d
Step 1 : RUN go-wrapper install
---> Running in c6a3080bf143
+ exec go install -v
app
---> ded5fb6e7620
Removing intermediate container 08c6074544bc
Removing intermediate container d49c7d9c3bdc
Removing intermediate container c6a3080bf143
Successfully built ded5fb6e7620
+ exec app
Goodbye cruel world I'm leaving you today. Goodbye. Goodbye. Goodbye. - Pink Floyd
Inform: Command: 'docker exec dind_10407_client_latest docker-entrypoint.sh /mount/golang/goodbyedind.sh' successfully terminated.
Inform: dind server named: 'dind_10407_server_latest' terminated & destroyed.
Inform: dind client named: 'dind_10407_client_latest' terminated & destroyed.
##Security
The AppArmor module exists within the dind server container (this is probably "wrong" they are probably mounted, however, none of the suporting inspection utilities, like aa-status, appear within the image. Therefore, it's currently difficult to determine if apparmor is enabled and applying custom policies. I'm still not sure if AppArmor is fully encapsulated within dind - most likely not as it's a kernel module, however, that would mean that something else isn't working as I anticipated because containers, within the dind container network, using their own custom apparmor profiles don't appear in the information generated aa-status
when executed within the context of the Docker Engine Host. Need to further investigate this behavior and update this section with my findings.
##Networking
As usual for a typical Docker Engine install, dind configures its own bridge network. However, this network connects the dind container network to the network interface of the dind server container, not the Docker Engine Host network. In other words, the dind container network is encapsulated within the Docker Engine Host container network. This isn't typically an issue when running a single dind server container instance and there isn't a need to connect to a container within the dind container network from the Docker Engine Host container network. The trouble begins when attempting to concurrently run two dind server containers, as by default they are assigned the same IP network identifier or if you're attempting to connect a container or service, like x11, running on the Docker Engine Host to one in the dind container network. In the second situation, you may be able to add a route using ip route add sudo ip route add <IP NETORK> via <IP Address of dind server container> dev docker0
to forward the dind container network packets from the Docker Engine Host to the dind server bridge.
Regarding the situation, involving duplicate IP network identifiers, it would seem that this can be solved by configuring the dind server with custom networking. Need to investigate.
##Terms Docker Engine Host - refers to the Docker server instance that manages (runs, terminates) the dind server and associated client containers.
##Warning Label The Docker Engine Host version can differ from the Docker Engine versions running in the dind server and client, however, version incompatibilities may arise when mixing certain groupings of differning versions. In general, conflicts arise from differences in capability settings and they can sometimes be resolved. View the following links for an indepth discussion of dind cautionary tales: