README ¶
container-do
Run project-level CLI tools in (Linux) containers. In particular,
- use tools not available on your platform,
- avoid managing version conflicts of tooling,
- persist and share specific setups, and
- minimize the blast radius of potential mishaps.
A more eloquent reasoning for using containers as development environment can be found in the documentation of kudulab/dojo.
Premise
- You have a "project directory", which we take to mean a directory which contains all files pertaining to the task at hand, and nothing else.
- You need a certain suite of tools (at certain versions) to perform this task.
- A compatible container image exists for this suite.
Install
Download
the binary matching your OS and put it on the PATH
.
As an alternative, you can compile from the sources like so:
go get github.com/reitzig/container-do/cmd/container-do
Find the binary at $GOPATH/bin
.
Use
Prerequisites:
- Docker installed and user can run
docker
. - Container has
sh
.
There are three special commands:
container-do --help
-- print usage instructions.container-do --init
-- create config file (template).container-do --kill
-- kill the configured container (if it exists).
All other calls will be passed directly to the configured container. For instance:
container-do npm install
will run npm install
inside the container and, more specifically,
through the default SHELL
in that container.
By default, container-do
will try to stay out of your way and
allow you to focus on the normal command output.
However, you can enable rather more verbose logging
by setting environment variable CONTAINER_DO_LOGGING
to debug
.
Config File
At the very least, you will have to tell container-do
which image to use.
Create a file ContainerDo.toml
with the following content:
[container]
image = "my-image"
Alternatively, run container-do --init
to get a full template.
Here is a full list of the optional values:
-
container.os_flavor
(Default: auto-detect)For some commands run in the container, we need to know which flavor of Linux it runs. While we will attempt to detect that automatically, this induces a slight performance over head (and may fail). Set to one of
debian
,fedora
,alpine
,gnu/linux
, orbusybox
. -
container.name
(Default:${project_dir}-do
) -
container.work_dir
(Default:$WORKDIR
)Use to override the working directory defined in the container image. Set to an absolute path.
-
container.mounts
(Default:[.:$WORKDIR]
/[]
)Unless the container working directory is
/
, the default is a bind-mount from the host working directory to it. Override with entries using the Docker--volume
syntax; unlikedocker
, we will resolve relative host paths.Note: You can also use named volumes!
-
container.ports
(Default:[]
)Given a list of port mapping strings, we publish the ports as specified.
-
container.keep_alive
(Default:15m
)The duration to keep the container alive for after the last command was run in it. Set to any value compatible with Go
time
. -
container.keep_stopped
(Default:false
)By default, we remove the container after it stops. Set to
true
to have the container stick around. -
container.environment
(Default: none)Set environment variables of the container.
If a value is of the form"$VARIABLE_NAME"
, it will be replaced with the value of the thus named environment variable of the host, or the empty string if it is not set. -
run.setup
-- run once after container creation
run.before
-- run once before each command
run.after
-- run once after each (successful) commandRun pre-defined shell commands, each section specified by:
-
run._.attach
(Default:false
)Set
true
in order to attach the current shell to the command runs. -
run._.user
(Default:$USER
)Override the default container user to run the commands.
-
run._.script_file
(Default: none)Set to a script file in (relative to
container.work_dir
). Run before any of thecommands
in the same section. -
run._.commands
(Default:[]
)Set to a list of shell commands run one after the other, so long as they are successful.
-
-
copy.setup
-- copy files into the container after its creation, but beforerun.setup
.
copy.before
-- copy files into the container before each command, and beforerun.before
.
copy.after
-- copy files out of the container after each (successful) command, and afterrun.after
.Note: Each of these can occur multiple times; include them as
[[copy.setup]]
.-
copy._.files
(Default: none)A list of file names and/or glob strings; all matching files will be copied. Directories will be copied recursively.
-
copy._.to
(Default:container.work_dir
/.
)Name of the target file or directory. The target will be a directory unless there is a single source file and the target file name does not end in
/
. If the target directory does not exist, it will be created.
Relative paths are relative to the working directory (for
copy.setup
,copy.before
) resp.container.work_dir
(forcopy.after
). -
Note: When errors happen during run.setup
or copy.setup
, we can not recover graciously.
Therefore, we immediately kill the container so _.setup
will be retried before the next command.
Examples
Explore some use cases:
Short ADRs
-
Why containers?
While this is not about running services, containers seem a prudent way to isolate versioned tooling from the host system without too much overhead. Also, the approach eliminates the need for tools specific to a certain ecosystem such as venv, rvm, etc.
-
Why Go?
Efficient binaries seem prudent here. Also, Go seems to be prevalent in the OCI space. If we were to use
docker/client
orlibpod
as libraries, those are written in Go. -
Why TOML?
Comparing to the most common alternatives, TOML seems to provide a better trade-off between expressiveness, cleanliness and usability than either of INI, JSON, YAML.
-
Why not use
docker/client
resp.libpod
as libraries?That would mean higher maintenance debt (security patches etc.) and put the duty of ensuring compatibility with user systems on us.
Other Tools
- kudulab/dojo -- Similar vision. Requires custom images but handles more complex setups.
- batect/batect -- More of a build tool than an environment specification.
Acknowledgements
Parts of this project were created during 20% time graciously provided by codecentric. Thank you!