Skip to content

Proposal: stack composition #9459

Closed
Closed
@aanand

Description

@aanand

⚠️ 👉 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:

  1. delightful
  2. eventually suitable for production orchestration in the “80% case”
  3. 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:

  1. Parse group.yml
  2. For each defined container, check whether we have an image for it. If not, either build or pull it as needed.
  3. 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.)
  4. Start the containers in dependency order (based on links and volumes_from declarations in group.yml).
  5. 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 named web in group.yml.
  • : designates all containers defined in group.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

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:

docker up example

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

thaJeztah commented on Dec 2, 2014

@thaJeztah
Member

Third time's a charm? :)

In think my comments in the previous proposal still apply;

  1. Since we're defining a "stack" not a "group", perhaps the file should be called Dockerstack.yml? (Proposal: stack composition #9175 (comment))
  2. For naming and possibly multiple instances of a stack (Proposal: stack composition #9175 (comment))

Thanks (again), will give these builds a try soon.

andrewmichaelsmith

andrewmichaelsmith commented on Dec 4, 2014

@andrewmichaelsmith

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

aanand commented on Dec 4, 2014

@aanand
ContributorAuthor

@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.

links:
  - /external-redis:redis

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

sbuss commented on Dec 4, 2014

@sbuss

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.

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 for FROM :web, then I do support adding an app_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 to fig 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:

FROM myapp_web
ADD requirements-dev.txt /srv/myapp
RUN pip install -r requirements-dev.txt

requirements-dev.txt:

nosetests

And I add these lines to fig.yml:

test:
    build: dockerfiles/test
    environment:
        DEBUG: True

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 do fig build web so the myapp_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 like FROM :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 run fig run test without first building base (I'd like fig docker 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

ahawkins commented on Dec 4, 2014

@ahawkins

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.

@sbuss Fig previously supported --projectname (or --project-name) Can't remember right now off the top of my head. I codified this in the project Makefile with something like FIG:=fig --project-name foo, then use $(FIG) everywhere. If you're invoking fig 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 via docker 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

deniszgonjanin commented on Dec 4, 2014

@deniszgonjanin

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.

  • It's mostly syntactic sugar.
  • It already exists as a healthy and active open source project
  • Fig is already governed by Docker, so it's direction is not an issue. Any desired improvements can find their way into fig instead.
  • The direction we've been hearing about is towards a tight and modular core.
tomfotherby

tomfotherby commented on Dec 4, 2014

@tomfotherby
Contributor

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 of fig up.

Other thoughts:

  • It would be great to support the --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 the fig.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)
  • I find fig adding _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 (group.yml) which docker 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. (Not valid - thanks for the tip @itorres )
olimart

olimart commented on Dec 4, 2014

@olimart

@aanand 👍

itorres

itorres commented on Dec 4, 2014

@itorres

@tomfotherby: about the fig.yml per directory I think you're missing this fig option:

$ fig -h
[...]
-f, --file FILE           Specify an alternate fig file (default: fig.yml)

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

potto007 commented on Dec 4, 2014

@potto007

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

weihanwang commented on Dec 5, 2014

@weihanwang

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

legdba commented on Dec 5, 2014

@legdba

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

chenzhiwei commented on Dec 5, 2014

@chenzhiwei

I agree to separate it. Just like git-review, if you installed it, you can use either git review or git-review.

48 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/stackkind/featureFunctionality or other elements that the project doesn't currently have. Features are new and shiny

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @aanand@softprops@Peeja@pkieltyka@x4lldux

        Issue actions

          Proposal: stack composition · Issue #9459 · moby/moby