diff --git a/arkindex/process/serializers/workers.py b/arkindex/process/serializers/workers.py
index 9faef9fcefe3938a44c5ac498ed6ddae90ad834d..6a00e11b79a3ece24b90f75a001c0aab4b09e0fc 100644
--- a/arkindex/process/serializers/workers.py
+++ b/arkindex/process/serializers/workers.py
@@ -605,6 +605,7 @@ class DockerWorkerVersionSerializer(serializers.ModelSerializer):
     )
     gpu_usage = EnumField(FeatureUsage, required=False, default=FeatureUsage.Disabled)
     model_usage = EnumField(FeatureUsage, required=False, default=FeatureUsage.Disabled)
+    configuration = serializers.DictField(required=False, default={})
 
     class Meta:
         model = WorkerVersion
@@ -630,6 +631,20 @@ class DockerWorkerVersionSerializer(serializers.ModelSerializer):
             }
         }
 
+    def validate_configuration(self, configuration):
+        errors = defaultdict(list)
+        user_configuration = configuration.get("user_configuration")
+        if not user_configuration:
+            return configuration
+        field = serializers.DictField(child=UserConfigurationFieldSerializer())
+        try:
+            field.to_internal_value(user_configuration)
+        except ValidationError as e:
+            errors["user_configuration"].append(e.detail)
+        if errors:
+            raise ValidationError(errors)
+        return configuration
+
     @transaction.atomic
     def create(self, validated_data):
         """
diff --git a/arkindex/process/tests/test_docker_worker_version.py b/arkindex/process/tests/test_docker_worker_version.py
index 38c872135b0d169496c1afa5e0c9544ef36ca8a3..59bd8b58cb22f47f4bd6e5b93786953b4083d359 100644
--- a/arkindex/process/tests/test_docker_worker_version.py
+++ b/arkindex/process/tests/test_docker_worker_version.py
@@ -6,6 +6,7 @@ from rest_framework import status
 from arkindex.ponos.models import Farm
 from arkindex.process.models import FeatureUsage, GitRefType, Process, ProcessMode, Repository, Worker, WorkerType
 from arkindex.project.tests import FixtureAPITestCase
+from arkindex.training.models import Model
 from arkindex.users.models import Role, Scope
 
 
@@ -128,6 +129,7 @@ class TestDockerWorkerVersion(FixtureAPITestCase):
             )
             self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
         self.assertDictEqual(response.json(), {
+            "configuration": ['Expected a dictionary of items but got type "str".'],
             "docker_image_iid": ["Not a valid string."],
             "gpu_usage": ["Value is not of type FeatureUsage"],
             "model_usage": ["Value is not of type FeatureUsage"],
@@ -546,3 +548,600 @@ class TestDockerWorkerVersion(FixtureAPITestCase):
         self.assertListEqual(list(new_repo.memberships.values_list("user", "level")), [
             (self.user.id, Role.Admin.value)
         ])
+
+    # test version user configuration
+
+    def test_create_version_valid_user_configuration(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_integer": {"title": "Demo Integer", "type": "int", "required": True, "default": 1},
+                        "demo_boolean": {"title": "Demo Boolean", "type": "bool", "required": False, "default": True},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.json()["configuration"], {
+            "user_configuration": {
+                "demo_integer": {
+                    "title": "Demo Integer",
+                    "type": "int",
+                    "required": True,
+                    "default": 1
+                },
+                "demo_boolean": {
+                    "title": "Demo Boolean",
+                    "type": "bool",
+                    "required": False,
+                    "default": True
+                }
+            }
+        })
+
+    def test_create_version_valid_user_configuration_dict(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_dict": {"title": "Demo Dict", "type": "dict", "required": True, "default": {"a": "b", "c": "d"}},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.json()["configuration"], {
+            "user_configuration": {
+                "demo_dict": {
+                    "title": "Demo Dict",
+                    "type": "dict",
+                    "required": True,
+                    "default": {"a": "b", "c": "d"}
+                }
+            }
+        })
+
+    def test_create_version_user_configuration_dict_strings_only(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_dict": {"title": "Demo Dict", "type": "dict", "required": True, "default": {"a": ["12", "13"], "c": "d"}},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_version_valid_user_configuration_enum(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_choice": {"title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": [1, 2, 3]}
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.json()["configuration"], {
+            "user_configuration": {
+                "demo_choice": {
+                    "title": "Decisions",
+                    "type": "enum",
+                    "required": True,
+                    "default": 1,
+                    "choices": [1, 2, 3]
+                }
+            }
+        })
+
+    def test_create_version_valid_user_configuration_list(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": [1, 2, 3, 4]},
+                        "boolean_list": {"title": "It's a list of booleans", "type": "list", "required": False, "subtype": "bool", "default": [True, False, False]}
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.json()["configuration"], {
+            "user_configuration": {
+                "demo_list": {
+                    "title": "Demo List",
+                    "type": "list",
+                    "subtype": "int",
+                    "required": True,
+                    "default": [1, 2, 3, 4]
+                },
+                "boolean_list": {
+                    "title": "It's a list of booleans",
+                    "type": "list",
+                    "subtype": "bool",
+                    "required": False,
+                    "default": [True, False, False]
+                }
+            }
+        })
+
+    def test_create_version_valid_user_configuration_model(self):
+        test_model = Model.objects.create(
+            name="Generic model",
+            public=False,
+        )
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_model": {"title": "Model for training", "type": "model", "required": True},
+                        "other_model": {"title": "Model the second", "type": "model", "default": str(test_model.id)}
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+        self.assertEqual(response.json()["configuration"], {
+            "user_configuration": {
+                "demo_model": {
+                    "title": "Model for training",
+                    "type": "model",
+                    "required": True
+                },
+                "other_model": {
+                    "title": "Model the second",
+                    "type": "model",
+                    "default": str(test_model.id)
+                }
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_list_requires_subtype(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_list": {"title": "Demo List", "type": "list", "required": True, "default": [1, 2, 3, 4]},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "demo_list": {
+                        "subtype": ['The "subtype" field must be set for "list" type properties.']
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_list_wrong_default(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": 12},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "demo_list": {
+                        "default": ["This is not a valid value for a field of type list."]
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_list_wrong_subtype(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "dict", "default": [1, 2, 3, 4]},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "demo_list": {
+                        "subtype": ["Subtype can only be int, float, bool or string."]
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_list_wrong_default_subtype(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_list": {"title": "Demo List", "type": "list", "required": True, "subtype": "int", "default": [1, 2, "three", 4]},
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "demo_list": {
+                        "default": ["All items in the default value must be of type int."]
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_not_list_choices(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_choice": {"title": "Decisions", "type": "enum", "required": True, "default": 1, "choices": "eeee"}
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "demo_choice": {
+                        "choices": ['Expected a list of items but got type "str".']
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_not_dict(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": "non"
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {"configuration": {"user_configuration": [['Expected a dictionary of items but got type "str".']]}})
+
+    def test_create_version_invalid_user_configuration_item_not_dict(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "secrets": ["aaaaaa"]
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {"configuration": {
+            "user_configuration": [{
+                "secrets":
+                {"__all__": ["User configuration field definitions should be of type dict, not list."]}
+            }]
+        }})
+
+    def test_create_version_invalid_user_configuration_wrong_field_type(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "something": {
+                            "title": "some thing",
+                            "type": "uh oh",
+                            "required": 2
+                        }
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "something": {
+                        "required": ["Must be a valid boolean."],
+                        "type": ["Value is not of type UserConfigurationFieldType"]
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_wrong_default_type(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "one_float": {
+                            "title": "a float",
+                            "type": "float",
+                            "default": "bonjour",
+                            "required": True
+                        }
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "one_float": {
+                        "default": ["This is not a valid value for a field of type float."]
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_choices_no_enum(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "something": {
+                            "title": "some thing",
+                            "type": "int",
+                            "required": False,
+                            "choices": [1, 2, 3]
+                        }
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [{
+                    "something": {
+                        "choices": ['The "choices" field can only be set for an "enum" type property.']
+                    }
+                }]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_missing_key(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_integer": {"type": "int", "required": True, "default": 1}
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(
+            response.json(),
+            {
+                "configuration": {
+                    "user_configuration": [{
+                        "demo_integer": {
+                            "title": ["This field is required."]
+                        }
+                    }]
+                }
+            }
+        )
+
+    def test_create_version_invalid_user_configuration_invalid_key(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "demo_integer": {
+                            "title": "an integer",
+                            "type": "int",
+                            "required": True,
+                            "default": 1,
+                            "some_key": "oh no",
+                        }
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {
+            "configuration": {
+                "user_configuration": [
+                    {
+                        "demo_integer": {
+                            "some_key": ["Configurable properties can only be defined using the following keys: title, type, required, default, subtype, choices."]
+                        }
+                    }
+                ]
+            }
+        })
+
+    def test_create_version_invalid_user_configuration_default_value(self):
+        self.client.force_login(self.user)
+        cases = [
+            ({"type": "int", "default": False}, "int"),
+            ({"type": "int", "default": True}, "int"),
+            ({"type": "float", "default": False}, "float"),
+            ({"type": "float", "default": True}, "float"),
+            ({"type": "bool", "default": 0}, "bool"),
+            ({"type": "bool", "default": 1}, "bool"),
+            ({"type": "string", "default": 1}, "string"),
+            ({"type": "dict", "default": ["a", "b"]}, "dict"),
+            ({"type": "model", "default": "gigi hadid"}, "model"),
+            ({"type": "model", "default": False}, "model")
+        ]
+        for params, expected in cases:
+            with self.subTest(**params):
+                response = self.client.post(
+                    reverse("api:version-from-docker"),
+                    data={
+                        "docker_image_iid": "a_docker_image",
+                        "repository_url": self.repo.url,
+                        "revision_hash": "new_revision_hash",
+                        "worker_slug": self.worker.slug,
+                        "configuration": {
+                            "user_configuration": {
+                                "param": {"title": "param", **params}
+                            }
+                        },
+                    },
+                    format="json",
+                )
+                self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+                self.assertEqual(response.json(), {"configuration": {"user_configuration": [{"param": {"default": [f"This is not a valid value for a field of type {expected}."]}}]}})
+
+    def test_create_version_user_configuration_model_default_doesnt_exist(self):
+        self.client.force_login(self.user)
+        response = self.client.post(
+            reverse("api:version-from-docker"),
+            data={
+                "docker_image_iid": "a_docker_image",
+                "repository_url": self.repo.url,
+                "revision_hash": "new_revision_hash",
+                "worker_slug": self.worker.slug,
+                "configuration": {
+                    "user_configuration": {
+                        "param": {"title": "Model to train", "type": "model", "default": "12341234-1234-1234-1234-123412341234"}
+                    }
+                },
+            },
+            format="json",
+        )
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+        self.assertEqual(response.json(), {"configuration": {"user_configuration": [{"param": {"default": [
+            "Model 12341234-1234-1234-1234-123412341234 not found."
+        ]}}]}})
diff --git a/arkindex/process/tests/test_workers.py b/arkindex/process/tests/test_workers.py
index b1a24c423fe0b298b581d5a28522514b9762c6ae..42efd23b2a115cce187872da243f93f9ca7508d4 100644
--- a/arkindex/process/tests/test_workers.py
+++ b/arkindex/process/tests/test_workers.py
@@ -1552,7 +1552,7 @@ class TestWorkersWorkerVersions(FixtureAPITestCase):
             },
         })
 
-    def test_create_version_empty_configuration(self):
+    def test_create_version_configuration_wrong_type(self):
         """
         Configuration body must be an object
         """