On the CLI, these are passed as multiple instances of a given CLI option.
    In Salt, we accept these as a comma-delimited list but the API expects a
    Python dictionary mapping ports to their bindings. The format the API
    expects is complicated depending on whether or not the external port maps
    to a different internal port, or if the port binding is for UDP instead of
    TCP (the default). For reference, see the "Port bindings" section in the
    docker-py documentation at the following URL:
    http://docker-py.readthedocs.io/en/stable/api.html

def port_bindings(val, **kwargs):
    '''
    On the CLI, these are passed as multiple instances of a given CLI option.
    In Salt, we accept these as a comma-delimited list but the API expects a
    Python dictionary mapping ports to their bindings. The format the API
    expects is complicated depending on whether or not the external port maps
    to a different internal port, or if the port binding is for UDP instead of
    TCP (the default). For reference, see the "Port bindings" section in the
    docker-py documentation at the following URL:
    http://docker-py.readthedocs.io/en/stable/api.html
    '''
    validate_ip_addrs = kwargs.get('validate_ip_addrs', True)
    if not isinstance(val, dict):
        if not isinstance(val, list):
            try:
                val = helpers.split(val)
            except AttributeError:
                val = helpers.split(six.text_type(val))

        for idx in range(len(val)):
            if not isinstance(val[idx], six.string_types):
                val[idx] = six.text_type(val[idx])

        def _format_port(port_num, proto):
            return six.text_type(port_num) + '/udp' if proto.lower() == 'udp' else port_num

        bindings = {}
        for binding in val:
            bind_parts = helpers.split(binding, ':')
            num_bind_parts = len(bind_parts)
            if num_bind_parts == 1:
                # Single port or port range being passed through (no
                # special mapping)
                container_port = six.text_type(bind_parts[0])
                if container_port == '':
                    raise SaltInvocationError(
                        'Empty port binding definition found'
                    )
                container_port, _, proto = container_port.partition('/')
                try:
                    start, end = helpers.get_port_range(container_port)
                except ValueError as exc:
                    # Using __str__() to avoid deprecation warning for using
                    # the message attribute of the ValueError.
                    raise SaltInvocationError(exc.__str__())
                bind_vals = [
                    (_format_port(port_num, proto), None)
                    for port_num in range(start, end + 1)
                ]
            elif num_bind_parts == 2:
                if bind_parts[0] == '':
                    raise SaltInvocationError(
                        'Empty host port in port binding definition '
                        '\'{0}\''.format(binding)
                    )
                if bind_parts[1] == '':
                    raise SaltInvocationError(
                        'Empty container port in port binding definition '
                        '\'{0}\''.format(binding)
                    )
                container_port, _, proto = bind_parts[1].partition('/')
                try:
                    cport_start, cport_end = \
                        helpers.get_port_range(container_port)
                    hport_start, hport_end = \
                        helpers.get_port_range(bind_parts[0])
                except ValueError as exc:
                    # Using __str__() to avoid deprecation warning for
                    # using the message attribute of the ValueError.
                    raise SaltInvocationError(exc.__str__())
                if (hport_end - hport_start) != (cport_end - cport_start):
                    # Port range is mismatched
                    raise SaltInvocationError(
                        'Host port range ({0}) does not have the same '
                        'number of ports as the container port range '
                        '({1})'.format(bind_parts[0], container_port)
                    )
                cport_list = list(range(cport_start, cport_end + 1))
                hport_list = list(range(hport_start, hport_end + 1))
                bind_vals = [
                    (_format_port(cport_list[x], proto), hport_list[x])
                    for x in range(len(cport_list))
                ]
            elif num_bind_parts == 3:
                host_ip, host_port = bind_parts[0:2]
                if validate_ip_addrs:
                    helpers.validate_ip(host_ip)
                container_port, _, proto = bind_parts[2].partition('/')
                try:
                    cport_start, cport_end = \
                        helpers.get_port_range(container_port)
                except ValueError as exc:
                    # Using __str__() to avoid deprecation warning for
                    # using the message attribute of the ValueError.
                    raise SaltInvocationError(exc.__str__())
                cport_list = list(range(cport_start, cport_end + 1))
                if host_port == '':
                    hport_list = [None] * len(cport_list)
                else:
                    try:
                        hport_start, hport_end = \
                            helpers.get_port_range(host_port)
                    except ValueError as exc:
                        # Using __str__() to avoid deprecation warning for
                        # using the message attribute of the ValueError.
                        raise SaltInvocationError(exc.__str__())
                    hport_list = list(range(hport_start, hport_end + 1))

                    if (hport_end - hport_start) != (cport_end - cport_start):
                        # Port range is mismatched
                        raise SaltInvocationError(
                            'Host port range ({0}) does not have the same '
                            'number of ports as the container port range '
                            '({1})'.format(host_port, container_port)
                        )

                bind_vals = [(
                    _format_port(val, proto),
                    (host_ip,) if hport_list[idx] is None
                        else (host_ip, hport_list[idx])
                ) for idx, val in enumerate(cport_list)]
            else:
                raise SaltInvocationError(
                    '\'{0}\' is an invalid port binding definition (at most '
                    '3 components are allowed, found {1})'.format(
                        binding, num_bind_parts
                    )
                )

            for cport, bind_def in bind_vals:
                if cport not in bindings:
                    bindings[cport] = bind_def
                else:
                    if isinstance(bindings[cport], list):
                        # Append to existing list of bindings for this
                        # container port.
                        bindings[cport].append(bind_def)
                    else:
                        bindings[cport] = [bindings[cport], bind_def]
                    for idx in range(len(bindings[cport])):
                        if bindings[cport][idx] is None:
                            # Now that we are adding multiple
                            # bindings
                            try:
                                # Convert 1234/udp to 1234
                                bindings[cport][idx] = int(cport.split('/')[0])
                            except AttributeError:
                                # Port was tcp, the AttributeError
                                # signifies that the split failed
                                # because the port number was
                                # already defined as an integer.
                                # Just use the cport.
                                bindings[cport][idx] = cport
        val = bindings
    return val