diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst
index 78677332be..414b7437f5 100644
--- a/doc/source/command-objects/port.rst
+++ b/doc/source/command-objects/port.rst
@@ -19,3 +19,19 @@ Delete port(s)
 .. describe:: <port>
 
     Port(s) to delete (name or ID)
+
+port show
+---------
+
+Display port details
+
+.. program:: port show
+.. code:: bash
+
+    os port show
+        <port>
+
+.. _port_show-port:
+.. describe:: <port>
+
+    Port to display (name or ID)
diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py
index 1ca2bc574a..a468629ba2 100644
--- a/openstackclient/common/utils.py
+++ b/openstackclient/common/utils.py
@@ -142,6 +142,16 @@ def format_list(data, separator=', '):
     return separator.join(sorted(data))
 
 
+def format_list_of_dicts(data):
+    """Return a formatted string of key value pairs for each dict
+
+    :param data: a list of dicts
+    :rtype: a string formatted to key='value' with dicts separated by new line
+    """
+
+    return '\n'.join(format_dict(i) for i in data)
+
+
 def get_field(item, field):
     try:
         if isinstance(item, dict):
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 0d5b183e47..46cb031f91 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -14,6 +14,42 @@
 """Port action implementations"""
 
 from openstackclient.common import command
+from openstackclient.common import utils
+
+
+def _format_admin_state(state):
+    return 'UP' if state else 'DOWN'
+
+
+_formatters = {
+    'admin_state_up': _format_admin_state,
+    'allowed_address_pairs': utils.format_list_of_dicts,
+    'binding_profile': utils.format_dict,
+    'binding_vif_details': utils.format_dict,
+    'dns_assignment':  utils.format_list_of_dicts,
+    'extra_dhcp_opts': utils.format_list_of_dicts,
+    'fixed_ips':  utils.format_list_of_dicts,
+    'security_groups': utils.format_list,
+}
+
+
+def _get_columns(item):
+    columns = item.keys()
+    if 'tenant_id' in columns:
+        columns.remove('tenant_id')
+        columns.append('project_id')
+    binding_columns = [
+        'binding:host_id',
+        'binding:profile',
+        'binding:vif_details',
+        'binding:vif_type',
+        'binding:vnic_type',
+    ]
+    for binding_column in binding_columns:
+        if binding_column in columns:
+            columns.remove(binding_column)
+            columns.append(binding_column.replace('binding:', 'binding_', 1))
+    return sorted(columns)
 
 
 class DeletePort(command.Command):
@@ -35,3 +71,23 @@ class DeletePort(command.Command):
         for port in parsed_args.port:
             res = client.find_port(port)
             client.delete_port(res)
+
+
+class ShowPort(command.ShowOne):
+    """Display port details"""
+
+    def get_parser(self, prog_name):
+        parser = super(ShowPort, self).get_parser(prog_name)
+        parser.add_argument(
+            'port',
+            metavar="<port>",
+            help="Port to display (name or ID)"
+        )
+        return parser
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.network
+        obj = client.find_port(parsed_args.port, ignore_missing=False)
+        columns = _get_columns(obj)
+        data = utils.get_item_properties(obj, columns, formatters=_formatters)
+        return (tuple(columns), data)
diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py
index 064ad417e6..62e3638eeb 100644
--- a/openstackclient/tests/common/test_utils.py
+++ b/openstackclient/tests/common/test_utils.py
@@ -348,6 +348,15 @@ class TestFindResource(test_utils.TestCase):
         self.assertEqual(expected, utils.format_list(['a', 'b', 'c']))
         self.assertEqual(expected, utils.format_list(['c', 'b', 'a']))
 
+    def test_format_list_of_dicts(self):
+        expected = "a='b', c='d'\ne='f'"
+        sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}]
+        unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}]
+        self.assertEqual(expected, utils.format_list_of_dicts(sorted_data))
+        self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data))
+        self.assertEqual('', utils.format_list_of_dicts([]))
+        self.assertEqual('', utils.format_list_of_dicts([{}]))
+
     def test_format_list_separator(self):
         expected = 'a\nb\nc'
         actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n')
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index 95c5aca644..812a64af9f 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -172,15 +172,31 @@ class FakePort(object):
         :param Dictionary methods:
             A dictionary with all methods
         :return:
-            A FakeResource object, with id, name, admin_state_up,
-            status, tenant_id
+            A FakeResource object, with id, name, etc.
         """
+
         # Set default attributes.
         port_attrs = {
-            'id': 'port-id-' + uuid.uuid4().hex,
-            'name': 'port-name-' + uuid.uuid4().hex,
-            'status': 'ACTIVE',
             'admin_state_up': True,
+            'allowed_address_pairs': [{}],
+            'binding:host_id': 'binding-host-id-' + uuid.uuid4().hex,
+            'binding:profile': {},
+            'binding:vif_details': {},
+            'binding:vif_type': 'ovs',
+            'binding:vnic_type': 'normal',
+            'device_id': 'device-id-' + uuid.uuid4().hex,
+            'device_owner': 'compute:nova',
+            'dns_assignment': [{}],
+            'dns_name': 'dns-name-' + uuid.uuid4().hex,
+            'extra_dhcp_opts': [{}],
+            'fixed_ips': [{}],
+            'id': 'port-id-' + uuid.uuid4().hex,
+            'mac_address': 'fa:16:3e:a9:4e:72',
+            'name': 'port-name-' + uuid.uuid4().hex,
+            'network_id': 'network-id-' + uuid.uuid4().hex,
+            'port_security_enabled': True,
+            'security_groups': [],
+            'status': 'ACTIVE',
             'tenant_id': 'project-id-' + uuid.uuid4().hex,
         }
 
@@ -188,7 +204,16 @@ class FakePort(object):
         port_attrs.update(attrs)
 
         # Set default methods.
-        port_methods = {}
+        port_methods = {
+            'keys': ['admin_state_up', 'allowed_address_pairs',
+                     'binding:host_id', 'binding:profile',
+                     'binding:vif_details', 'binding:vif_type',
+                     'binding:vnic_type', 'device_id', 'device_owner',
+                     'dns_assignment', 'dns_name', 'extra_dhcp_opts',
+                     'fixed_ips', 'id', 'mac_address', 'name',
+                     'network_id', 'port_security_enabled',
+                     'security_groups', 'status', 'tenant_id'],
+        }
 
         # Overwrite default methods.
         port_methods.update(methods)
@@ -196,6 +221,15 @@ class FakePort(object):
         port = fakes.FakeResource(info=copy.deepcopy(port_attrs),
                                   methods=copy.deepcopy(port_methods),
                                   loaded=True)
+
+        # Set attributes with special mappings.
+        port.project_id = port_attrs['tenant_id']
+        port.binding_host_id = port_attrs['binding:host_id']
+        port.binding_profile = port_attrs['binding:profile']
+        port.binding_vif_details = port_attrs['binding:vif_details']
+        port.binding_vif_type = port_attrs['binding:vif_type']
+        port.binding_vnic_type = port_attrs['binding:vnic_type']
+
         return port
 
     @staticmethod
diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py
index a1ddefa1de..bc246bd877 100644
--- a/openstackclient/tests/network/v2/test_port.py
+++ b/openstackclient/tests/network/v2/test_port.py
@@ -13,8 +13,10 @@
 
 import mock
 
+from openstackclient.common import utils
 from openstackclient.network.v2 import port
 from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils as tests_utils
 
 
 class TestPort(network_fakes.TestNetworkV2):
@@ -51,3 +53,88 @@ class TestDeletePort(TestPort):
         result = self.cmd.take_action(parsed_args)
         self.network.delete_port.assert_called_with(self._port)
         self.assertIsNone(result)
+
+
+class TestShowPort(TestPort):
+
+    # The port to show.
+    _port = network_fakes.FakePort.create_one_port()
+
+    columns = (
+        'admin_state_up',
+        'allowed_address_pairs',
+        'binding_host_id',
+        'binding_profile',
+        'binding_vif_details',
+        'binding_vif_type',
+        'binding_vnic_type',
+        'device_id',
+        'device_owner',
+        'dns_assignment',
+        'dns_name',
+        'extra_dhcp_opts',
+        'fixed_ips',
+        'id',
+        'mac_address',
+        'name',
+        'network_id',
+        'port_security_enabled',
+        'project_id',
+        'security_groups',
+        'status',
+    )
+
+    data = (
+        port._format_admin_state(_port.admin_state_up),
+        utils.format_list_of_dicts(_port.allowed_address_pairs),
+        _port.binding_host_id,
+        utils.format_dict(_port.binding_profile),
+        utils.format_dict(_port.binding_vif_details),
+        _port.binding_vif_type,
+        _port.binding_vnic_type,
+        _port.device_id,
+        _port.device_owner,
+        utils.format_list_of_dicts(_port.dns_assignment),
+        _port.dns_name,
+        utils.format_list_of_dicts(_port.extra_dhcp_opts),
+        utils.format_list_of_dicts(_port.fixed_ips),
+        _port.id,
+        _port.mac_address,
+        _port.name,
+        _port.network_id,
+        _port.port_security_enabled,
+        _port.project_id,
+        utils.format_list(_port.security_groups),
+        _port.status,
+    )
+
+    def setUp(self):
+        super(TestShowPort, self).setUp()
+
+        self.network.find_port = mock.Mock(return_value=self._port)
+
+        # Get the command object to test
+        self.cmd = port.ShowPort(self.app, self.namespace)
+
+    def test_show_no_options(self):
+        arglist = []
+        verifylist = []
+
+        self.assertRaises(tests_utils.ParserException,
+                          self.check_parser, self.cmd, arglist, verifylist)
+
+    def test_show_all_options(self):
+        arglist = [
+            self._port.name,
+        ]
+        verifylist = [
+            ('port', self._port.name),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+
+        self.network.find_port.assert_called_with(self._port.name,
+                                                  ignore_missing=False)
+        self.assertEqual(tuple(self.columns), columns)
+        self.assertEqual(self.data, data)
diff --git a/releasenotes/notes/add-port-show-command-de0a599017189a21.yaml b/releasenotes/notes/add-port-show-command-de0a599017189a21.yaml
new file mode 100644
index 0000000000..cb65266353
--- /dev/null
+++ b/releasenotes/notes/add-port-show-command-de0a599017189a21.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Add support for the ``port show`` command.
+    [Bug `1519909 <https://bugs.launchpad.net/python-openstackclient/+bug/1519909>`_]
diff --git a/setup.cfg b/setup.cfg
index 2751eb3178..d24e7aeaaa 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -333,6 +333,7 @@ openstack.network.v2 =
     network_set = openstackclient.network.v2.network:SetNetwork
     network_show = openstackclient.network.v2.network:ShowNetwork
     port_delete = openstackclient.network.v2.port:DeletePort
+    port_show = openstackclient.network.v2.port:ShowPort
     router_create = openstackclient.network.v2.router:CreateRouter
     router_delete = openstackclient.network.v2.router:DeleteRouter
     router_list = openstackclient.network.v2.router:ListRouter