diff --git a/remoteappmanager/docker/container.py b/remoteappmanager/docker/container.py
index cb484b0..f92304c 100644
--- a/remoteappmanager/docker/container.py
+++ b/remoteappmanager/docker/container.py
@@ -56,7 +56,7 @@ class Container(HasTraits):
             ")>")
 
     @classmethod
-    def from_docker_containers_dict(cls, docker_dict):
+    def from_docker_dict(cls, docker_dict):
         """Returns a Container object with the info given by a
         docker Client.
 
@@ -74,24 +74,66 @@ class Container(HasTraits):
         >>> # containers is a list of dict
         >>> containers = docker.Client().containers()
 
-        >>> Container.from_docker_containers_dict(containers[0])
+        >>> Container.from_docker_dict(containers[0])
         """
-        ip = cls.ip.default_value
-        port = cls.port.default_value
-
-        if docker_dict.get('Ports'):
-            ip = docker_dict['Ports'][0].get('IP', ip)
-            port = docker_dict['Ports'][0].get('PublicPort', port)
-
-        labels = docker_dict.get("Labels", {})
-        mapping_id = labels.get(SIMPHONY_NS+"mapping_id", "")
-        url_id = labels.get(SIMPHONY_NS+"url_id", "")
-
-        return cls(docker_id=docker_dict.get('Id', ''),
-                   name=docker_dict.get('Names', ('',))[0],
-                   image_name=docker_dict.get('Image', ''),
-                   image_id=docker_dict.get('ImageID', ''),
-                   mapping_id=mapping_id,
-                   ip=ip,
-                   port=port,
-                   url_id=url_id)
+
+        is_inspect_container_output = ("Config" in docker_dict)
+
+        kwargs = dict(
+            docker_id=docker_dict.get('Id') or '',
+            ip=cls.ip.default_value,
+            port=cls.port.default_value,
+        )
+
+        if is_inspect_container_output:
+            # It's a client.inspect_container() output
+
+            network_settings = docker_dict.get("NetworkSettings") or {}
+            ports = network_settings.get("Ports") or {}
+            # unfortunately, in the case of a running container, we don't have
+            # a single list. Instead, we have a dict where the keys are
+            # the "port identifier" (e.g. 8888/tcp) and the value is a list
+            # of dictionaries.
+            # We assume that we only have one, as above
+            if len(ports) > 1:
+                raise ValueError("Container Ports had more than one element.")
+
+            if len(ports):
+                port_values = list(ports.values())[0]
+                if len(port_values) > 1:
+                    raise ValueError("Container Ports values had "
+                                     "more than one element.")
+
+                if len(port_values):
+                    kwargs["ip"] = port_values[0].get("HostIp") or kwargs["ip"]
+                    kwargs["port"] = int(port_values[0].get("HostPort") or
+                                         kwargs["port"])
+
+            config = docker_dict.get("Config", {})
+            labels = config.get("Labels")
+
+            kwargs["image_name"] = config.get("Image")
+            kwargs["image_id"] = docker_dict["Image"]
+            kwargs["name"] = docker_dict["Name"]
+        else:
+            # It's a client.containers() output, so we have different rules.
+            ports = docker_dict.get('Ports') or []
+            if len(ports) > 1:
+                raise ValueError("Container Ports had more than one element.")
+
+            if len(ports):
+                kwargs["ip"] = ports[0].get('IP') or kwargs["ip"]
+                kwargs["port"] = int(ports[0].get('PublicPort') or
+                                     kwargs["port"])
+
+            labels = docker_dict.get("Labels") or {}
+
+            kwargs["image_name"] = docker_dict.get('Image') or ''
+            kwargs["image_id"] = docker_dict.get("ImageID") or ''
+            names = docker_dict.get("Names") or ('', )
+            kwargs["name"] = names[0]
+
+        kwargs["mapping_id"] = labels.get(SIMPHONY_NS+"mapping_id") or ""
+        kwargs["url_id"] = labels.get(SIMPHONY_NS+"url_id") or ""
+
+        return cls(**kwargs)
diff --git a/remoteappmanager/docker/container_manager.py b/remoteappmanager/docker/container_manager.py
index 1db82e5..7b85d53 100644
--- a/remoteappmanager/docker/container_manager.py
+++ b/remoteappmanager/docker/container_manager.py
@@ -166,8 +166,8 @@ class ContainerManager(LoggingMixin):
         infos = yield self.docker_client.containers(filters=filters)
         for info in infos:
             try:
-                container = Container.from_docker_containers_dict(info)
-            except Exception:
+                container = Container.from_docker_dict(info)
+            except ValueError:
                 self.log.exception("Unable to parse container info.")
                 continue
 
diff --git a/remoteappmanager/restresources/container.py b/remoteappmanager/restresources/container.py
index c3cd5e8..64bf36a 100644
--- a/remoteappmanager/restresources/container.py
+++ b/remoteappmanager/restresources/container.py
@@ -118,7 +118,7 @@ class Container(Resource):
         if not container_dict:
             return None
 
-        return DockerContainer.from_docker_containers_dict(container_dict[0])
+        return DockerContainer.from_docker_dict(container_dict[0])
 
     @gen.coroutine
     def _start_container(self, user_name, app, policy, mapping_id):

Image.from_docker_dict and Container.from_docker_dict take inconsistent inputs
`Container.from_docker_dict` assumes inputs from the result of `docker.client.containers` but not `docker.client.inspect_container`, whilst `Image.from_docker_dict` can take inputs from both.

It would be useful and neater if `Container.from_docker_dict` accepts input from the result `docker.client.inspect_container` as well.

Follows #79 

diff --git a/tests/docker/test_container.py b/tests/docker/test_container.py
index f5223dc..249eac9 100644
--- a/tests/docker/test_container.py
+++ b/tests/docker/test_container.py
@@ -1,7 +1,8 @@
 from unittest import TestCase
 
 from remoteappmanager.docker.container import Container
-from tests.utils import assert_containers_equal
+from tests.utils import assert_containers_equal, \
+    mock_docker_client_with_running_containers
 
 
 class TestContainer(TestCase):
@@ -42,7 +43,7 @@ class TestContainer(TestCase):
             'Status': 'Up 56 minutes'}
 
         # Container with public port
-        actual = Container.from_docker_containers_dict(container_dict)
+        actual = Container.from_docker_dict(container_dict)
         expected = Container(
             docker_id='248e45e717cd740ae763a1c565',
             name='/remoteexec-user-empty-ubuntu_3Alatest',
@@ -75,7 +76,7 @@ class TestContainer(TestCase):
             'Status': 'Up 56 minutes'}
 
         # Container without public port
-        actual = Container.from_docker_containers_dict(container_dict)
+        actual = Container.from_docker_dict(container_dict)
         expected = Container(
             docker_id='812c765d0549be0ab831ae8348',
             name='/remoteexec-user-empty-ubuntu_3Alatest',
@@ -86,3 +87,57 @@ class TestContainer(TestCase):
             url_id="8e2fe66d5de74db9bbab50c0d2f92b33")
 
         assert_containers_equal(self, actual, expected)
+
+    def test_from_docker_dict_inspect_container(self):
+        client = mock_docker_client_with_running_containers()
+        actual = Container.from_docker_dict(
+            client.inspect_container("id"))
+
+        expected = Container(
+            docker_id='35d88fe321c3d575ec3be64f54b8967ef49c0dc92395bf4c1e511ed3e6ae0c79',  # noqa
+            name='/remoteexec-username-simphonyproject_2Fsimphonic-mayavi_5F1',
+            image_name='simphonyproject/simphonic-mayavi',
+            image_id='sha256:f43b749341ee37b56e7bd8d99f09629f311aaec35a8045a39185b5659edef169',  # noqa
+            ip='0.0.0.0',
+            port=32782,
+            url_id="55555555555555555555555555555555",
+            mapping_id="1c08c87878634e90af43d799e90f61d2")
+
+        assert_containers_equal(self, actual, expected)
+
+    def test_multiple_ports_data(self):
+        client = mock_docker_client_with_running_containers()
+        docker_dict = client.inspect_container("id")
+        docker_dict["NetworkSettings"]["Ports"] = {
+            '8888/tcp': [{'HostIp': '0.0.0.0', 'HostPort': '32782'}],
+            '8889/tcp': [{'HostIp': '0.0.0.0', 'HostPort': '32783'}]
+        }
+        with self.assertRaises(ValueError):
+            Container.from_docker_dict(docker_dict)
+
+        docker_dict["NetworkSettings"]["Ports"] = {
+            '8888/tcp': [
+                {'HostIp': '0.0.0.0', 'HostPort': '32782'},
+                {'HostIp': '0.0.0.0', 'HostPort': '32783'}
+            ]
+        }
+        with self.assertRaises(ValueError):
+            Container.from_docker_dict(docker_dict)
+
+        docker_dict = client.containers()[0]
+        docker_dict["Ports"] = [
+             {
+                'IP': '0.0.0.0',
+                'PublicIP': 34567,
+                'PrivatePort': 22,
+                'Type': 'tcp'
+             },
+             {
+                'IP': '0.0.0.0',
+                'PublicIP': 34562,
+                'PrivatePort': 21,
+                'Type': 'tcp'
+             }
+        ]
+        with self.assertRaises(ValueError):
+            Container.from_docker_dict(docker_dict)
diff --git a/tests/utils.py b/tests/utils.py
index 9279636..2556bcd 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -110,6 +110,30 @@ def mock_docker_client_with_running_containers():
          'State': 'running',
          'Status': 'Up About an hour'},
     ]
+    client.inspect_container.return_value = {
+        'Config': {
+                   'Image': 'simphonyproject/simphonic-mayavi',
+                   'Labels': {'eu.simphony-project.docker.description': 'Ubuntu '
+                                                                        'machine '
+                                                                        'with '
+                                                                        'simphony '
+                                                                        'framework '
+                                                                        'preinstalled',
+                              'eu.simphony-project.docker.mapping_id': '1c08c87878634e90af43d799e90f61d2',
+                              'eu.simphony-project.docker.ui_name': 'Simphony '
+                                                                    'Framework '
+                                                                    '(w/ mayavi)',
+                              'eu.simphony-project.docker.url_id': '55555555555555555555555555555555',
+                              'eu.simphony-project.docker.user': 'username'},
+                    },
+        'Id': '35d88fe321c3d575ec3be64f54b8967ef49c0dc92395bf4c1e511ed3e6ae0c79',
+        'Image': 'sha256:f43b749341ee37b56e7bd8d99f09629f311aaec35a8045a39185b5659edef169',
+        'Name': '/remoteexec-username-simphonyproject_2Fsimphonic-mayavi_5F1',
+        'NetworkSettings': {
+                            'Ports': {'8888/tcp': [{'HostIp': '0.0.0.0',
+                                                    'HostPort': '32782'}]},
+                           },
+    }
 
     return client
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
flake8 sphinx coverage pytest
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
alembic==1.15.2
annotated-types==0.7.0
arrow==1.3.0
async-generator==1.10
attrs==25.3.0
babel==2.17.0
certifi==2025.1.31
certipy==0.2.2
cffi==1.17.1
charset-normalizer==3.4.1
click==8.1.8
coverage==7.8.0
cryptography==44.0.2
docker-py==1.10.6
docker-pycreds==0.4.0
docutils==0.21.2
escapism==1.0.1
exceptiongroup==1.2.2
flake8==7.2.0
fqdn==1.5.1
greenlet==3.1.1
idna==3.10
imagesize==1.4.1
importlib_metadata==8.6.1
iniconfig==2.1.0
isoduration==20.11.0
Jinja2==3.1.6
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter-events==0.12.0
jupyter_client==8.6.3
jupyter_core==5.7.2
jupyterhub==5.2.1
Mako==1.3.9
MarkupSafe==3.0.2
mccabe==0.7.0
oauthlib==3.2.2
packaging==24.2
pamela==1.2.0
platformdirs==4.3.7
pluggy==1.5.0
prometheus_client==0.21.1
pycodestyle==2.13.0
pycparser==2.22
pydantic==2.11.1
pydantic_core==2.33.0
pyflakes==3.3.1
Pygments==2.19.1
pytest==8.3.5
python-dateutil==2.9.0.post0
python-json-logger==3.3.0
PyYAML==6.0.2
pyzmq==26.3.0
referencing==0.36.2
-e git+https://github.com/simphony/simphony-remote.git@af0203df6cb3232a28b50fa1023baf73e234aa6d#egg=remoteappmanager
requests==2.32.3
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.24.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==7.4.7
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
SQLAlchemy==2.0.40
tabulate==0.9.0
tomli==2.2.1
tornado==6.4.2
traitlets==5.14.3
types-python-dateutil==2.9.0.20241206
typing-inspection==0.4.0
typing_extensions==4.13.0
uri-template==1.3.0
urllib3==2.3.0
webcolors==24.11.1
websocket-client==1.8.0
zipp==3.21.0

name: simphony-remote
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - alembic==1.15.2
      - annotated-types==0.7.0
      - arrow==1.3.0
      - async-generator==1.10
      - attrs==25.3.0
      - babel==2.17.0
      - certifi==2025.1.31
      - certipy==0.2.2
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.8.0
      - cryptography==44.0.2
      - docker-py==1.10.6
      - docker-pycreds==0.4.0
      - docutils==0.21.2
      - escapism==1.0.1
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - fqdn==1.5.1
      - greenlet==3.1.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - isoduration==20.11.0
      - jinja2==3.1.6
      - jsonpointer==3.0.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2024.10.1
      - jupyter-client==8.6.3
      - jupyter-core==5.7.2
      - jupyter-events==0.12.0
      - jupyterhub==5.2.1
      - mako==1.3.9
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - oauthlib==3.2.2
      - packaging==24.2
      - pamela==1.2.0
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - prometheus-client==0.21.1
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pydantic==2.11.1
      - pydantic-core==2.33.0
      - pyflakes==3.3.1
      - pygments==2.19.1
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - python-json-logger==3.3.0
      - pyyaml==6.0.2
      - pyzmq==26.3.0
      - referencing==0.36.2
      - requests==2.32.3
      - rfc3339-validator==0.1.4
      - rfc3986-validator==0.1.1
      - rpds-py==0.24.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==7.4.7
      - sphinxcontrib-applehelp==2.0.0
      - sphinxcontrib-devhelp==2.0.0
      - sphinxcontrib-htmlhelp==2.1.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==2.0.0
      - sphinxcontrib-serializinghtml==2.0.0
      - sqlalchemy==2.0.40
      - tabulate==0.9.0
      - tomli==2.2.1
      - tornado==6.4.2
      - traitlets==5.14.3
      - types-python-dateutil==2.9.0.20241206
      - typing-extensions==4.13.0
      - typing-inspection==0.4.0
      - uri-template==1.3.0
      - urllib3==2.3.0
      - webcolors==24.11.1
      - websocket-client==1.8.0
      - zipp==3.21.0
prefix: /opt/conda/envs/simphony-remote

tests/docker/test_container.py::TestContainer::test_from_docker_dict_inspect_container tests/docker/test_container.py::TestContainer::test_from_docker_dict_with_public_port tests/docker/test_container.py::TestContainer::test_from_docker_dict_without_public_port tests/docker/test_container.py::TestContainer::test_multiple_ports_data
tests/docker/test_container.py::TestContainer::test_host_url tests/docker/test_container.py::TestContainer::test_url
remoteappmanager/docker/container.py remoteappmanager/restresources/container.py remoteappmanager/docker/container_manager.py
remoteappmanager/docker/container.py remoteappmanager/restresources/container.py remoteappmanager/docker/container_manager.py