diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c904a29fca936f9ddd886cc9c518ab8385c826c4
--- /dev/null
+++ b/helpers/Dojo/AssignmentValidator.ts
@@ -0,0 +1,258 @@
+import { TypedEmitter }          from 'tiny-typed-emitter';
+import AssignmentValidatorEvents from '../../types/Dojo/AssignmentValidatorEvents';
+import SharedAssignmentHelper    from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
+import path                      from 'node:path';
+import AssignmentCheckerError    from '../../../shared/types/Dojo/AssignmentCheckerError';
+import fs                        from 'fs-extra';
+import JSON5                     from 'json5';
+import ClientsSharedConfig       from '../../config/ClientsSharedConfig';
+import YAML                      from 'yaml';
+import DojoDockerCompose         from '../../types/Dojo/DojoDockerCompose';
+import { exec, spawn }           from 'child_process';
+import AssignmentFile            from '../../../shared/types/Dojo/AssignmentFile';
+import ExerciseDockerCompose     from './ExerciseDockerCompose';
+import util                      from 'util';
+
+
+const execAsync = util.promisify(exec);
+
+
+class AssignmentValidator {
+    readonly events: TypedEmitter<AssignmentValidatorEvents> = new TypedEmitter<AssignmentValidatorEvents>();
+
+    public displayableLogs: string = '';
+    public allLogs: string = '';
+
+    public isFinished: boolean = false;
+    public success: boolean = false;
+    public exitCode: number = -1;
+
+    constructor(private folderAssignment: string) {
+        this.events.on('logs', (log: string, _error: boolean, displayable: boolean) => {
+            this.allLogs += log;
+            this.displayableLogs += displayable ? log : '';
+        });
+
+        this.events.on('finished', (success: boolean, exitCode: number) => {
+            this.isFinished = true;
+            this.success = success;
+            this.exitCode = exitCode;
+        });
+    }
+
+    run(doDown: boolean = false) {
+        (async () => {
+            let dockerComposeFile: DojoDockerCompose;
+            let assignmentFile: AssignmentFile;
+
+            const emitError = (subStepName: string, subStepMessage: string, stepName: string, stepMessage: string, code: AssignmentCheckerError) => {
+                this.events.emit('endSubStep', subStepName, subStepMessage, true);
+                this.events.emit('endStep', stepName, stepMessage, true);
+                this.events.emit('finished', false, code);
+            };
+
+
+            /*
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1: Check requirements
+             -   Check if Docker daemon is running
+             -   Check if required files exists
+             */
+            {
+                this.events.emit('step', 'REQUIREMENTS_CHECKING', 'Please wait while we are checking if Docker daemon is running...');
+
+
+                // Check requirements
+                this.events.emit('subStep', 'DOCKER_RUNNING', 'Checking if Docker daemon is running');
+                try {
+                    await execAsync(`cd "${ this.folderAssignment }";docker ps`);
+                } catch ( error ) {
+                    emitError('DOCKER_RUNNING', `Docker daemon isn't running`, 'REQUIREMENTS_CHECKING', `Some requirements are not satisfied.`, AssignmentCheckerError.DOCKER_DAEMON_NOT_RUNNING);
+                    return;
+                }
+                this.events.emit('endSubStep', 'DOCKER_RUNNING', 'Docker daemon is running', false);
+
+
+                // Check if required files exists
+                this.events.emit('subStep', 'REQUIRED_FILES_EXISTS', 'Checking if required files exists');
+                const files = fs.readdirSync(this.folderAssignment);
+                const missingFiles = ClientsSharedConfig.assignment.neededFiles.map((file: string): [ string, boolean ] => [ file, files.includes(file) ]).filter((file: [ string, boolean ]) => !file[1]);
+
+                if ( missingFiles.length > 0 ) {
+                    emitError('REQUIRED_FILES_EXISTS', `The exercise folder is missing the following files: ${ missingFiles.map((file: [ string, boolean ]) => file[0]).join(', ') }`, 'REQUIREMENTS_CHECKING', 'Some requirements are not satisfied', AssignmentCheckerError.REQUIRED_FILES_MISSING);
+                    return;
+                }
+                this.events.emit('endSubStep', 'REQUIRED_FILES_EXISTS', 'All required files exists', false);
+
+
+                this.events.emit('endStep', 'REQUIREMENTS_CHECKING', 'All requirements are satisfied', false);
+            }
+
+
+            /*
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: dojo_assignment.json file validation
+             -   Structure validation
+             -   Immutable files validation (Check if exists and if the given type is correct)
+             */
+            {
+                this.events.emit('step', 'ASSIGNMENT_FILE_VALIDATION', 'Please wait while we are validating dojo_assignment.json file...');
+
+
+                // Structure validation
+                this.events.emit('subStep', 'ASSIGNMENT_FILE_SCHEMA_VALIDATION', 'Validating dojo_assignment.json file schema');
+                const validationResults = SharedAssignmentHelper.validateDescriptionFile(path.join(this.folderAssignment, ClientsSharedConfig.assignment.filename));
+                if ( !validationResults.isValid ) {
+                    emitError('ASSIGNMENT_FILE_SCHEMA_VALIDATION', `dojo_assignment.json file schema is invalid.\nHere are the errors:\n${ JSON5.stringify(validationResults.errors) }`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.ASSIGNMENT_FILE_SCHEMA_ERROR);
+                    return;
+                }
+                assignmentFile = validationResults.results!;
+                this.events.emit('endSubStep', 'ASSIGNMENT_FILE_SCHEMA_VALIDATION', 'dojo_assignment.json file schema is valid', false);
+
+
+                // Immutable files validation (Check if exists and if the given type is correct)
+                this.events.emit('subStep', 'ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Validating immutable files');
+                for ( const immutable of validationResults.results!.immutable ) {
+                    const immutablePath = path.join(this.folderAssignment, immutable.path);
+                    if ( !fs.existsSync(immutablePath) ) {
+                        emitError('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', `Immutable path not found: ${ immutable.path }`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_NOT_FOUND);
+                        return;
+                    }
+
+                    const isDirectory = fs.lstatSync(immutablePath).isDirectory();
+                    if ( isDirectory && !immutable.isDirectory ) {
+                        emitError('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', `Immutable (${ immutable.path }) is declared as a file but is a directory.`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_IS_NOT_DIRECTORY);
+                        return;
+                    } else if ( !isDirectory && immutable.isDirectory === true ) {
+                        emitError('ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', `Immutable (${ immutable.path }) is declared as a directory but is a file.`, 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is invalid', AssignmentCheckerError.IMMUTABLE_PATH_IS_DIRECTORY);
+                        return;
+                    }
+                }
+                this.events.emit('endSubStep', 'ASSIGNMENT_FILE_IMMUTABLES_VALIDATION', 'Immutable files are valid', false);
+
+
+                this.events.emit('endStep', 'ASSIGNMENT_FILE_VALIDATION', 'dojo_assignment.json file is valid', false);
+            }
+
+
+            /*
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 3: Docker Compose file validation
+             -   Global validation
+             -   Validation of the containers and volumes named in dojo_assignment.json
+             */
+            {
+                this.events.emit('step', 'DOCKER_COMPOSE_VALIDATION', 'Please wait while we are validating docker compose file...');
+
+
+                // Global validation
+                this.events.emit('subStep', 'DOCKER_COMPOSE_STRUCTURE_VALIDATION', 'Docker compose file structure validation');
+                try {
+                    dockerComposeFile = YAML.parse(fs.readFileSync(path.join(this.folderAssignment, 'docker-compose.yml'), 'utf8')) as DojoDockerCompose;
+                } catch ( error ) {
+                    emitError('DOCKER_COMPOSE_STRUCTURE_VALIDATION', `Docker compose file yaml structure is invalid.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_YAML_ERROR);
+                    return;
+                }
+
+                try {
+                    await new Promise<void>((resolve, reject) => {
+                        const dockerComposeValidation = spawn(`docker compose -f docker-compose.yml config --quiet`, {
+                            cwd  : this.folderAssignment,
+                            shell: true
+                        });
+
+                        dockerComposeValidation.on('exit', (code) => {
+                            code !== null && code == 0 ? resolve() : reject();
+                        });
+                    });
+                } catch ( error ) {
+                    emitError('DOCKER_COMPOSE_STRUCTURE_VALIDATION', `Docker compose file structure is invalid.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_SCHEMA_ERROR);
+                    return;
+                }
+                this.events.emit('endSubStep', 'DOCKER_COMPOSE_STRUCTURE_VALIDATION', 'Docker compose file structure is valid', false);
+
+
+                // Validation of the containers and volumes named in dojo_assignment.json
+                this.events.emit('subStep', 'DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content validation');
+                if ( !(assignmentFile.result.container in dockerComposeFile!.services) ) {
+                    emitError('DOCKER_COMPOSE_CONTENT_VALIDATION', `Container specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_CONTAINER_MISSING);
+                    return;
+                }
+                if ( assignmentFile.result.volume && (!dockerComposeFile!.volumes || !(assignmentFile.result.volume in dockerComposeFile!.volumes)) ) {
+                    emitError('DOCKER_COMPOSE_CONTENT_VALIDATION', `Volume specified in ${ ClientsSharedConfig.assignment.filename } is missing from compose file.`, 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is invalid', AssignmentCheckerError.COMPOSE_FILE_VOLUME_MISSING);
+                    return;
+                }
+                this.events.emit('endSubStep', 'DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content is valid', false);
+
+
+                this.events.emit('endStep', 'DOCKER_COMPOSE_VALIDATION', 'Docker compose file is valid', false);
+            }
+
+
+            /*
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 4: Dockerfiles validation
+             -   Check if file exists
+             -   TODO - Dockerfile structure linter - Issue #51 - https://github.com/hadolint/hadolint
+             */
+            {
+                this.events.emit('step', 'DOCKERFILE_VALIDATION', 'Please wait while we are validating dockerfiles...');
+
+
+                this.events.emit('subStep', 'DOCKERFILE_EXIST', 'Docker compose file content validation');
+                const dockerfilesPaths = Object.values(dockerComposeFile!.services).filter((service) => service.build).map((service) => path.join(this.folderAssignment, service.build!.context ?? '', service.build!.dockerfile!));
+                const filesNotFound = dockerfilesPaths.filter((dockerfilePath) => !fs.existsSync(dockerfilePath));
+                if ( filesNotFound.length > 0 ) {
+                    emitError('DOCKERFILE_VALIDATION', `Dockerfiles not found: ${ filesNotFound.join(', ') }`, 'DOCKERFILE_VALIDATION', 'Dockerfiles are invalid', AssignmentCheckerError.DOCKERFILE_NOT_FOUND);
+                    return;
+                }
+                this.events.emit('endSubStep', 'DOCKER_COMPOSE_CONTENT_VALIDATION', 'Docker compose file content is valid', false);
+
+
+                this.events.emit('endStep', 'DOCKERFILE_VALIDATION', 'Dockerfiles are valid', false);
+            }
+
+
+            /*
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 5: Run
+             -   Make a run of the assignment (If the return code is 0, the assignment is not valid because it means that there no need of modification for succeed the exercise)
+             */
+            {
+                this.events.emit('step', 'ASSIGNMENT_RUN', 'Please wait while we are running the assignment...');
+
+
+                const exerciseDockerCompose = new ExerciseDockerCompose(ClientsSharedConfig.dockerCompose.projectName, assignmentFile, this.folderAssignment);
+
+                try {
+                    await new Promise<void>((resolve, reject) => {
+                        exerciseDockerCompose.events.on('logs', (log: string, error: boolean, displayable: boolean) => {
+                            this.events.emit('logs', log, error, displayable);
+                        });
+
+                        exerciseDockerCompose.events.on('step', (name: string, message: string) => {
+                            this.events.emit('subStep', name, message);
+                        });
+
+                        exerciseDockerCompose.events.on('endStep', (stepName: string, message: string, error: boolean) => {
+                            this.events.emit('endSubStep', stepName, message, error);
+                        });
+
+                        exerciseDockerCompose.events.on('finished', (success: boolean, exitCode: number) => {
+                            exitCode != 0 ? resolve() : reject();
+                        });
+
+                        exerciseDockerCompose.run(doDown);
+                    });
+                } catch ( error ) {
+                    this.events.emit('endStep', 'ASSIGNMENT_RUN', 'Assignment is already solved', true);
+                    this.events.emit('finished', false, AssignmentCheckerError.COMPOSE_RUN_SUCCESSFULLY);
+                }
+
+
+                this.events.emit('endStep', 'ASSIGNMENT_RUN', 'Assignment run successfully', false);
+            }
+
+
+            this.events.emit('finished', true, 0);
+        })();
+    }
+}
+
+
+export default AssignmentValidator;
\ No newline at end of file
diff --git a/types/Dojo/AssignmentValidatorEvents.ts b/types/Dojo/AssignmentValidatorEvents.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eb13e4ad0c1497bb23f23feb89cbd0c4e02115cd
--- /dev/null
+++ b/types/Dojo/AssignmentValidatorEvents.ts
@@ -0,0 +1,11 @@
+interface AssignmentValidatorEvents {
+    step: (name: string, message: string) => void;
+    subStep: (name: string, message: string) => void;
+    endStep: (subStepName: string, message: string, error: boolean) => void;
+    endSubStep: (subStepName: string, message: string, error: boolean) => void;
+    logs: (log: string, error: boolean, displayable: boolean) => void;
+    finished: (success: boolean, exitCode: number) => void;
+}
+
+
+export default AssignmentValidatorEvents;
\ No newline at end of file
diff --git a/types/Dojo/DojoDockerCompose.ts b/types/Dojo/DojoDockerCompose.ts
new file mode 100644
index 0000000000000000000000000000000000000000..03b83f014c520d39289dad39b154d71dee49be4c
--- /dev/null
+++ b/types/Dojo/DojoDockerCompose.ts
@@ -0,0 +1,15 @@
+interface DojoDockerCompose {
+    services: {
+        [serviceName: string]: Partial<{
+            container_name: string; image: string; build: Partial<{
+                context: string; dockerfile: string;
+            }>
+        }>
+    };
+    volumes?: {
+        [volumeName: string]: null
+    };
+}
+
+
+export default DojoDockerCompose;
\ No newline at end of file