From a8e275a4321c4bf743a88793ed9f14cd905df053 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 12:01:17 +0000 Subject: [PATCH 01/14] Implement UserError __unicode__ method --- fig/cli/errors.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fig/cli/errors.py b/fig/cli/errors.py index bb1702fda6..874d35918b 100644 --- a/fig/cli/errors.py +++ b/fig/cli/errors.py @@ -5,3 +5,6 @@ from textwrap import dedent class UserError(Exception): def __init__(self, msg): self.msg = dedent(msg).strip() + + def __unicode__(self): + return self.msg From 3c5e334d9d0c9c20f825ee8a16385d4718cfabb9 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Fri, 3 Jan 2014 11:18:59 +0000 Subject: [PATCH 02/14] Add recreate_containers method to service --- fig/project.py | 7 +++++++ fig/service.py | 19 +++++++++++++++++++ tests/service_test.py | 7 +++++++ 3 files changed, 33 insertions(+) diff --git a/fig/project.py b/fig/project.py index 7c05b2c738..95d13ee9e6 100644 --- a/fig/project.py +++ b/fig/project.py @@ -87,6 +87,13 @@ class Project(object): if len(service.containers(stopped=True)) == 0: service.create_container() + def recreate_containers(self, service_names): + """ + For each service, create or recreate their containers. + """ + for service in self.get_services(service_names): + service.recreate_containers() + def start(self, service_names=None, **options): for service in self.get_services(service_names): service.start(**options) diff --git a/fig/service.py b/fig/service.py index 5b7b7663e5..9ae47a36e8 100644 --- a/fig/service.py +++ b/fig/service.py @@ -73,6 +73,25 @@ class Service(object): return Container.create(self.client, **container_options) raise + def recreate_containers(self, **override_options): + """ + If a container for this service doesn't exist, create one. If there are + any, stop, remove and recreate them. + """ + containers = self.containers(stopped=True) + if len(containers) == 0: + return [self.create_container(**override_options)] + else: + new_containers = [] + for old_container in containers: + if old_container.is_running: + old_container.stop() + options = dict(override_options) + options['volumes_from'] = old_container.id + new_containers.append(self.create_container(**options)) + old_container.remove() + return new_containers + def start_container(self, container=None, **override_options): if container is None: container = self.create_container(**override_options) diff --git a/tests/service_test.py b/tests/service_test.py index d2078414aa..202c5e2641 100644 --- a/tests/service_test.py +++ b/tests/service_test.py @@ -102,6 +102,13 @@ class ServiceTest(DockerClientTestCase): container = db.create_container(one_off=True) self.assertEqual(container.name, 'figtest_db_run_1') + def test_recreate_containers(self): + service = self.create_service('db') + container = service.create_container() + new_container = service.recreate_containers()[0] + self.assertEqual(len(service.containers(stopped=True)), 1) + self.assertNotEqual(container.id, new_container.id) + def test_start_container_passes_through_options(self): db = self.create_service('db') db.start_container(environment={'FOO': 'BAR'}) From 207e83ac2f5345e3ecc7e4ff8f282284cb2f7d83 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 12:22:55 +0000 Subject: [PATCH 03/14] Be sure to test that recreate_containers updates config --- tests/service_test.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/service_test.py b/tests/service_test.py index 202c5e2641..ea7db0ca40 100644 --- a/tests/service_test.py +++ b/tests/service_test.py @@ -103,9 +103,14 @@ class ServiceTest(DockerClientTestCase): self.assertEqual(container.name, 'figtest_db_run_1') def test_recreate_containers(self): - service = self.create_service('db') + service = self.create_service('db', environment={'FOO': '1'}) container = service.create_container() + self.assertEqual(container.dictionary['Config']['Env'], ['FOO=1']) + + service.options['environment']['FOO'] = '2' new_container = service.recreate_containers()[0] + self.assertEqual(new_container.dictionary['Config']['Env'], ['FOO=2']) + self.assertEqual(len(service.containers(stopped=True)), 1) self.assertNotEqual(container.id, new_container.id) From 3669236aa18b234b20b622c818662eb6f52592a5 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 12:43:40 +0000 Subject: [PATCH 04/14] Support volumes in config with an unspecified host path --- fig/service.py | 18 +++++++++++++++--- tests/service_test.py | 6 ++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/fig/service.py b/fig/service.py index 9ae47a36e8..d16ab95664 100644 --- a/fig/service.py +++ b/fig/service.py @@ -114,8 +114,9 @@ class Service(object): if options.get('volumes', None) is not None: for volume in options['volumes']: - external_dir, internal_dir = volume.split(':') - volume_bindings[os.path.abspath(external_dir)] = internal_dir + if ':' in volume: + external_dir, internal_dir = volume.split(':') + volume_bindings[os.path.abspath(external_dir)] = internal_dir container.start( links=self._get_links(), @@ -162,7 +163,7 @@ class Service(object): container_options['ports'] = ports if 'volumes' in container_options: - container_options['volumes'] = dict((v.split(':')[1], {}) for v in container_options['volumes']) + container_options['volumes'] = dict((split_volume(v)[1], {}) for v in container_options['volumes']) if self.can_be_built(): if len(self.client.images(name=self._build_tag_name())) == 0: @@ -233,3 +234,14 @@ def get_container_name(container): for name in container['Names']: if len(name.split('/')) == 2: return name[1:] + + +def split_volume(v): + """ + If v is of the format EXTERNAL:INTERNAL, returns (EXTERNAL, INTERNAL). + If v is of the format INTERNAL, returns (None, INTERNAL). + """ + if ':' in v: + return v.split(':', 1) + else: + return (None, v) diff --git a/tests/service_test.py b/tests/service_test.py index ea7db0ca40..c59b4ebb11 100644 --- a/tests/service_test.py +++ b/tests/service_test.py @@ -102,6 +102,12 @@ class ServiceTest(DockerClientTestCase): container = db.create_container(one_off=True) self.assertEqual(container.name, 'figtest_db_run_1') + def test_create_container_with_unspecified_volume(self): + service = self.create_service('db', volumes=['/var/db']) + container = service.create_container() + service.start_container(container) + self.assertIn('/var/db', container.inspect()['Volumes']) + def test_recreate_containers(self): service = self.create_service('db', environment={'FOO': '1'}) container = service.create_container() From bdc6b47e1f8a0d9aafef7cb3a0b261b7f0f8bf4f Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 13:06:25 +0000 Subject: [PATCH 05/14] service.recreate_containers() no longer removes the old containers We need to keep them around until the new ones have been started. --- fig/service.py | 11 +++++------ tests/service_test.py | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/fig/service.py b/fig/service.py index d16ab95664..c6396d8c70 100644 --- a/fig/service.py +++ b/fig/service.py @@ -76,21 +76,20 @@ class Service(object): def recreate_containers(self, **override_options): """ If a container for this service doesn't exist, create one. If there are - any, stop, remove and recreate them. + any, stop them and create new ones. Does not remove the old containers. """ - containers = self.containers(stopped=True) - if len(containers) == 0: + old_containers = self.containers(stopped=True) + if len(old_containers) == 0: return [self.create_container(**override_options)] else: new_containers = [] - for old_container in containers: + for old_container in old_containers: if old_container.is_running: old_container.stop() options = dict(override_options) options['volumes_from'] = old_container.id new_containers.append(self.create_container(**options)) - old_container.remove() - return new_containers + return (old_containers, new_containers) def start_container(self, container=None, **override_options): if container is None: diff --git a/tests/service_test.py b/tests/service_test.py index c59b4ebb11..ee2e90933c 100644 --- a/tests/service_test.py +++ b/tests/service_test.py @@ -109,16 +109,24 @@ class ServiceTest(DockerClientTestCase): self.assertIn('/var/db', container.inspect()['Volumes']) def test_recreate_containers(self): - service = self.create_service('db', environment={'FOO': '1'}) - container = service.create_container() - self.assertEqual(container.dictionary['Config']['Env'], ['FOO=1']) + service = self.create_service('db', environment={'FOO': '1'}, volumes=['/var/db']) + old_container = service.create_container() + self.assertEqual(old_container.dictionary['Config']['Env'], ['FOO=1']) + service.start_container(old_container) + volume_path = old_container.inspect()['Volumes']['/var/db'] service.options['environment']['FOO'] = '2' - new_container = service.recreate_containers()[0] - self.assertEqual(new_container.dictionary['Config']['Env'], ['FOO=2']) + (old, new) = service.recreate_containers() + self.assertEqual(old, [old_container]) + self.assertEqual(len(new), 1) - self.assertEqual(len(service.containers(stopped=True)), 1) - self.assertNotEqual(container.id, new_container.id) + new_container = new[0] + self.assertEqual(new_container.dictionary['Config']['Env'], ['FOO=2']) + service.start_container(new_container) + self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path) + + self.assertEqual(len(service.containers(stopped=True)), 2) + self.assertNotEqual(old_container.id, new_container.id) def test_start_container_passes_through_options(self): db = self.create_service('db') From f5f93577366ac1cfdbb7ea803c157959aa9a7009 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 13:17:39 +0000 Subject: [PATCH 06/14] Remove project.create_containers(), revamp project.recreate_containers() `recreate_containers` now returns two lists of old+new containers, along with their services. --- fig/project.py | 22 ++++++++++++---------- fig/service.py | 2 +- tests/project_test.py | 13 +++++++++---- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/fig/project.py b/fig/project.py index 95d13ee9e6..f77da5f7d6 100644 --- a/fig/project.py +++ b/fig/project.py @@ -79,20 +79,22 @@ class Project(object): unsorted = [self.get_service(name) for name in service_names] return [s for s in self.services if s in unsorted] - def create_containers(self, service_names=None): - """ - For each service, creates a container if there are none. - """ - for service in self.get_services(service_names): - if len(service.containers(stopped=True)) == 0: - service.create_container() - - def recreate_containers(self, service_names): + def recreate_containers(self, service_names=None): """ For each service, create or recreate their containers. + Returns a tuple with two lists. The first is a list of + (service, old_container) tuples; the second is a list + of (service, new_container) tuples. """ + old = [] + new = [] + for service in self.get_services(service_names): - service.recreate_containers() + (s_old, s_new) = service.recreate_containers() + old += [(service, container) for container in s_old] + new += [(service, container) for container in s_new] + + return (old, new) def start(self, service_names=None, **options): for service in self.get_services(service_names): diff --git a/fig/service.py b/fig/service.py index c6396d8c70..91a89791d1 100644 --- a/fig/service.py +++ b/fig/service.py @@ -80,7 +80,7 @@ class Service(object): """ old_containers = self.containers(stopped=True) if len(old_containers) == 0: - return [self.create_container(**override_options)] + return ([], [self.create_container(**override_options)]) else: new_containers = [] for old_container in old_containers: diff --git a/tests/project_test.py b/tests/project_test.py index 09792fabdd..bad9f612b6 100644 --- a/tests/project_test.py +++ b/tests/project_test.py @@ -42,17 +42,22 @@ class ProjectTest(DockerClientTestCase): project = Project('test', [web], self.client) self.assertEqual(project.get_service('web'), web) - def test_create_containers(self): + def test_recreate_containers(self): web = self.create_service('web') db = self.create_service('db') project = Project('test', [web, db], self.client) - project.create_containers(service_names=['web']) + old_web_container = web.create_container() self.assertEqual(len(web.containers(stopped=True)), 1) self.assertEqual(len(db.containers(stopped=True)), 0) - project.create_containers() - self.assertEqual(len(web.containers(stopped=True)), 1) + (old, new) = project.recreate_containers() + self.assertEqual(old, [(web, old_web_container)]) + self.assertEqual(len(new), 2) + self.assertEqual(new[0][0], web) + self.assertEqual(new[1][0], db) + + self.assertEqual(len(web.containers(stopped=True)), 2) self.assertEqual(len(db.containers(stopped=True)), 1) def test_start_stop_kill_remove(self): From 5db6c9f51ba045220ec1c8f3e12e6fbe620cf3d4 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 13:25:40 +0000 Subject: [PATCH 07/14] Rework 'fig up' to use recreate_containers() --- fig/cli/main.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/fig/cli/main.py b/fig/cli/main.py index d2d0af156d..51c4d27fb3 100644 --- a/fig/cli/main.py +++ b/fig/cli/main.py @@ -220,14 +220,18 @@ class TopLevelCommand(Command): """ detached = options['-d'] - self.project.create_containers(service_names=options['SERVICE']) - containers = self.project.containers(service_names=options['SERVICE'], stopped=True) + (old, new) = self.project.recreate_containers(service_names=options['SERVICE']) if not detached: - print("Attaching to", list_containers(containers)) - log_printer = LogPrinter(containers) + to_attach = [c for (s, c) in new] + print("Attaching to", list_containers(to_attach)) + log_printer = LogPrinter(to_attach) - self.project.start(service_names=options['SERVICE']) + for (service, container) in new: + service.start_container(container) + + for (service, container) in old: + container.remove() if not detached: try: From 8a0071d9c150a3b11ac8ee5f8cc05601963bce9f Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 15:51:39 +0000 Subject: [PATCH 08/14] Reduce stop() timeout when recreating containers --- fig/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fig/service.py b/fig/service.py index 91a89791d1..e81ec810a6 100644 --- a/fig/service.py +++ b/fig/service.py @@ -85,7 +85,7 @@ class Service(object): new_containers = [] for old_container in old_containers: if old_container.is_running: - old_container.stop() + old_container.stop(timeout=1) options = dict(override_options) options['volumes_from'] = old_container.id new_containers.append(self.create_container(**options)) From 3956d85a8c2a8ce0ab8edfd70dc474fda6b88b4b Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 16:15:46 +0000 Subject: [PATCH 09/14] Refactor recreate_containers() in preparation for smart name-preserving logic --- fig/service.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/fig/service.py b/fig/service.py index e81ec810a6..d7e2fff315 100644 --- a/fig/service.py +++ b/fig/service.py @@ -78,19 +78,30 @@ class Service(object): If a container for this service doesn't exist, create one. If there are any, stop them and create new ones. Does not remove the old containers. """ - old_containers = self.containers(stopped=True) - if len(old_containers) == 0: + containers = self.containers(stopped=True) + + if len(containers) == 0: return ([], [self.create_container(**override_options)]) else: + old_containers = [] new_containers = [] - for old_container in old_containers: - if old_container.is_running: - old_container.stop(timeout=1) - options = dict(override_options) - options['volumes_from'] = old_container.id - new_containers.append(self.create_container(**options)) + + for c in containers: + (old_container, new_container) = self.recreate_container(c, **override_options) + old_containers.append(old_container) + new_containers.append(new_container) + return (old_containers, new_containers) + def recreate_container(self, container, **override_options): + if container.is_running: + container.stop(timeout=1) + + options = dict(override_options) + options['volumes_from'] = container.id + + return (container, self.create_container(**options)) + def start_container(self, container=None, **override_options): if container is None: container = self.create_container(**override_options) From ea4753c49a95c9f2cedb9c00081499b17898d6b8 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 17:06:16 +0000 Subject: [PATCH 10/14] Use an anonymous intermediate container so that when recreating containers, suffixes always start from 1 --- fig/service.py | 17 ++++++++++++++--- tests/service_test.py | 8 ++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/fig/service.py b/fig/service.py index d7e2fff315..c9a36499f1 100644 --- a/fig/service.py +++ b/fig/service.py @@ -97,10 +97,21 @@ class Service(object): if container.is_running: container.stop(timeout=1) - options = dict(override_options) - options['volumes_from'] = container.id + intermediate_container = Container.create( + self.client, + image='ubuntu', + command='echo', + volumes_from=container.id, + ) + intermediate_container.start() + intermediate_container.wait() + container.remove() - return (container, self.create_container(**options)) + options = dict(override_options) + options['volumes_from'] = intermediate_container.id + new_container = self.create_container(**options) + + return (intermediate_container, new_container) def start_container(self, container=None, **override_options): if container is None: diff --git a/tests/service_test.py b/tests/service_test.py index ee2e90933c..2ccf17555b 100644 --- a/tests/service_test.py +++ b/tests/service_test.py @@ -112,20 +112,24 @@ class ServiceTest(DockerClientTestCase): service = self.create_service('db', environment={'FOO': '1'}, volumes=['/var/db']) old_container = service.create_container() self.assertEqual(old_container.dictionary['Config']['Env'], ['FOO=1']) + self.assertEqual(old_container.name, 'figtest_db_1') service.start_container(old_container) volume_path = old_container.inspect()['Volumes']['/var/db'] + num_containers_before = len(self.client.containers(all=True)) + service.options['environment']['FOO'] = '2' (old, new) = service.recreate_containers() - self.assertEqual(old, [old_container]) + self.assertEqual(len(old), 1) self.assertEqual(len(new), 1) new_container = new[0] self.assertEqual(new_container.dictionary['Config']['Env'], ['FOO=2']) + self.assertEqual(new_container.name, 'figtest_db_1') service.start_container(new_container) self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path) - self.assertEqual(len(service.containers(stopped=True)), 2) + self.assertEqual(len(self.client.containers(all=True)), num_containers_before + 1) self.assertNotEqual(old_container.id, new_container.id) def test_start_container_passes_through_options(self): From 8c583d1bb2f53f336437bb89821093c258cc21e6 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Wed, 15 Jan 2014 18:06:49 +0000 Subject: [PATCH 11/14] Quieter log output when recreating Moved log stuff to Service, which I think makes more sense anyway. Maybe. --- fig/container.py | 7 ------- fig/service.py | 6 ++++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/fig/container.py b/fig/container.py index 9556ec1f6f..24b239ac69 100644 --- a/fig/container.py +++ b/fig/container.py @@ -1,8 +1,5 @@ from __future__ import unicode_literals from __future__ import absolute_import -import logging - -log = logging.getLogger(__name__) class Container(object): """ @@ -91,19 +88,15 @@ class Container(object): return self.dictionary['State']['Running'] def start(self, **options): - log.info("Starting %s..." % self.name) return self.client.start(self.id, **options) def stop(self, **options): - log.info("Stopping %s..." % self.name) return self.client.stop(self.id, **options) def kill(self): - log.info("Killing %s..." % self.name) return self.client.kill(self.id) def remove(self): - log.info("Removing %s..." % self.name) return self.client.remove_container(self.id) def inspect_if_not_inspected(self): diff --git a/fig/service.py b/fig/service.py index c9a36499f1..e730041013 100644 --- a/fig/service.py +++ b/fig/service.py @@ -43,19 +43,23 @@ class Service(object): def start(self, **options): for c in self.containers(stopped=True): if not c.is_running: + log.info("Starting %s..." % c.name) self.start_container(c, **options) def stop(self, **options): for c in self.containers(): + log.info("Stopping %s..." % c.name) c.stop(**options) def kill(self, **options): for c in self.containers(): + log.info("Killing %s..." % c.name) c.kill(**options) def remove_stopped(self, **options): for c in self.containers(stopped=True): if not c.is_running: + log.info("Removing %s..." % c.name) c.remove(**options) def create_container(self, one_off=False, **override_options): @@ -81,12 +85,14 @@ class Service(object): containers = self.containers(stopped=True) if len(containers) == 0: + log.info("Creating %s..." % self.next_container_name()) return ([], [self.create_container(**override_options)]) else: old_containers = [] new_containers = [] for c in containers: + log.info("Recreating %s..." % c.name) (old_container, new_container) = self.recreate_container(c, **override_options) old_containers.append(old_container) new_containers.append(new_container) From ee0c4bf690a721d205849b780fb48b191da39dd2 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 16 Jan 2014 11:56:02 +0000 Subject: [PATCH 12/14] Fix test regression --- tests/project_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/project_test.py b/tests/project_test.py index bad9f612b6..a73aa25276 100644 --- a/tests/project_test.py +++ b/tests/project_test.py @@ -52,12 +52,13 @@ class ProjectTest(DockerClientTestCase): self.assertEqual(len(db.containers(stopped=True)), 0) (old, new) = project.recreate_containers() - self.assertEqual(old, [(web, old_web_container)]) + self.assertEqual(len(old), 1) + self.assertEqual(old[0][0], web) self.assertEqual(len(new), 2) self.assertEqual(new[0][0], web) self.assertEqual(new[1][0], db) - self.assertEqual(len(web.containers(stopped=True)), 2) + self.assertEqual(len(web.containers(stopped=True)), 1) self.assertEqual(len(db.containers(stopped=True)), 1) def test_start_stop_kill_remove(self): From e38b403b148b5b985d7dfc6965cd1adce566adae Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 16 Jan 2014 12:06:50 +0000 Subject: [PATCH 13/14] Update README for new 'fig up' behaviour --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e2e87f7553..f913949729 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Run `fig [COMMAND] --help` for full usage. Build or rebuild services. -Services are built once and then tagged as `project_service`. If you change a service's `Dockerfile` or its configuration in `fig.yml`, you will probably need to run `fig build` to rebuild it, then run `fig rm` to make `fig up` recreate your containers. +Services are built once and then tagged as `project_service`, e.g. `figtest_db`. If you change a service's `Dockerfile` or the contents of its build directory, you can run `fig build` to rebuild it. #### kill @@ -237,9 +237,11 @@ Stop running containers without removing them. They can be started again with `f #### up -Build, create, start and attach to containers for a service. +Build, (re)create, start and attach to containers for a service. -If there are stopped containers for a service, `fig up` will start those again instead of creating new containers. When it exits, the containers it started will be stopped. This means if you want to recreate containers, you will need to explicitly run `fig rm`. +By default, `fig up` will aggregate the output of each container, and when it exits, all containers will be stopped. If you run `fig up -d`, it'll start the containers in the background and leave them running. + +If there are existing containers for a service, `fig up` will stop and recreate them, so that changes in `fig.yml` are picked up. ### Environment variables From 7b31fdf6f60de481c9d1458fefc2e0e206ea1279 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Thu, 16 Jan 2014 12:41:18 +0000 Subject: [PATCH 14/14] Clarify that volumes are preserved when recreating containers --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f913949729..2f9599a8b9 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ Build, (re)create, start and attach to containers for a service. By default, `fig up` will aggregate the output of each container, and when it exits, all containers will be stopped. If you run `fig up -d`, it'll start the containers in the background and leave them running. -If there are existing containers for a service, `fig up` will stop and recreate them, so that changes in `fig.yml` are picked up. +If there are existing containers for a service, `fig up` will stop and recreate them (preserving mounted volumes with [volumes-from]), so that changes in `fig.yml` are picked up. ### Environment variables @@ -267,3 +267,4 @@ Fully qualified container name, e.g. `MYAPP_DB_1_NAME=/myapp_web_1/myapp_db_1` [Docker links]: http://docs.docker.io/en/latest/use/port_redirection/#linking-a-container +[volumes-from]: http://docs.docker.io/en/latest/use/working_with_volumes/