shin-: #764 needs to be merged for the tests to pass.
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index 46b35160..36edf8de 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -457,7 +457,8 @@ def create_host_config(
     restart_policy=None, cap_add=None, cap_drop=None, devices=None,
     extra_hosts=None, read_only=None, pid_mode=None, ipc_mode=None,
     security_opt=None, ulimits=None, log_config=None, mem_limit=None,
-    memswap_limit=None, cgroup_parent=None, group_add=None, version=None
+    memswap_limit=None, cgroup_parent=None, group_add=None, cpu_quota=None,
+    cpu_period=None, version=None
 ):
     host_config = {}
 
@@ -518,7 +519,7 @@ def create_host_config(
         host_config['Devices'] = parse_devices(devices)
 
     if group_add:
-        if compare_version(version, '1.20') < 0:
+        if version_lt(version, '1.20'):
             raise errors.InvalidVersion(
                 'group_add param not supported for API version < 1.20'
             )
@@ -601,6 +602,30 @@ def create_host_config(
             log_config = LogConfig(**log_config)
         host_config['LogConfig'] = log_config
 
+    if cpu_quota:
+        if not isinstance(cpu_quota, int):
+            raise TypeError(
+                'Invalid type for cpu_quota param: expected int but'
+                ' found {0}'.format(type(cpu_quota))
+            )
+        if version_lt(version, '1.19'):
+            raise errors.InvalidVersion(
+                'cpu_quota param not supported for API version < 1.19'
+            )
+        host_config['CpuQuota'] = cpu_quota
+
+    if cpu_period:
+        if not isinstance(cpu_period, int):
+            raise TypeError(
+                'Invalid type for cpu_period param: expected int but'
+                ' found {0}'.format(type(cpu_period))
+            )
+        if version_lt(version, '1.19'):
+            raise errors.InvalidVersion(
+                'cpu_period param not supported for API version < 1.19'
+            )
+        host_config['CpuPeriod'] = cpu_period
+
     return host_config
 
 

Add support for --cpu-quota & --cpu-period run flags
Any chance we could add support for the above run flags?
https://docs.docker.com/reference/commandline/run/
diff --git a/tests/utils_test.py b/tests/utils_test.py
index b67ac4ec..45929f73 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -25,6 +25,159 @@ TEST_CERT_DIR = os.path.join(
 )
 
 
+class HostConfigTest(base.BaseTestCase):
+    def test_create_host_config_no_options(self):
+        config = create_host_config(version='1.19')
+        self.assertFalse('NetworkMode' in config)
+
+    def test_create_host_config_no_options_newer_api_version(self):
+        config = create_host_config(version='1.20')
+        self.assertEqual(config['NetworkMode'], 'default')
+
+    def test_create_host_config_invalid_cpu_cfs_types(self):
+        with pytest.raises(TypeError):
+            create_host_config(version='1.20', cpu_quota='0')
+
+        with pytest.raises(TypeError):
+            create_host_config(version='1.20', cpu_period='0')
+
+        with pytest.raises(TypeError):
+            create_host_config(version='1.20', cpu_quota=23.11)
+
+        with pytest.raises(TypeError):
+            create_host_config(version='1.20', cpu_period=1999.0)
+
+    def test_create_host_config_with_cpu_quota(self):
+        config = create_host_config(version='1.20', cpu_quota=1999)
+        self.assertEqual(config.get('CpuQuota'), 1999)
+
+    def test_create_host_config_with_cpu_period(self):
+        config = create_host_config(version='1.20', cpu_period=1999)
+        self.assertEqual(config.get('CpuPeriod'), 1999)
+
+
+class UlimitTest(base.BaseTestCase):
+    def test_create_host_config_dict_ulimit(self):
+        ulimit_dct = {'name': 'nofile', 'soft': 8096}
+        config = create_host_config(
+            ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION
+        )
+        self.assertIn('Ulimits', config)
+        self.assertEqual(len(config['Ulimits']), 1)
+        ulimit_obj = config['Ulimits'][0]
+        self.assertTrue(isinstance(ulimit_obj, Ulimit))
+        self.assertEqual(ulimit_obj.name, ulimit_dct['name'])
+        self.assertEqual(ulimit_obj.soft, ulimit_dct['soft'])
+        self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft)
+
+    def test_create_host_config_dict_ulimit_capitals(self):
+        ulimit_dct = {'Name': 'nofile', 'Soft': 8096, 'Hard': 8096 * 4}
+        config = create_host_config(
+            ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION
+        )
+        self.assertIn('Ulimits', config)
+        self.assertEqual(len(config['Ulimits']), 1)
+        ulimit_obj = config['Ulimits'][0]
+        self.assertTrue(isinstance(ulimit_obj, Ulimit))
+        self.assertEqual(ulimit_obj.name, ulimit_dct['Name'])
+        self.assertEqual(ulimit_obj.soft, ulimit_dct['Soft'])
+        self.assertEqual(ulimit_obj.hard, ulimit_dct['Hard'])
+        self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft)
+
+    def test_create_host_config_obj_ulimit(self):
+        ulimit_dct = Ulimit(name='nofile', soft=8096)
+        config = create_host_config(
+            ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION
+        )
+        self.assertIn('Ulimits', config)
+        self.assertEqual(len(config['Ulimits']), 1)
+        ulimit_obj = config['Ulimits'][0]
+        self.assertTrue(isinstance(ulimit_obj, Ulimit))
+        self.assertEqual(ulimit_obj, ulimit_dct)
+
+    def test_ulimit_invalid_type(self):
+        self.assertRaises(ValueError, lambda: Ulimit(name=None))
+        self.assertRaises(ValueError, lambda: Ulimit(name='hello', soft='123'))
+        self.assertRaises(ValueError, lambda: Ulimit(name='hello', hard='456'))
+
+
+class LogConfigTest(base.BaseTestCase):
+    def test_create_host_config_dict_logconfig(self):
+        dct = {'type': LogConfig.types.SYSLOG, 'config': {'key1': 'val1'}}
+        config = create_host_config(
+            version=DEFAULT_DOCKER_API_VERSION, log_config=dct
+        )
+        self.assertIn('LogConfig', config)
+        self.assertTrue(isinstance(config['LogConfig'], LogConfig))
+        self.assertEqual(dct['type'], config['LogConfig'].type)
+
+    def test_create_host_config_obj_logconfig(self):
+        obj = LogConfig(type=LogConfig.types.SYSLOG, config={'key1': 'val1'})
+        config = create_host_config(
+            version=DEFAULT_DOCKER_API_VERSION, log_config=obj
+        )
+        self.assertIn('LogConfig', config)
+        self.assertTrue(isinstance(config['LogConfig'], LogConfig))
+        self.assertEqual(obj, config['LogConfig'])
+
+    def test_logconfig_invalid_config_type(self):
+        with pytest.raises(ValueError):
+            LogConfig(type=LogConfig.types.JSON, config='helloworld')
+
+
+class KwargsFromEnvTest(base.BaseTestCase):
+    def setUp(self):
+        self.os_environ = os.environ.copy()
+
+    def tearDown(self):
+        os.environ = self.os_environ
+
+    def test_kwargs_from_env_empty(self):
+        os.environ.update(DOCKER_HOST='',
+                          DOCKER_CERT_PATH='',
+                          DOCKER_TLS_VERIFY='')
+
+        kwargs = kwargs_from_env()
+        self.assertEqual(None, kwargs.get('base_url'))
+        self.assertEqual(None, kwargs.get('tls'))
+
+    def test_kwargs_from_env_tls(self):
+        os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376',
+                          DOCKER_CERT_PATH=TEST_CERT_DIR,
+                          DOCKER_TLS_VERIFY='1')
+        kwargs = kwargs_from_env(assert_hostname=False)
+        self.assertEqual('https://192.168.59.103:2376', kwargs['base_url'])
+        self.assertTrue('ca.pem' in kwargs['tls'].verify)
+        self.assertTrue('cert.pem' in kwargs['tls'].cert[0])
+        self.assertTrue('key.pem' in kwargs['tls'].cert[1])
+        self.assertEqual(False, kwargs['tls'].assert_hostname)
+        try:
+            client = Client(**kwargs)
+            self.assertEqual(kwargs['base_url'], client.base_url)
+            self.assertEqual(kwargs['tls'].verify, client.verify)
+            self.assertEqual(kwargs['tls'].cert, client.cert)
+        except TypeError as e:
+            self.fail(e)
+
+    def test_kwargs_from_env_no_cert_path(self):
+        try:
+            temp_dir = tempfile.mkdtemp()
+            cert_dir = os.path.join(temp_dir, '.docker')
+            shutil.copytree(TEST_CERT_DIR, cert_dir)
+
+            os.environ.update(HOME=temp_dir,
+                              DOCKER_CERT_PATH='',
+                              DOCKER_TLS_VERIFY='1')
+
+            kwargs = kwargs_from_env()
+            self.assertIn(cert_dir, kwargs['tls'].verify)
+            self.assertIn(cert_dir, kwargs['tls'].cert[0])
+            self.assertIn(cert_dir, kwargs['tls'].cert[1])
+        finally:
+            if temp_dir:
+                shutil.rmtree(temp_dir)
+
+
 class UtilsTest(base.BaseTestCase):
     longMessage = True
 
@@ -39,12 +192,6 @@ class UtilsTest(base.BaseTestCase):
         local_tempfile.close()
         return local_tempfile.name
 
-    def setUp(self):
-        self.os_environ = os.environ.copy()
-
-    def tearDown(self):
-        os.environ = self.os_environ
-
     def test_parse_repository_tag(self):
         self.assertEqual(parse_repository_tag("root"),
                          ("root", None))
@@ -103,51 +250,6 @@ class UtilsTest(base.BaseTestCase):
 
             assert parse_host(val, 'win32') == tcp_port
 
-    def test_kwargs_from_env_empty(self):
-        os.environ.update(DOCKER_HOST='',
-                          DOCKER_CERT_PATH='',
-                          DOCKER_TLS_VERIFY='')
-
-        kwargs = kwargs_from_env()
-        self.assertEqual(None, kwargs.get('base_url'))
-        self.assertEqual(None, kwargs.get('tls'))
-
-    def test_kwargs_from_env_tls(self):
-        os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376',
-                          DOCKER_CERT_PATH=TEST_CERT_DIR,
-                          DOCKER_TLS_VERIFY='1')
-        kwargs = kwargs_from_env(assert_hostname=False)
-        self.assertEqual('https://192.168.59.103:2376', kwargs['base_url'])
-        self.assertTrue('ca.pem' in kwargs['tls'].verify)
-        self.assertTrue('cert.pem' in kwargs['tls'].cert[0])
-        self.assertTrue('key.pem' in kwargs['tls'].cert[1])
-        self.assertEqual(False, kwargs['tls'].assert_hostname)
-        try:
-            client = Client(**kwargs)
-            self.assertEqual(kwargs['base_url'], client.base_url)
-            self.assertEqual(kwargs['tls'].verify, client.verify)
-            self.assertEqual(kwargs['tls'].cert, client.cert)
-        except TypeError as e:
-            self.fail(e)
-
-    def test_kwargs_from_env_no_cert_path(self):
-        try:
-            temp_dir = tempfile.mkdtemp()
-            cert_dir = os.path.join(temp_dir, '.docker')
-            shutil.copytree(TEST_CERT_DIR, cert_dir)
-
-            os.environ.update(HOME=temp_dir,
-                              DOCKER_CERT_PATH='',
-                              DOCKER_TLS_VERIFY='1')
-
-            kwargs = kwargs_from_env()
-            self.assertIn(cert_dir, kwargs['tls'].verify)
-            self.assertIn(cert_dir, kwargs['tls'].cert[0])
-            self.assertIn(cert_dir, kwargs['tls'].cert[1])
-        finally:
-            if temp_dir:
-                shutil.rmtree(temp_dir)
-
     def test_parse_env_file_proper(self):
         env_file = self.generate_tempfile(
             file_content='USER=jdoe\nPASS=secret')
@@ -181,79 +283,6 @@ class UtilsTest(base.BaseTestCase):
         for filters, expected in tests:
             self.assertEqual(convert_filters(filters), expected)
 
-    def test_create_host_config_no_options(self):
-        config = create_host_config(version='1.19')
-        self.assertFalse('NetworkMode' in config)
-
-    def test_create_host_config_no_options_newer_api_version(self):
-        config = create_host_config(version='1.20')
-        self.assertEqual(config['NetworkMode'], 'default')
-
-    def test_create_host_config_dict_ulimit(self):
-        ulimit_dct = {'name': 'nofile', 'soft': 8096}
-        config = create_host_config(
-            ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION
-        )
-        self.assertIn('Ulimits', config)
-        self.assertEqual(len(config['Ulimits']), 1)
-        ulimit_obj = config['Ulimits'][0]
-        self.assertTrue(isinstance(ulimit_obj, Ulimit))
-        self.assertEqual(ulimit_obj.name, ulimit_dct['name'])
-        self.assertEqual(ulimit_obj.soft, ulimit_dct['soft'])
-        self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft)
-
-    def test_create_host_config_dict_ulimit_capitals(self):
-        ulimit_dct = {'Name': 'nofile', 'Soft': 8096, 'Hard': 8096 * 4}
-        config = create_host_config(
-            ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION
-        )
-        self.assertIn('Ulimits', config)
-        self.assertEqual(len(config['Ulimits']), 1)
-        ulimit_obj = config['Ulimits'][0]
-        self.assertTrue(isinstance(ulimit_obj, Ulimit))
-        self.assertEqual(ulimit_obj.name, ulimit_dct['Name'])
-        self.assertEqual(ulimit_obj.soft, ulimit_dct['Soft'])
-        self.assertEqual(ulimit_obj.hard, ulimit_dct['Hard'])
-        self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft)
-
-    def test_create_host_config_obj_ulimit(self):
-        ulimit_dct = Ulimit(name='nofile', soft=8096)
-        config = create_host_config(
-            ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION
-        )
-        self.assertIn('Ulimits', config)
-        self.assertEqual(len(config['Ulimits']), 1)
-        ulimit_obj = config['Ulimits'][0]
-        self.assertTrue(isinstance(ulimit_obj, Ulimit))
-        self.assertEqual(ulimit_obj, ulimit_dct)
-
-    def test_ulimit_invalid_type(self):
-        self.assertRaises(ValueError, lambda: Ulimit(name=None))
-        self.assertRaises(ValueError, lambda: Ulimit(name='hello', soft='123'))
-        self.assertRaises(ValueError, lambda: Ulimit(name='hello', hard='456'))
-
-    def test_create_host_config_dict_logconfig(self):
-        dct = {'type': LogConfig.types.SYSLOG, 'config': {'key1': 'val1'}}
-        config = create_host_config(
-            version=DEFAULT_DOCKER_API_VERSION, log_config=dct
-        )
-        self.assertIn('LogConfig', config)
-        self.assertTrue(isinstance(config['LogConfig'], LogConfig))
-        self.assertEqual(dct['type'], config['LogConfig'].type)
-
-    def test_create_host_config_obj_logconfig(self):
-        obj = LogConfig(type=LogConfig.types.SYSLOG, config={'key1': 'val1'})
-        config = create_host_config(
-            version=DEFAULT_DOCKER_API_VERSION, log_config=obj
-        )
-        self.assertIn('LogConfig', config)
-        self.assertTrue(isinstance(config['LogConfig'], LogConfig))
-        self.assertEqual(obj, config['LogConfig'])
-
-    def test_logconfig_invalid_config_type(self):
-        with pytest.raises(ValueError):
-            LogConfig(type=LogConfig.types.JSON, config='helloworld')
-
     def test_resolve_repository_name(self):
         # docker hub library image
         self.assertEqual(
@@ -407,6 +436,8 @@ class UtilsTest(base.BaseTestCase):
             None,
         )
 
+
+class PortsTest(base.BaseTestCase):
     def test_split_port_with_host_ip(self):
         internal_port, external_port = split_port("127.0.0.1:1000:2000")
         self.assertEqual(internal_port, ["2000"])

pip install -e .
parse_log_pytest
requirements.txt
pytest flake8
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
-e git+https://github.com/docker/docker-py.git@02f330d8dc3da47215bed47b44fac73941ea6920#egg=docker_py
exceptiongroup==1.2.2
flake8==7.2.0
iniconfig==2.1.0
mccabe==0.7.0
packaging==24.2
pluggy==1.5.0
pycodestyle==2.13.0
pyflakes==3.3.2
pytest==8.3.5
requests==2.5.3
six==1.17.0
tomli==2.2.1
websocket_client==0.32.0

name: docker-py
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:
      - exceptiongroup==1.2.2
      - flake8==7.2.0
      - iniconfig==2.1.0
      - mccabe==0.7.0
      - packaging==24.2
      - pluggy==1.5.0
      - pycodestyle==2.13.0
      - pyflakes==3.3.2
      - pytest==8.3.5
      - requests==2.5.3
      - six==1.17.0
      - tomli==2.2.1
      - websocket-client==0.32.0
prefix: /opt/conda/envs/docker-py

tests/utils_test.py::HostConfigTest::test_create_host_config_with_cpu_period tests/utils_test.py::HostConfigTest::test_create_host_config_with_cpu_quota
tests/utils_test.py::HostConfigTest::test_create_host_config_invalid_cpu_cfs_types tests/utils_test.py::HostConfigTest::test_create_host_config_no_options tests/utils_test.py::HostConfigTest::test_create_host_config_no_options_newer_api_version tests/utils_test.py::UlimitTest::test_create_host_config_dict_ulimit tests/utils_test.py::UlimitTest::test_create_host_config_dict_ulimit_capitals tests/utils_test.py::UlimitTest::test_create_host_config_obj_ulimit tests/utils_test.py::UlimitTest::test_ulimit_invalid_type tests/utils_test.py::LogConfigTest::test_create_host_config_dict_logconfig tests/utils_test.py::LogConfigTest::test_create_host_config_obj_logconfig tests/utils_test.py::LogConfigTest::test_logconfig_invalid_config_type tests/utils_test.py::KwargsFromEnvTest::test_kwargs_from_env_empty tests/utils_test.py::KwargsFromEnvTest::test_kwargs_from_env_no_cert_path tests/utils_test.py::KwargsFromEnvTest::test_kwargs_from_env_tls tests/utils_test.py::UtilsTest::test_convert_filters tests/utils_test.py::UtilsTest::test_parse_bytes tests/utils_test.py::UtilsTest::test_parse_env_file_commented_line tests/utils_test.py::UtilsTest::test_parse_env_file_invalid_line tests/utils_test.py::UtilsTest::test_parse_env_file_proper tests/utils_test.py::UtilsTest::test_parse_host tests/utils_test.py::UtilsTest::test_parse_host_empty_value tests/utils_test.py::UtilsTest::test_parse_repository_tag tests/utils_test.py::UtilsTest::test_resolve_authconfig tests/utils_test.py::UtilsTest::test_resolve_registry_and_auth tests/utils_test.py::UtilsTest::test_resolve_repository_name tests/utils_test.py::PortsTest::test_build_port_bindings_with_matching_internal_port_ranges tests/utils_test.py::PortsTest::test_build_port_bindings_with_matching_internal_ports tests/utils_test.py::PortsTest::test_build_port_bindings_with_nonmatching_internal_port_ranges tests/utils_test.py::PortsTest::test_build_port_bindings_with_nonmatching_internal_ports tests/utils_test.py::PortsTest::test_build_port_bindings_with_one_port tests/utils_test.py::PortsTest::test_build_port_bindings_with_port_range tests/utils_test.py::PortsTest::test_host_only_with_colon tests/utils_test.py::PortsTest::test_non_matching_length_port_ranges tests/utils_test.py::PortsTest::test_port_and_range_invalid tests/utils_test.py::PortsTest::test_port_only_with_colon tests/utils_test.py::PortsTest::test_split_port_invalid tests/utils_test.py::PortsTest::test_split_port_no_host_port tests/utils_test.py::PortsTest::test_split_port_range_no_host_port tests/utils_test.py::PortsTest::test_split_port_range_with_host_ip_no_port tests/utils_test.py::PortsTest::test_split_port_range_with_host_port tests/utils_test.py::PortsTest::test_split_port_range_with_protocol tests/utils_test.py::PortsTest::test_split_port_with_host_ip tests/utils_test.py::PortsTest::test_split_port_with_host_ip_no_port tests/utils_test.py::PortsTest::test_split_port_with_host_port tests/utils_test.py::PortsTest::test_split_port_with_protocol tests/utils_test.py::ExcludePathsTest::test_directory tests/utils_test.py::ExcludePathsTest::test_directory_with_single_exception tests/utils_test.py::ExcludePathsTest::test_directory_with_subdir_exception tests/utils_test.py::ExcludePathsTest::test_directory_with_trailing_slash tests/utils_test.py::ExcludePathsTest::test_directory_with_wildcard_exception tests/utils_test.py::ExcludePathsTest::test_exclude_custom_dockerfile tests/utils_test.py::ExcludePathsTest::test_exclude_dockerfile_dockerignore tests/utils_test.py::ExcludePathsTest::test_no_dupes tests/utils_test.py::ExcludePathsTest::test_no_excludes tests/utils_test.py::ExcludePathsTest::test_question_mark tests/utils_test.py::ExcludePathsTest::test_single_filename tests/utils_test.py::ExcludePathsTest::test_single_filename_trailing_slash tests/utils_test.py::ExcludePathsTest::test_single_subdir_single_filename tests/utils_test.py::ExcludePathsTest::test_single_subdir_wildcard_filename tests/utils_test.py::ExcludePathsTest::test_subdirectory tests/utils_test.py::ExcludePathsTest::test_wildcard_exclude tests/utils_test.py::ExcludePathsTest::test_wildcard_filename_end tests/utils_test.py::ExcludePathsTest::test_wildcard_filename_start tests/utils_test.py::ExcludePathsTest::test_wildcard_subdir_single_filename tests/utils_test.py::ExcludePathsTest::test_wildcard_subdir_wildcard_filename tests/utils_test.py::ExcludePathsTest::test_wildcard_with_exception tests/utils_test.py::ExcludePathsTest::test_wildcard_with_wildcard_exception
docker/utils/utils.py
docker/utils/utils.py