Description
⚠️ 👉 NOTE: this proposal has been replaced by #9694.
This proposal, replacing #9175, is to bring straightforward, Fig-inspired stack composition to the Docker client. The ultimate goal is to provide an out-of-the-box Docker development experience that is:
- delightful
- eventually suitable for production orchestration in the “80% case”
- compatible with Docker clustering
(The previous proposal built on the docker groups
proposal, #8637. This proposal does not, as I've determined that - since groups aren't necessary for implementing composition - it's preferable to avoid making changes or additions to the Docker API.)
I’ve already implemented an alpha of the required functionality on my composition branch - though it’s not ready for prime time, everyone is very much encouraged to try it out, especially Fig users. Scroll down for test builds!
The basic idea of stack composition is that with a very simple configuration file which describes what containers you want your application to consist of, you can type a single command and Docker will do everything necessary to get it running.
Configuration file
A group.yml
is used to describe your application. It looks a lot like fig.yml
, except that what Fig calls the “project name” is specified explicitly:
name: rails_example
containers:
db:
image: postgres:latest
web:
build: .
command: bundle exec rackup -p 3000
volumes:
- .:/myapp
ports:
- "3000:3000"
links:
- db
A container entry must specify either an image to be pulled or a build directory (but not both). Other configuration options mostly map to their docker run
counterparts.
docker up
There’s a new docker up
command. It performs the following steps:
- Parse
group.yml
- For each defined container, check whether we have an image for it. If not, either build or pull it as needed.
- Create/recreate the defined containers. (For now, containers are always recreated if they already exist - this is the simplest way to ensure changes to
group.yml
are picked up.) - Start the containers in dependency order (based on
links
andvolumes_from
declarations ingroup.yml
). - Unless
docker up
was invoked with-d
, attach to all containers and stream their aggregated log output until the user sends Ctrl-C, at which point attempt to stop all containers with SIGTERM. Subsequent Ctrl-Cs result in a SIGKILL.
Enhancements to existing CLI commands
An optional NAME_PREFIX
argument is added to docker ps
to allow filtering of containers based on name prefix (on the client side, initially).
A new syntax is introduced as a shorthand for referring to containers, images and build directories:
:web
designates the container namedweb
ingroup.yml
.:
designates all containers defined ingroup.yml
. Depending on the exact command being invoked, this is restricted to containers which currently exist on the host, or those whose image has been built.
Here are some example commands with their longwinded/non-portable equivalents.
List our containers:
$ docker ps rails_example_
$ docker ps :
Rebuild the web image:
$ docker build -t rails_example_web .
$ docker build :web
Re-pull the db image:
$ docker pull postgres:latest
$ docker pull :db
Kill the web container:
$ docker kill rails_example_web
$ docker kill :web
Kill all containers:
$ docker kill rails_example_web rails_example_db
$ docker kill :
Kill and remove all containers:
$ docker rm -f rails_example_web rails_example_db
$ docker rm -f :
Delete the web image:
$ docker rmi rails_example_web
$ docker rmi :web
Open a bash shell in the web container:
$ docker exec -ti rails-example/web bash
$ docker exec :web bash
Run a one-off container using web
’s image and configuration:
$ docker build -t rails_example_web . && docker run -ti -v `pwd`:/myapp --link rails_example_db:db rails_example_web bash
$ docker run -ti :web bash
Topics for discussion: an inexhaustive list
Including the app name in the file. I’m unsure about making this the default - lots of Fig users want to be able to do it, but I’m worried that it’ll hurt portability (we don’t do it with Dockerfile, and in my opinion it’s better off for it). Alternate approaches include using the basename of the current directory (like Fig does), or generating a name and storing it in a separate, unversioned file.
Clustering and production. People are already deploying single-host production sites with fig up -d
, validating this general approach in simple scenarios, but we need to be sure that it’ll port well to a clustered Docker instance.
Scaling. I don't think an equivalent to fig scale
is necessary on day one, but it will eventually be needed as Docker becomes a multi-host platform, so there shouldn't be anything in the design that'll make that difficult to implement later.
Test builds
- linux/amd64: http://cl.ly/0Q3G1l2t301S/download/docker-1.3.2-dev-linux
- darwin/386: http://cl.ly/1J1h0p2Q0J2m/download/docker-1.3.2-dev-darwin-386
- darwin/amd64: http://cl.ly/1J1G323h3d3T/download/docker-1.3.2-dev-darwin-amd64
Here's how to test it out if you're running boot2docker. First, replace the binary in your VM:
$ boot2docker ssh
docker@boot2docker:~$ sudo -i
root@boot2docker:~# curl -LO http://cl.ly/0Q3G1l2t301S/download/docker-1.3.2-dev-linux
root@boot2docker:~# /etc/init.d/docker stop
root@boot2docker:~# mv /usr/local/bin/docker ./docker-stable
root@boot2docker:~# mv ./docker-1.3.2-dev-linux /usr/local/bin/docker
root@boot2docker:~# chmod +x /usr/local/bin/docker
root@boot2docker:~# /etc/init.d/docker start
root@boot2docker:~# docker version # both "Git commit"s should be c6bf574
root@boot2docker:~# exit
docker@boot2docker:~$ exit
Next, replace your client binary:
$ curl -LO http://cl.ly/1J1G323h3d3T/download/docker-1.3.2-dev-darwin-amd64
$ mv /usr/local/bin/docker ./docker-stable
$ mv ./docker-1.3.2-dev-darwin-amd64 /usr/local/bin/docker
$ chmod +x /usr/local/bin/docker
$ docker version # both "Git commit"s should be c6bf574
Not yet implemented
There are a few things left to implement:
- Supporting
volumes_from
- Validation of the YAML file
- Code cleanup
- Test coverage
Example app 1: Python/Redis counter
Here’s a sample app you can try:
app.py:
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host="redis", port=6379)
@app.route('/')
def hello():
redis.incr('hits')
return 'Hello World! I have been seen %s times.' % redis.get('hits')
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
requirements.txt:
flask
redis
Dockerfile:
FROM python:2.7
ADD . /code
WORKDIR /code
RUN pip install -r requirements.txt
group.yml:
name: counter
containers:
web:
build: .
command: python app.py
ports:
- "5000:5000"
volumes:
- .:/code
links:
- redis
environment:
- PYTHONUNBUFFERED=1
redis:
image: redis:latest
command: redis-server --appendonly yes
If you put those four files in a directory and type docker up
, you should see everything start up:
It'll build the web image, pull the redis image, start both containers and stream their aggregated output. If you Ctrl-C, it'll shut them down.
Example app 2: Fresh Rails app
I’ve ported the Rails example from Fig. See composing_rails.md.
Code
To get hacking, check out the composition
branch on my fork:
# if you don’t already have Docker cloned
$ git clone git@github.com:docker/docker
$ cd docker
$ git remote add aanand git@github.com:aanand/docker.git
$ git fetch --all
$ git checkout -b composition aanand/composition
Activity
thaJeztah commentedon Dec 2, 2014
Third time's a charm? :)
In think my comments in the previous proposal still apply;
Dockerstack.yml
? (Proposal: stack composition #9175 (comment))Thanks (again), will give these builds a try soon.
andrewmichaelsmith commentedon Dec 4, 2014
As has been discussed in fig issue #159 are there plans to allow for sharing (in this example) redis between YAML files? So I can have 2 applications that both get the same redis when I
docker up
?aanand commentedon Dec 4, 2014
@andrewmichaelsmith I agree that we should support links to existing containers. This should actually work in the test build (though I haven't stress-tested it much) - try prepending a link with a slash, e.g.
Zooming out, ideally there'd be a way to do "dependency injection" of containers, so I could e.g. specify a stock redis container in
group.yml
and then override it in production so it points to my already-running, separately-managed Redis instance.sbuss commentedon Dec 4, 2014
tl;dr
Are there use cases for referencing the app name besides building test or dev containers? If not, I think you should keep the current fig behavior, but also support referencing other containers in a
FROM :web
style. If you don't add support forFROM :web
, then I do support adding anapp_name
key to the yaml file, since that removes broken behavior when working in differently-named directories.I'd love to do
FROM :web
to build a tests-only container, rather than have tofig build web
&&FROM myappdir_web
.Using fig for building testing containers
I'm currently using fig to build a container for running tests, which requires knowing the basename of the current directory. This is problematic if devs check out the code into differently-named directories. Let me show you an example, using the sample python app you provided in the ticket description.
In addition to the Dockerfile and fig.yml you provide above, I also have a
dockerfiles/test
directory which contains the following Dockerfile:requirements-dev.txt:
And I add these lines to
fig.yml
:This lets me do:
fig run test nosetests
, and lets me avoid polluting the production container with my development dependencies. However, it also means that before running the tests I have to dofig build web
so themyapp_web
tag will get created.The detail to note is
FROM myapp_web
, which is of the format<myapp_basedir>_<fig_base_container_name>
. I'd love to do something likeFROM :web
. Or, if support for that will not be added, then I'd at least like to be independent on the directory name. A nice bonus would be allowing me to runfig run test
without first building base (I'd likefigdocker composition to just figure that out).I haven't found a good pattern for building containers for testing, so please let me know if you know of a better pattern that sidesteps these issues without adding new features.
ahawkins commentedon Dec 4, 2014
@sbuss Fig previously supported
--projectname
(or--project-name
) Can't remember right now off the top of my head. I codified this in the projectMakefile
with something likeFIG:=fig --project-name foo
, then use$(FIG)
everywhere. If you're invokingfig
through the shell itself, there is an environment variable you can use as well. All in all I've found the directory scoping things very annoying in general. I'd like to opt out of it completely since I run everything inside a VM anyways, so no collisions can happen regardless.@aanand I previously used fig for development environments for my team. I switched away because it did not expose docker's full functionality or made it awkward in places. The previous paragraph also speaks to this. Does your implementation also postfix everything with
_N
e.g.some_container_1
? Also does your implementation also remove underscores from containers/group names? This may be a small gripe but I found it be very annoying if you ever wanted to do something outside fig's context. There were times when I wanted to start a one off docker container viadocker run
but had to link to something created with fig. This requires you to know two things: the namespace may change (if not using--project-name
as mentioned above), that container name is not what's listed in the config files. Of course you learn these things but it's a strange hurdle to jump and introduces odd dependencies between different things. Does your proposal include referencing containers for use with--link
? e.g.--link :posgres:db
. If not I think you should certainly consider it. That abstraction should stretch out to all docker aspects of the docker CLI. Also, does this mean docker groups will be built into docker officially and use the same internal APIs as the CLI? I also ran into problems with fig where the API it communicated with did use the same configuration as the daemon (--insecure-registry
). Will such concerns go away in your implementation?deniszgonjanin commentedon Dec 4, 2014
This is awesome!
I would say keep the
name:
optional and use directory as default, like fig does. But it's definitely a needed feature. One thing I don't like about fig is that I am not able to choose what my images are named aside from renaming directories.And I don't want to be a killjoy here but, this really is just fig. Re-implemented in Go and looking to find its way into the core. So my preference, as a heavy fig & Docker user, would be to keep this stuff separate.
tomfotherby commentedon Dec 4, 2014
In the argument as to whether to have compose included in the docker binary or separate, I vote included. I've been using fig and found myself wishing I could do
docker up
instead offig up
.Other thoughts:
--env-file
option in compose from the beginning. Having a file for environment variables is a nice way to tweak the container config instead of having to tweak thefig.yml/group.yml
files directly, especially for teams where some settings are individual (e.g. a catchall email address for the dev container). (fig issue: Support for --env-file docker/compose#479)_1
to the container name really annoying, especially as I don't need the scale option. It would be great if compose didn't do the same. and when a scale option is implemented, subsequent copies can start from_2
onwards.As well as having a file ((Not valid - thanks for the tip @itorres )group.yml
) whichdocker up
magically knows to use, it would be great to also be able to provide the file directly, e.g.docker up myconfig.yml
. I have a folder with several fig.yml files and they each have to be in their own sub-folder to be usable, which is a little annoying.olimart commentedon Dec 4, 2014
@aanand 👍
itorres commentedon Dec 4, 2014
@tomfotherby: about the fig.yml per directory I think you're missing this fig option:
I'm not sure that I really like the idea of including composition in the main docker binary. I see composition/fig as a (very) convenient rather than core functionality (very as in "I don't really use docker without fig nowadays").
potto007 commentedon Dec 4, 2014
I vote to keep Compose separate. It will be much easier to add the hooks necessary to do cluster management between Compose and Swarm without risking regressions to Docker. Commands like 'docker up' could be added when Swarm is installed onto a system where Docker resides - ie: shell aliases.
weihanwang commentedon Dec 5, 2014
I vote to keep Compose separate, since its functions are largely independent from the core and bear quite different responsibilities. Modularity would make maintenance (of both the core & Compose) and third-party integration easier. So far I'm not aware of strong technical reasons against separation.
legdba commentedon Dec 5, 2014
Please keep it separate.
It's best having different tools, each with a clear and simple goal.
This will make life of users better by making each tool purpose simple to understand and letting them choose which tools to use for each purpose (several projects are working on composition already). This will make life of coders easier by reducing test/scope surface and dependencies.
chenzhiwei commentedon Dec 5, 2014
I agree to separate it. Just like git-review, if you installed it, you can use either
git review
orgit-review
.48 remaining items