diff --git a/README.md b/README.md
index 1d5f1d3b..c7f4c432 100644
--- a/README.md
+++ b/README.md
@@ -27,18 +27,14 @@ Identical to the `docker ps` command.
* `c.copy(container, resource)`
Identical to the `docker cp` command.
-* c.create_container(image, command=None, hostname=None, user=None, detach=False,
- stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None,
- dns=None, volumes=None, volumes_from=None, privileged=False, name=None)
+* c.create_container(image, command=None, hostname=None, user=None,
+ detach=False, stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None, dns=None, volumes=None, volumes_from=None,
+ privileged=False, name=None)
Creates a container that can then be `start`ed. Parameters are similar to those
for the `docker run` command except it doesn't support the attach options
(`-a`)
-In order to create volumes that can be rebinded at start time, use the
-following syntax: `volumes={"/srv": {}}`. The `ports` parameter is a
-dictionary whose key is the port to expose and the value is an empty
-dictionary: `ports={"2181/tcp": {}}`. Note, this will simply expose the ports in
-the container, but does not make them available on the host. See `start`
-below.
+See "Port bindings" and "Using volumes" below for more information on how to
+create port bindings and volume mappings.
* `c.diff(container)`
Identical to the `docker diff` command.
@@ -105,16 +101,10 @@ Identical to the `docker search` command.
* `c.start(container, binds=None, port_bindings=None, lxc_conf=None)`
Similar to the `docker start` command, but doesn't support attach options.
Use `docker logs` to recover `stdout`/`stderr`
-`binds` Allows to bind a directory in the host to the container.
- Similar to the `docker run` command with option `-v="/host:/mnt"`.
-Note that you must declare "blank" volumes at container creation to use binds.
-Example of binds mapping from host to container: `{'/mnt/srv/': '/srv'}`
-`port_bindings` Exposes container ports to the host. This is a
-dictionary whose key is the container's port and the value is a `[{'HostIp': ''
-'HostPort': ''}]` list. Leaving `HostIp` blank will expose the port on
-all host interfaces. By leaving the `HostPort` blank, Docker will
-automatically assign a port. For example: `port_bindings={"2181/tcp": [{'HostIp': '',
-'HostPort': ''}]}`.
+`binds` Allows to bind a directory in the host to the container. See
+"Using volumes" below for more information.
+`port_bindings` Exposes container ports to the host. See "Port bindings" below
+for more information.
`lxc_conf` allows to pass LXC configuration options using a dictionary.
* `c.stop(container, timeout=10)`
@@ -133,3 +123,52 @@ Identical to the `docker version` command.
Wait for a container and return its exit code. Similar to the `docker wait`
command.
+
+
+Port bindings
+=============
+
+Port bindings is done in two parts. Firstly, by providing a list of ports to
+open inside the container in the `Client.create_container` method.
+
+ client.create_container('busybox', 'ls', ports=[1111, 2222])
+
+If you wish to use UDP instead of TCP (default), you can declare it like such:
+
+ client.create_container('busybox', 'ls', ports=[(1111, 'udp'), 2222])
+
+Bindings are then declared in the `Client.start` method.
+
+ client.start(container_id, port_bindings={
+ 1111: 4567,
+ 2222: None
+ })
+
+You can limit the host address on which the port will be exposed like such:
+
+ client.start(container_id, port_bindings={
+ 1111: ('127.0.0.1', 4567)
+ })
+
+or without host port assignment:
+
+ client.start(container_id, port_bindings={
+ 1111: ('127.0.0.1',)
+ })
+
+
+
+Using volumes
+=============
+
+Similarly, volume declaration is done in two parts. First, you have to provide
+a list of mountpoints to the `Client.create_container` method.
+
+ client.create_container('busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2'])
+
+Volume mappings are then declared inside the `Client.start` method like this:
+
+ client.start(container_id, bindings={
+ '/mnt/vol2': '/home/user1/',
+ '/mnt/vol1': '/var/www'
+ })
\ No newline at end of file
diff --git a/docker/client.py b/docker/client.py
index d9242d3a..90f430ca 100644
--- a/docker/client.py
+++ b/docker/client.py
@@ -133,6 +133,27 @@ class Client(requests.Session):
'{0}={1}'.format(k, v) for k, v in environment.items()
]
+ if ports and isinstance(ports, list):
+ exposed_ports = {}
+ for port_definition in ports:
+ port = port_definition
+ proto = None
+ if isinstance(port_definition, tuple):
+ if len(port_definition) == 2:
+ proto = port_definition[1]
+ port = port_definition[0]
+ exposed_ports['{0}{1}'.format(
+ port,
+ '/' + proto if proto else ''
+ )] = {}
+ ports = exposed_ports
+
+ if volumes and isinstance(volumes, list):
+ volumes_dict = {}
+ for vol in volumes:
+ volumes_dict[vol] = {}
+ volumes = volumes_dict
+
attach_stdin = False
attach_stdout = False
attach_stderr = False
@@ -598,7 +619,9 @@ class Client(requests.Session):
start_config['Binds'] = bind_pairs
if port_bindings:
- start_config['PortBindings'] = port_bindings
+ start_config['PortBindings'] = utils.convert_port_bindings(
+ port_bindings
+ )
start_config['PublishAllPorts'] = publish_all_ports
diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py
index 82f94413..386a01af 100644
--- a/docker/utils/__init__.py
+++ b/docker/utils/__init__.py
@@ -1 +1,3 @@
-from .utils import compare_version, mkbuildcontext, ping, tar # flake8: noqa
+from .utils import (
+ compare_version, convert_port_bindings, mkbuildcontext, ping, tar
+) # flake8: noqa
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index 1aa86aab..8fd9e947 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -60,3 +60,37 @@ def ping(url):
return res.status >= 400
except Exception:
return False
+
+
+def _convert_port_binding(binding):
+ result = {'HostIp': '', 'HostPort': ''}
+ if isinstance(binding, tuple):
+ if len(binding) == 2:
+ result['HostPort'] = binding[1]
+ result['HostIp'] = binding[0]
+ elif isinstance(binding[0], six.string_types):
+ result['HostIp'] = binding[0]
+ else:
+ result['HostPort'] = binding[0]
+ else:
+ result['HostPort'] = binding
+
+ if result['HostPort'] is None:
+ result['HostPort'] = ''
+ else:
+ result['HostPort'] = str(result['HostPort'])
+
+ return result
+
+
+def convert_port_bindings(port_bindings):
+ result = {}
+ for k, v in six.iteritems(port_bindings):
+ key = str(k)
+ if '/' not in key:
+ key = key + '/tcp'
+ if isinstance(v, list):
+ result[key] = [_convert_port_binding(binding) for binding in v]
+ else:
+ result[key] = [_convert_port_binding(v)]
+ return result
diff --git a/tests/test.py b/tests/test.py
index e058d287..7417842b 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -190,7 +190,7 @@ class DockerClientTest(unittest.TestCase):
try:
self.client.create_container('busybox', ['ls', mount_dest],
- volumes={mount_dest: {}})
+ volumes=[mount_dest])
except Exception as e:
self.fail('Command should not raise exception: {0}'.format(e))
@@ -207,6 +207,30 @@ class DockerClientTest(unittest.TestCase):
self.assertEqual(args[1]['headers'],
{'Content-Type': 'application/json'})
+ def test_create_container_with_ports(self):
+ try:
+ self.client.create_container('busybox', 'ls',
+ ports=[1111, (2222, 'udp'), (3333,)])
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+
+ args = fake_request.call_args
+ self.assertEqual(args[0][0],
+ 'unix://var/run/docker.sock/v1.6/containers/create')
+ self.assertEqual(json.loads(args[1]['data']),
+ json.loads('''
+ {"Tty": false, "Image": "busybox",
+ "Cmd": ["ls"], "AttachStdin": false,
+ "Memory": 0, "ExposedPorts": {
+ "1111": {},
+ "2222/udp": {},
+ "3333": {}
+ },
+ "AttachStderr": true, "Privileged": false,
+ "AttachStdout": true, "OpenStdin": false}'''))
+ self.assertEqual(args[1]['headers'],
+ {'Content-Type': 'application/json'})
+
def test_create_container_privileged(self):
try:
self.client.create_container('busybox', 'true', privileged=True)
@@ -324,6 +348,53 @@ class DockerClientTest(unittest.TestCase):
docker.client.DEFAULT_TIMEOUT_SECONDS
)
+ def test_start_container_with_port_binds(self):
+ self.maxDiff = None
+ try:
+ self.client.start(fake_api.FAKE_CONTAINER_ID, port_bindings={
+ 1111: None,
+ 2222: 2222,
+ '3333/udp': (3333,),
+ 4444: ('127.0.0.1',),
+ 5555: ('127.0.0.1', 5555),
+ 6666: [('127.0.0.1',), ('192.168.0.1',)]
+ })
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+
+ args = fake_request.call_args
+ self.assertEqual(args[0][0], 'unix://var/run/docker.sock/v1.6/'
+ 'containers/3cc2351ab11b/start')
+ self.assertEqual(json.loads(args[1]['data']), {
+ "PublishAllPorts": False,
+ "PortBindings": {
+ "1111/tcp": [{"HostPort": "", "HostIp": ""}],
+ "2222/tcp": [{"HostPort": "2222", "HostIp": ""}],
+ "3333/udp": [{"HostPort": "3333", "HostIp": ""}],
+ "4444/tcp": [{
+ "HostPort": "",
+ "HostIp": "127.0.0.1"
+ }],
+ "5555/tcp": [{
+ "HostPort": "5555",
+ "HostIp": "127.0.0.1"
+ }],
+ "6666/tcp": [{
+ "HostPort": "",
+ "HostIp": "127.0.0.1"
+ }, {
+ "HostPort": "",
+ "HostIp": "192.168.0.1"
+ }]
+ }
+ })
+ self.assertEqual(args[1]['headers'],
+ {'Content-Type': 'application/json'})
+ self.assertEqual(
+ args[1]['timeout'],
+ docker.client.DEFAULT_TIMEOUT_SECONDS
+ )
+
def test_start_container_with_links(self):
# one link
try: