dock
is a tool for defining, building, and running self-contained development
environments inside Docker containers.
All you need to run dock
is to have Docker and Bash installed on your system.
brew tap brigade/dock
brew install dock
You can then upgrade at any time by running:
brew upgrade dock
You can install/upgrade Dock with this command:
curl https://raw.githubusercontent.com/brigade/dock/master/script/install-dock | bash
It will ask for your sudo password only if necessary.
If you are using Docker for Mac,
you need to add /usr/local/Cellar
(if you installed Dock using Homebrew) or
/usr/local/bin
(if you installed using the Bash script) to the list of mountable
directories.
This allows Dock to mount itself within the container so you can recursively execute Dock within Dock containers. You can add the path via Preferences -> File Sharing:
You can try out Dock against the Dock repository itself to get a feel for how it works.
WARNING: This will start a privileged container on your machine (in order to start a Docker daemon within the container, it needs extended privileges). Proceed at your own risk.
git clone git://github.com/brigade/dock
cd dock
bin/dock # If you have installed Dock then you can just run `dock`
After running dock
inside the Dock repository, you should be running inside
a Docker container. The environment will look and feel like CentOS 7 because
it is based off of that image (see the corresponding Dockerfile).
Your current directory will be /workspace
inside that container, and the
contents of that directory will be the Dock repository itself (i.e. the current
project).
$ pwd
/workspace
$ ls
Dockerfile LICENSE.md README.md ...
Any changes you make to these files will automatically be reflected in the original repository, and vice versa. This allows you to continue using your favorite tools and editors to make changes to your project, but actually run code or tests inside the container to isolate these from the rest of your system.
Run dock
within your repository by specifying any options followed by the
command you wish to run in the Dock container:
cd path/to/repo
dock [options] [command...]
If no command is given, Dock will execute the default_command
defined in your
Dock configuration file. If no default_command
is specified, the
ENTRYPOINT
/CMD
directives defined in the Dockerfile that created the image
Dock is running will dictate which command will be executed.
Option | Description |
---|---|
-a |
Attach to an already-running Dock container |
-c config-file |
Path of Dock configuration file to use (default .dock ) |
-d |
Detach/daemonize (run resulting Dock container in the background) |
-e dock-id |
Extend an existing Dock container (add new project configuration and services) |
-f |
Force creation of new container (destroying old one if it exists) |
-h |
Display summary of command line options |
-q |
Silence Dock-related output (so only output from command run in the container is shown) |
-t dock-id |
Terraform an existing Dock container (fully compose and merge embedded projects) |
-v |
Display version information |
-V |
Display verbose version information (for bug reports) |
Dock is intended to make it easy to manage a single development environment
for a repository, like you would any virtual machine. Thus it explicitly
prevents you from running dock
multiple times in the same project to run
multiple containers for that single project (if you must, you can get around
this by cloning the project into separate directories with different names so
they are assigned different container names by default).
You can however open multiple shells to the same container by attaching to an
already-running Dock container by running dock -a
in the project directory.
If you run dock
in a repository which already has an associated container
running, you'll prompted to confirm if you would like to destroy the current
container (if there's no interactive shell, Dock will halt with an error).
You can bypass this prompt by setting the environment variable
DOCK_FORCE_DESTROY=1
. This is useful in CI environments where there is no
interactive shell but you are sure you always want to destroy any lingering
containers.
If you want to have Dock start up isolated services and don't require an active shell session inside the container, you can start the container in detached mode.
Run dock -d
or specify detach true
in your Dock configuration.
You can run multiple projects within a single Dock
container in very much the
same way Dock
runs a single project.
If you run dock
in a repository and specify a Dock
container identifier (used
to annotate and distinguish between Dock
container environments) to extend, dock
will add the project src and all associated environment configuration (e.g. volumes,
exposed ports, environment variables) into the Dock
container represented by the
identifier.
If the specified Dock
container does not exist, dock
will create and run a new
Dock
container based on the configuration of the current project.
$ cd /<path-to-project> # step 1
$ dock -e <dock-id> # step 2
...
SUCCESS: Dock <dock-id> successfully created!
$ cd /<path-to-new-project> # step 3
$ dock -e <dock-id> # step 4
...
SUCCESS: Dock <dock-id> successfully extended!
Specifying a -t
flag along with a Dock
container identifier when running Dock
will result in the full composition and merging of the embedded project components
within the container. This option essentially stands up all services and associated
components added to the target Dock
container using the extends
options while also
handling the proper identification and deduplication of shared components.
The result of the operation will be a container consistent with running docker-compose up
on each docker-compose.yml file associated with all projects embedded within
and obeying Docker's official docker-compose extension support.
$ dock -t <dock-id> # step 1
...
SUCCESS: Dock <dock-id> successfully terraformed!
By adding a correct shebang line to your script, you can have the script automatically run inside a Dock container.
#!/usr/local/bin/dock bash
echo "We are in a container!"
If you run this in a project with a valid Dock configuration file, the script
will invoke Dock which will start a container using the image defined by the
configuration execute the script using whatever command you passed as the second
argument (bash
in this case).
Any Dock-related output will go to the standard error stream, but the standard
output stream will contain output from the original script. If you need to
inspect the command's standard error stream and don't want to deal with filtering
out Dock-related output, you can specify the QUIET
environment variable in order
to silence all Dock-related output. Note that this can potentially be confusing
since if the image has never been built before it may take a while to build,
giving the appearance of nothing happening.
WARNING: Never specify more than one argument to the shebang line. Different operating systems have different restrictions on shebangs. While some allow you to specify as many as you want, Linux in particular will treat all arguments after the executable as a single argument. For example, the following code:
#!/usr/local/bin/dock bash -c 'some command'
...
...will treat the shebang line as /usr/local/bin/dock "bash -c 'some command'"
(i.e. treating bash -c 'some command'
as a single argument), which will fail
since there is no such file.
If you need to execute a script with a complicated set of arguments, create a wrapper script:
#!/usr/local/bin/dock script/my-wrapper-script
...
The wrapper script will be passed the path to the script file as a single argument. It is up to you to write the script to know how to handle/execute the file it is given.
While an important feature offered by Dock is isolating your development environments from each other (e.g. so that services listening on the same port don't conflict), it is convenient to be able to expose these ports on your host so you can easily interact with them using tools installed on your host.
You can expose/publish a port using the publish
command in your
configuration:
publish 3306:3306 # Expose MySQL on the same port on the host
Where this differs from the --publish
flag of docker run
is that if the
port is already taken by another process on your machine (e.g. another Dock
container for a different project) you'll still be able to start the
container. You'll see a warning letting you know that the port could not
be exposed.
If you really need to expose the service for a given project, stop the container for the other project so it releases the port, start the project whose service you want to expose, and then start back up your other project.
Alternatively, you can decide to expose the services to different ports on the host so they don't conflict.
The Dock configuration file is quite flexible since it's simply sourced as a
Bash script. By default Dock looks for a .dock
file at the root of your
repository and sources it.
This means anything you can do in Bash you can also do in this script. Therefore caution is required!
However, this also provides an incredible amount of power. A common use case is to dynamically change which environment variables are set or volumes are mounted based on whether you are running in a CI (continuous integration) testing environment or just a regular development environment.
Dock exposes configuration options and helpers.
These configuration options can be set in your Dock configuration file.
attach_command
build_arg
build_context
build_flags
container_hostname
container_name
default_command
detach
detach_keys
dockerfile
dock_in_dock
env_var
image
optional_env_var
privileged
publish
pull_latest
required_env_var
run_flags
startup_services
volume
workspace_path
Command to execute when attaching to a container via dock -a
. By default this
is just bash
.
attach_command gosu app_user
WARNING: You must split the command into individual arguments in order for them to be executed correctly. This means if you have a single argument with whitespace you'll need to wrap it in quotes or escape it:
attach_command echo "This is a single argument"
attach_command echo These are multiple separate arguments
Specify an additional build argument to pass to the docker build
command that
Dock will execute when building the Dockerfile specified by the dockerfile
option. Ignored if you have only specified an image
rather than a dockerfile
,
as no building is done in such case.
build_arg MY_ARG "some value"
Specify the path or URL to use for the build context of the docker build
command that Dock will execute when building the Dockerfile. Ignored if you
have only specified an image
rather than a dockerfile
.
build_context path/in/repo
build_context https://example.com/context.tar.gz
See the docker build
documentation for details on how the build context and Dockerfile paths are handled.
Specify additional arguments for the docker build
command that Dock will
execute when building the Dockerfile specified by the dockerfile
option.
Ignored if you have only specified an image
rather than a dockerfile
, as
no building is done in such case.
Note: This is not exclusively for the --build-arg
flag that allows you to
specify build-time variables (use the build_arg
option instead). It allows you
to specify any flag that docker build
accepts, e.g. --memory
, --no-cache
,
etc.
build_flags --no-cache
Specifies an explicit hostname for the container.
container_hostname "$(container_name).test.com"
Specifies the name to give the Dock container. Default is the directory name of
the repository followed by "-dock", e.g. my-project-dock
.
The container name is what Dock uses to determine if the container is already running.
container_name my-container-name
Specifies a default command to run if no command is explicitly given.
You should ideally always have a default command specified so that a user who
knows nothing about your repository can simply run dock
to get started.
default_command script/start-services
WARNING: You must split the command into individual arguments in order for them to be executed correctly. This means if you have a single argument with whitespace you'll need to wrap it in quotes or escape it:
default_command echo "This is a single argument"
default_command echo These are multiple separate arguments
Specifies whether the Dock container should run in the background (e.g. the
--detach
flag of docker run
).
You can re-attach to a detached Dock container by running dock -a
.
detach true
Specifies the key sequence to detach from the container.
Dock changes this sequence from the default ctrl-p,p
to ctrl-x,x
. This
makes the ctrl-p
readline shortcut for cycling through previous commands
work as expected without needing to type ctrl-p
twice, which is useful
when you are running a shell in the container.
This is almost certainly what you want, but detach_keys
is available if you
want to change it to some other sequence.
Specify path to the Dockerfile to build into an image used by the Dock
container. If a relative path is given, this will be relative to the
repository root directory (even if you have specified a custom
build_context
, the path is always relative to the root
of the repository).
Cannot specify dockerfile
and image
options in the same configuration.
dockerfile Dockerfile
Specify whether to allow Dock to create containers within a Dock container.
Default is false
, and this is almost always what you want.
The Dock project itself needs to test the creation of Dock containers within a Dock container, so it enables this feature.
dock_in_dock true
Specifies an environment variable name and value to be set inside the container.
env_var MY_ENV_VAR "my value"
If dockerfile
is specified, defines the image name to tag the build with.
Otherwise this specifies the image used to create the container.
image centos:7.2.1511
Specifies the name of an environment variable which is included in the
container environment if defined in the context in which dock
is run.
If not defined, it is ignored and no value is set inside the container.
This is a way of safely declaring which environment variables you want to "inject" from your host into the Dock container.
optional_env_var MY_OPTIONAL_ENV_VAR
Whether to run the container with elevated privileges. Defaults to false
.
privileged true
Expose a port inside the Dock container to the host. Useful if you want to make it easier for developers to connect to services inside the Dock container using tools installed on their host machine.
If another process on the host has already bound to the port, Dock will display a warning but will otherwise ignore the error, since you will still be able to access the service from inside the container.
The format of the port specification is the same as the -p
/--publish
flag
of the docker run
command ([host-ip:][host-port:]container-port
).
# Expose MySQL
publish "3306:3306"
Specifies whether Docker should always attempt to pull the latest version of
an image/tag to ensure it is up to date. Default is false
.
pull_latest true
Specifies the name of an environment variable that must be defined. If not defined, Dock will halt with an error message.
required_env_var MY_REQUIRED_ENV_VAR
Specify additional arguments for the docker run
command that Dock will
execute.
Most common flags are configured via the various options allowed in the Dock
configuration, e.g. env_var
, volume
, etc. However, for special cases
this is provided to allow you to specify additional flags.
run_flags --memory 1g
Services to startup when terraforming a multi-project, integrated and self-contained Dock development environment, as defined within a project's docker-compose.yml.
startup_services "my_service mysql"
Specify a volume or bind mount.
# Mount /var/lib/docker directory in the container on the host file system.
# Destroyed on container shutdown. Useful if you are using a container file
# system like OverlayFS and want to perform a lot of writes with the
# performance of the underlying host file system.
volume "/var/lib/docker"
# Create a data volume named "${container_name}_docker" which store the
# contents of the container's /var/lib/docker directory. Will not be destroyed
# when the container is shutdown/destroyed.
volume "${container_name}_docker:/var/lib/docker"
# Mount a file/directory on the host file system at a particular location in
# the container. Use the ${repo_root} variable so you reference a path that
# will exist regardless of the location of your repository.
volume "${repo_root}/script/my-script:/usr/bin/my-script"
Define the path in the container that the repository will be mounted at.
Default is /workspace
.
workspace_path /my-custom-dir
Dock defines a number of helper functions which may be useful when writing your configuration file.
WARNING: Bash variables and functions are referenced differently. Since
these helpers are all functions, always use $(...)
to include the output of a
function in a string, e.g.:
volume "$(container_name)_docker:/var/lib/docker"
ask
detach
group_id
container_hostname
interactive
linux
mac_os
privileged
repo_path
user_id
workspace_path
Asks a user for input. Uses the default answer if we're not running in
an interactive context (as specified by interactive
).
All arguments must be specified.
ask "Are you sure? (y/n)" n variable_to_store_answer_in
Outputs the explicit hostname that will be assigned to the container, if one
was specified via container_hostname "some.name"
. Otherwise it returns
a non-zero exit status.
Outputs the name of the Docker container that Dock will create.
Returns whether container will be detached on startup.
Returns zero exit status (success) if yes.
Group ID of the user that ran the Dock command.
Whether we're running Dock in an interactive context where a human can provide input (e.g. standard input is a TTY).
Returns zero exit status (success) if yes.
Whether the host is running Linux.
Returns zero exit status (success) if yes.
Whether the host is running macOS.
Returns zero exit status (success) if yes.
Returns whether the container will be started with extended privileges.
Returns zero exit status (success) if yes.
Path to repository root on the host system.
User ID of the user that ran the Dock command.
Directory in the container that the repository will be mounted at.
Outputs the absolute path.
Hacking on Dock is easy thanks to the fact that it is run within a Dock
container! Provided you have Docker and Bash installed on your system, working
on Dock is as easy as running bin/dock
from the root of the repository.
Tests can be run by executing:
bin/test
...from the root of the repository. This will start a Dock container and run all tests, which are written in Bash using Bats.
To run a specific test or set of tests, execute:
bin/test test/path/to/test.bats test/path/to/another.bats
If you're interested in seeing a summarized list of changes between each version of Dock, see the Dock Change Log
Dock is released under the Apache 2.0 license.