From b1a1c6a2345f05e3905e908e00abc81c8374a4db Mon Sep 17 00:00:00 2001
From: Joffrey F <joffrey@docker.com>
Date: Thu, 26 Apr 2018 15:20:45 -0700
Subject: [PATCH] Prevent duplicate binds in generated container config

Signed-off-by: Joffrey F <joffrey@docker.com>
---
 compose/service.py         |  7 ++++---
 tests/unit/service_test.py | 19 +++++++++++++++++++
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/compose/service.py b/compose/service.py
index 0a866161c..ae9e0bb08 100644
--- a/compose/service.py
+++ b/compose/service.py
@@ -877,7 +877,6 @@ class Service(object):
             container_volumes, self.options.get('tmpfs') or [], previous_container,
             container_mounts
         )
-        override_options['binds'] = binds
         container_options['environment'].update(affinity)
 
         container_options['volumes'] = dict((v.internal, {}) for v in container_volumes or {})
@@ -890,13 +889,13 @@ class Service(object):
                 if m.is_tmpfs:
                     override_options['tmpfs'].append(m.target)
                 else:
-                    override_options['binds'].append(m.legacy_repr())
+                    binds.append(m.legacy_repr())
                     container_options['volumes'][m.target] = {}
 
         secret_volumes = self.get_secret_volumes()
         if secret_volumes:
             if version_lt(self.client.api_version, '1.30'):
-                override_options['binds'].extend(v.legacy_repr() for v in secret_volumes)
+                binds.extend(v.legacy_repr() for v in secret_volumes)
                 container_options['volumes'].update(
                     (v.target, {}) for v in secret_volumes
                 )
@@ -904,6 +903,8 @@ class Service(object):
                 override_options['mounts'] = override_options.get('mounts') or []
                 override_options['mounts'].extend([build_mount(v) for v in secret_volumes])
 
+        # Remove possible duplicates (see e.g. https://github.com/docker/compose/issues/5885)
+        override_options['binds'] = list(set(binds))
         return container_options, override_options
 
     def _get_container_host_config(self, override_options, one_off=False):
diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py
index 4ccc48653..d50db9044 100644
--- a/tests/unit/service_test.py
+++ b/tests/unit/service_test.py
@@ -10,6 +10,7 @@ from docker.errors import NotFound
 from .. import mock
 from .. import unittest
 from compose.config.errors import DependencyError
+from compose.config.types import MountSpec
 from compose.config.types import ServicePort
 from compose.config.types import ServiceSecret
 from compose.config.types import VolumeFromSpec
@@ -955,6 +956,24 @@ class ServiceTest(unittest.TestCase):
 
         assert service.create_container().id == 'new_cont_id'
 
+    def test_build_volume_options_duplicate_binds(self):
+        self.mock_client.api_version = '1.29'  # Trigger 3.2 format workaround
+        service = Service('foo', client=self.mock_client)
+        ctnr_opts, override_opts = service._build_container_volume_options(
+            previous_container=None,
+            container_options={
+                'volumes': [
+                    MountSpec.parse({'source': 'vol', 'target': '/data', 'type': 'volume'}),
+                    VolumeSpec.parse('vol:/data:rw'),
+                ],
+                'environment': {},
+            },
+            override_options={},
+        )
+        assert 'binds' in override_opts
+        assert len(override_opts['binds']) == 1
+        assert override_opts['binds'][0] == 'vol:/data:rw'
+
 
 class TestServiceNetwork(unittest.TestCase):
     def setUp(self):