diff --git a/deadlock-plugins/deadlock-extension/src/config.ts b/deadlock-plugins/deadlock-extension/src/config.ts
index 21960df6fea4d8e530bc89caaf217a4219f82ab8..fcbe08addba35d99f5fd64632720c89235514ed1 100644
--- a/deadlock-plugins/deadlock-extension/src/config.ts
+++ b/deadlock-plugins/deadlock-extension/src/config.ts
@@ -4,7 +4,6 @@ export const KEYCLOAK_TOKEN_CREATE_URL =
   'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token';
 export const KEYCLOAK_USER_INFO_URL =
   'https://auth.dev.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
-export const REGISTRY_MISSION_URL = 'registry.takima.io/deadlock/deadlock-challenges';
 export const REJECT_UNAUTHORIZED = false;
 export const ENABLE_AUTOMATIC_SAVE = true;
 export const ENABLE_RECORDER_HTTP_SERVER = false;
diff --git a/deadlock-plugins/deadlock-extension/src/core/controller.ts b/deadlock-plugins/deadlock-extension/src/core/controller.ts
index 7d3d362dff3265d01acf55d5ea49180c11d4453f..8a876b338555b9d2e6f0a0bebcc5060b6aaad5d3 100644
--- a/deadlock-plugins/deadlock-extension/src/core/controller.ts
+++ b/deadlock-plugins/deadlock-extension/src/core/controller.ts
@@ -1,10 +1,5 @@
 import * as vscode from 'vscode';
-import {
-  KEYCLOAK_DEVICE_AUTH_URL,
-  KEYCLOAK_TOKEN_CREATE_URL,
-  KEYCLOAK_USER_INFO_URL,
-  REGISTRY_MISSION_URL,
-} from '../config';
+import { KEYCLOAK_DEVICE_AUTH_URL, KEYCLOAK_TOKEN_CREATE_URL, KEYCLOAK_USER_INFO_URL } from '../config';
 
 import { log } from '../recorder/utils';
 import { OPEN_QUICK_SETUP_COMMAND } from '../theia/command';
@@ -13,7 +8,7 @@ import QuickSetupView from '../view/quickSetupView';
 import { CHOOSE_MISSION_WORKDIR_COMMAND, CommandHandler, OPEN_URL_IN_BROWSER_COMMAND } from './commandHandler';
 import ExtensionStore from './extensionStore';
 import KeycloakOAuth2DeviceFlowConnection from './keycloakOAuth2DeviceFlowConnection';
-import Mission from './mission';
+import Mission from './mission/mission';
 import ApiService from './api.service';
 import SshKeyManager from './sshKeyManager';
 import { GiteaPublicProperties } from '../model/giteaPublicProperties.model';
@@ -131,19 +126,19 @@ export default class Controller {
 
     const user: User = await this.callApiService.getUser();
     const giteaPublicProperties: GiteaPublicProperties = await this.callApiService.getGiteaPublicProperties();
-    const mission = new Mission(
-      {
-        registryBaseURL: REGISTRY_MISSION_URL,
-        missionId: missionId,
-        missionVersion: missionVersion,
-      },
-      user,
-      giteaPublicProperties,
-    );
-    vscode.window.showInformationMessage(
-      'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId,
-    );
-    await mission.setup();
+
+      const mission = new Mission(
+        {
+          missionId: missionId,
+          missionVersion: missionVersion,
+        },
+        user,
+        giteaPublicProperties,
+      );
+      vscode.window.showInformationMessage(
+        'opening inside folder ' + this.extensionStore.getMissionWorkdir()! + '/' + missionId,
+      );
+      await mission.setup();
 
     await mission.openEditorInFolder();
 
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission.ts
deleted file mode 100644
index da765ccb2724f4dd0f1f04f3e7c6401fdbb22783..0000000000000000000000000000000000000000
--- a/deadlock-plugins/deadlock-extension/src/core/mission.ts
+++ /dev/null
@@ -1,177 +0,0 @@
-import { exec as _exec } from 'child_process';
-import * as fs from 'fs';
-import * as util from 'util';
-import * as vscode from 'vscode';
-import { error as err, log } from '../recorder/utils';
-import ExtensionStore from './extensionStore';
-import { userSshKeyFolderPath } from './config';
-import { GiteaPublicProperties } from '../model/giteaPublicProperties.model';
-import { User, UserChallengeJson } from '../model/user.model';
-
-/**
- * {@link https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback}
- */
-const exec = util.promisify(_exec);
-
-export default class Mission {
-  private readonly hostBaseWorkDir: string;
-  private readonly dockerImageURL: string;
-  private readonly hostMissionDir: string;
-  private readonly hostMissionDevcontainerDir: string;
-  private readonly hostMissionDevcontainerFileDir: string;
-  private readonly hostMissionMountDir: string;
-  private readonly remoteUserHomeDir: string;
-  private readonly remoteMissionDir: any;
-  private readonly remoteGiteaWorkDir: string;
-  private readonly userMissionConfigPath: string;
-  private readonly user: User;
-  private readonly giteaPublicProperties: GiteaPublicProperties;
-
-  constructor(
-    private params: { registryBaseURL: string; missionId: string; missionVersion: string },
-    user: User,
-    giteaPublicProperties: GiteaPublicProperties,
-  ) {
-    const { registryBaseURL, missionId, missionVersion } = params;
-    this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? '';
-    this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`;
-    this.userMissionConfigPath = `${this.hostMissionDir}/.config`;
-    this.hostMissionDevcontainerDir = `${this.hostMissionDir}/.devcontainer`;
-    this.hostMissionDevcontainerFileDir = `${this.hostMissionDevcontainerDir}/devcontainer.json`;
-    this.hostMissionMountDir = `${this.hostMissionDir}/mounted`;
-    this.dockerImageURL = getDockerImageURL(registryBaseURL, missionId, missionVersion);
-    this.remoteUserHomeDir = '/home/deadlock';
-    this.remoteMissionDir = `${this.remoteUserHomeDir}/mission/`;
-    this.remoteGiteaWorkDir = `/src`;
-    this.user = user;
-    this.giteaPublicProperties = giteaPublicProperties;
-  }
-
-  static async pullImage(url) {
-    const { stdout, stderr } = await exec(`docker pull ${url}`);
-    log(stdout);
-    err(stderr);
-  }
-
-  public async setup(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) {
-    await fs.promises.mkdir(this.hostMissionDevcontainerDir, { recursive: true });
-    await fs.promises.mkdir(this.hostMissionMountDir, { recursive: true });
-    await fs.promises.mkdir(this.userMissionConfigPath, { recursive: true });
-    await this.createDevContainerFile(options);
-    await this.createUserChallengeJsonFile();
-  }
-
-  private createDevContainerFile(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) {
-    return fs.promises.writeFile(
-      this.hostMissionDevcontainerFileDir,
-      (() => {
-        const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = {
-          name: `deadlock-${this.params.missionId}`,
-          image: this.dockerImageURL,
-          extensions: ['Deadlock.deadlock-coding'],
-          remoteUser: 'deadlock',
-          mounts: [
-            `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached,readonly`,
-            `source=${this.userMissionConfigPath},target=/home/config/,type=bind,consistency=cached,readonly`,
-            'source=/etc/hosts,target=/etc/hosts,type=bind,consistency=cached,readonly',
-          ],
-          userEnvProbe: 'interactiveShell',
-          settings: {
-            'terminal.integrated.defaultProfile.linux': 'bash',
-            'terminal.integrated.profiles.linux': {
-              bash: {
-                path: '/bin/bash',
-              },
-            },
-          },
-          overrideCommand: false,
-          shutdownAction: 'stopContainer',
-          workspaceMount: `source=${this.hostMissionMountDir},target=${this.remoteMissionDir},type=bind`,
-          workspaceFolder: `${this.remoteMissionDir}`,
-          onCreateCommand: `cp -R ${this.remoteGiteaWorkDir}/. ${this.remoteMissionDir}`,
-          runArgs: ['--privileged'],
-          ...options,
-        };
-        return JSON.stringify(devcontainer, null, 2);
-      })(),
-    );
-  }
-
-  private createUserChallengeJsonFile() {
-    return fs.promises.writeFile(
-      `${this.userMissionConfigPath}/user-challenge.json`,
-      (() => {
-        const userChallengeJson: UserChallengeJson = {
-          giteaHost: this.giteaPublicProperties.sshHost,
-          giteaSshPort: this.giteaPublicProperties.sshPort,
-          username: this.user.id.split('-').join(''),
-          email: `${this.user.id.split('-').join('')}@deadlock.io`,
-          missionId: this.params.missionId,
-          remoteGitUsername: this.user.id.split('-').join(''),
-          currentUserDetails: this.user.details,
-          remoteUserDetails: this.user.details,
-        };
-        return JSON.stringify(userChallengeJson, null, 2);
-      })(),
-    );
-  }
-
-  public async openEditorInFolder(arbitraryPath?: string) {
-    if (arbitraryPath) {
-      return vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(arbitraryPath));
-    }
-    if (!fs.existsSync(this.hostMissionDir)) {
-      log('WARN missing path ', this.hostMissionDir);
-      await fs.promises.mkdir(this.hostMissionDir, { recursive: true });
-    }
-    await vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(this.hostMissionDir));
-  }
-}
-
-export function getDockerImageURL(base, missionId, missionVersion) {
-  return `${base}/${missionId}:${missionVersion}`;
-}
-interface DockerfileSpecific {
-  image?;
-  dockerFile?;
-  context?;
-  'build.args'?;
-  'build.target'?;
-  'build.cacheFrom'?;
-  containerEnv?;
-  containerUser?;
-  mounts?;
-  workspaceMount?;
-  workspaceFolder?;
-  runArgs?;
-}
-
-interface Base {
-  name?;
-  forwardPorts?;
-  portsAttributes?;
-  otherPortsAttributes?;
-  remoteEnv?;
-  remoteUser?;
-  updateRemoteUserUID?;
-  userEnvProbe?;
-  overrideCommand?;
-  features?;
-  shutdownAction?;
-}
-
-interface VSCodespecific {
-  extensions?;
-  settings?;
-  devPort?;
-}
-
-interface LifecycleScripts {
-  initializeCommand?;
-  onCreateCommand?;
-  updateContentCommand?;
-  postCreateCommand?;
-  postStartCommand?;
-  postAttachCommand?;
-  waitFor?;
-}
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/devContainer.ts b/deadlock-plugins/deadlock-extension/src/core/mission/devContainer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a0a4ca41c3444e16f126984ec799cf5306835b3a
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/mission/devContainer.ts
@@ -0,0 +1,44 @@
+export interface DockerfileSpecific {
+  image?: string;
+  dockerFile?: string;
+  context?: string;
+  'build.args'?: string[];
+  'build.target'?: string;
+  'build.cacheFrom'?: string;
+  containerEnv?: string;
+  containerUser?: string;
+  mounts?: string[];
+  workspaceMount?: string;
+  workspaceFolder?: string;
+  runArgs?: string[];
+}
+
+export interface Base {
+  name?: string;
+  forwardPorts?: string[];
+  portsAttributes?: string[];
+  otherPortsAttributes?: string[];
+  remoteEnv?: string;
+  remoteUser?: string;
+  updateRemoteUserUID?: string;
+  userEnvProbe?: string;
+  overrideCommand?: boolean;
+  features?: string[];
+  shutdownAction?: string;
+}
+
+export interface VSCodespecific {
+  extensions?: string[];
+  settings?: object;
+  devPort?: string;
+}
+
+export interface LifecycleScripts {
+  initializeCommand?: string;
+  onCreateCommand?: string;
+  updateContentCommand?: string;
+  postCreateCommand?: string;
+  postStartCommand?: string;
+  postAttachCommand?: string;
+  waitFor?: string;
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dc1808af35e68af50ece12803ae0a9ba0fc1d119
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/mission/mission.ts
@@ -0,0 +1,64 @@
+import { exec as _exec } from 'child_process';
+import * as fs from 'fs';
+import * as util from 'util';
+import * as vscode from 'vscode';
+import { error as err, log } from '../../recorder/utils';
+import ExtensionStore from '../extensionStore';
+import { userSshKeyFolderPath } from '../config';
+import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model';
+import { User, UserChallengeJson } from '../../model/user.model';
+import { createDevContainerFile } from './missionDevContainer';
+import { DockerfileSpecific, Base, VSCodespecific, LifecycleScripts } from './devContainer';
+import { MissionResource } from './missionResources';
+import { createUserChallengeJsonFile } from './missionUserChallenge';
+
+/**
+ * {@link https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback}
+ */
+const exec = util.promisify(_exec);
+
+export default class Mission {
+  private readonly hostBaseWorkDir: string;
+  private readonly hostMissionDir: string;
+  private readonly hostMissionDevcontainerDir: string;
+  private readonly hostMissionMountDir: string;
+  private readonly userMissionConfigPath: string;
+  private readonly user: User;
+  private readonly giteaPublicProperties: GiteaPublicProperties;
+  private readonly missionRessource: MissionResource;
+
+  constructor(
+    private params: { missionId: string; missionVersion: string },
+    user: User,
+    giteaPublicProperties: GiteaPublicProperties,
+  ) {
+    const { missionId, missionVersion } = params;
+    this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? '';
+    this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`;
+    this.userMissionConfigPath = `${this.hostMissionDir}/.config`;
+    this.hostMissionDevcontainerDir = `${this.hostMissionDir}/.devcontainer`;
+    this.hostMissionMountDir = `${this.hostMissionDir}/mounted`;
+    this.user = user;
+    this.giteaPublicProperties = giteaPublicProperties;
+    this.missionRessource = new MissionResource(this.hostMissionDir, missionId, missionVersion);
+  }
+
+  public async setup(options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>) {
+    await fs.promises.mkdir(this.hostMissionDevcontainerDir, { recursive: true });
+    await fs.promises.mkdir(this.hostMissionMountDir, { recursive: true });
+    await fs.promises.mkdir(this.userMissionConfigPath, { recursive: true });
+    await createDevContainerFile(this.missionRessource, options);
+    await createUserChallengeJsonFile(this.user, this.missionRessource, this.giteaPublicProperties);
+  }
+
+  public async openEditorInFolder(arbitraryPath?: string) {
+    if (arbitraryPath) {
+      return vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(arbitraryPath));
+    }
+    if (!fs.existsSync(this.hostMissionDir)) {
+      log('WARN missing path ', this.hostMissionDir);
+      await fs.promises.mkdir(this.hostMissionDir, { recursive: true });
+    }
+    await vscode.commands.executeCommand('remote-containers.openFolder', vscode.Uri.file(this.hostMissionDir));
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..37403ba5a04af97494906c0453e5c3818ecc4c69
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionDevContainer.ts
@@ -0,0 +1,57 @@
+import { userSshKeyFolderPath } from '../config';
+import ExtensionStore from '../extensionStore';
+import { Base, DockerfileSpecific, LifecycleScripts, VSCodespecific } from './devContainer';
+import * as fs from 'fs';
+import { MissionResource } from './missionResources';
+
+const DOCKER_IMAGE_URL = 'registry.takima.io/deadlock/deadlock-challenges';
+
+export function createDevContainerFile(
+  mission: MissionResource,
+  options?: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts>,
+) {
+  const hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? '';
+  const hostMissionDir = `${hostBaseWorkDir}/${mission.id}`;
+  const userMissionConfigPath = `${hostMissionDir}/.config`;
+  const hostMissionDevcontainerDir = `${hostMissionDir}/.devcontainer`;
+  const hostMissionDevcontainerFileDir = `${hostMissionDevcontainerDir}/devcontainer.json`;
+  const hostMissionMountDir = `${hostMissionDir}/mounted`;
+  const dockerImageURL = `${DOCKER_IMAGE_URL}/${mission.id}:${mission.version}`;
+  const remoteUserHomeDir = '/home/deadlock';
+  const remoteMissionDir = `${remoteUserHomeDir}/mission/`;
+  const remoteGiteaWorkDir = `/src`;
+
+  return fs.promises.writeFile(
+    hostMissionDevcontainerFileDir,
+    (() => {
+      const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = {
+        name: `deadlock-${mission.id}`,
+        image: dockerImageURL,
+        extensions: ['Deadlock.deadlock-coding'],
+        remoteUser: 'deadlock',
+        mounts: [
+          `source=${userSshKeyFolderPath},target=/tmp/.ssh,type=bind,consistency=cached,readonly`,
+          `source=${userMissionConfigPath},target=/home/config/,type=bind,consistency=cached,readonly`,
+          'source=/etc/hosts,target=/etc/hosts,type=bind,consistency=cached,readonly',
+        ],
+        userEnvProbe: 'interactiveShell',
+        settings: {
+          'terminal.integrated.defaultProfile.linux': 'bash',
+          'terminal.integrated.profiles.linux': {
+            bash: {
+              path: '/bin/bash',
+            },
+          },
+        },
+        overrideCommand: false,
+        shutdownAction: 'stopContainer',
+        workspaceMount: `source=${hostMissionMountDir},target=${remoteMissionDir},type=bind`,
+        workspaceFolder: `${remoteMissionDir}`,
+        onCreateCommand: `cp -R ${remoteGiteaWorkDir}/. ${remoteMissionDir}`,
+        runArgs: ['--privileged'],
+        ...options,
+      };
+      return JSON.stringify(devcontainer, null, 2);
+    })(),
+  );
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionDocker.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionDocker.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionResources.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionResources.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7196429d9554c1f32a7957e16f96b30258c983ac
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionResources.ts
@@ -0,0 +1,7 @@
+export class MissionResource {
+  constructor(public missionDir: string, public id: string, public version: string) {
+    this.missionDir = missionDir;
+    this.id = id;
+    this.version = version;
+  }
+}
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts b/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts
new file mode 100644
index 0000000000000000000000000000000000000000..13166798da2d7c09d2d17b7177f4d0dc6cce0616
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/mission/missionUserChallenge.ts
@@ -0,0 +1,27 @@
+import { User, UserChallengeJson } from '../../model/user.model';
+import * as fs from 'fs';
+import { GiteaPublicProperties } from '../../model/giteaPublicProperties.model';
+import { MissionResource } from './missionResources';
+
+export function createUserChallengeJsonFile(
+  user: User,
+  mission: MissionResource,
+  giteaPublicProperties: GiteaPublicProperties,
+) {
+  return fs.promises.writeFile(
+    `${mission.missionDir}/.config/user-challenge.json`,
+    (() => {
+      const userChallengeJson: UserChallengeJson = {
+        giteaHost: giteaPublicProperties.sshHost,
+        giteaSshPort: giteaPublicProperties.sshPort,
+        username: user.id.split('-').join(''),
+        email: `${user.id.split('-').join('')}@deadlock.io`,
+        missionId: mission.id,
+        remoteGitUsername: user.id.split('-').join(''),
+        currentUserDetails: user.details,
+        remoteUserDetails: user.details,
+      };
+      return JSON.stringify(userChallengeJson, null, 2);
+    })(),
+  );
+}