diff --git a/deadlock-plugins/deadlock-extension/src/config.prod.ts b/deadlock-plugins/deadlock-extension/src/config.prod.ts
index 80c3d02a4657f8ae232f5ce39fe43447cad805d2..40beee92f6ff88f45ecca11fd9946b6da96571cf 100644
--- a/deadlock-plugins/deadlock-extension/src/config.prod.ts
+++ b/deadlock-plugins/deadlock-extension/src/config.prod.ts
@@ -2,3 +2,4 @@ export const KEYCLOAK_DEVICE_AUTH_URL =
   'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/auth/device';
 export const KEYCLOAK_TOKEN_CREATE_URL = 'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token';
 export const KEYCLOAK_USER_INFO_URL = 'https://auth.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
+export const REGISTRY_MISSION_URL = 'registry.e-biz.fr/deadlock/deadlock-challenges';
diff --git a/deadlock-plugins/deadlock-extension/src/config.staging.ts b/deadlock-plugins/deadlock-extension/src/config.staging.ts
index 757b1b4dfe1e2593f8215cff490499b5000e601d..5030cdd73e8e942bf5f0ebe0906c96b3088feeac 100644
--- a/deadlock-plugins/deadlock-extension/src/config.staging.ts
+++ b/deadlock-plugins/deadlock-extension/src/config.staging.ts
@@ -4,3 +4,4 @@ export const KEYCLOAK_TOKEN_CREATE_URL =
   'https://auth.staging.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/token';
 export const KEYCLOAK_USER_INFO_URL =
   'https://auth.staging.deadlock.io/auth/realms/Deadlock/protocol/openid-connect/userinfo';
+export const REGISTRY_MISSION_URL = 'registry.e-biz.fr/deadlock/deadlock-challenges';
diff --git a/deadlock-plugins/deadlock-extension/src/config.ts b/deadlock-plugins/deadlock-extension/src/config.ts
index 4ea235cfab69d493e8eff3d659173a2972fead3f..eb332e792712a5598b88bdab2e38ec43ab137f5a 100644
--- a/deadlock-plugins/deadlock-extension/src/config.ts
+++ b/deadlock-plugins/deadlock-extension/src/config.ts
@@ -4,3 +4,4 @@ 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.e-biz.fr/deadlock/deadlock-challenges';
diff --git a/deadlock-plugins/deadlock-extension/src/core/mission.ts b/deadlock-plugins/deadlock-extension/src/core/mission.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8354996a360f37958654c69a95f5dc25697183b6
--- /dev/null
+++ b/deadlock-plugins/deadlock-extension/src/core/mission.ts
@@ -0,0 +1,135 @@
+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';
+
+/**
+ * {@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;
+  constructor(private params: { registryBaseURL: string; missionId: string; missionVersion: string }) {
+    const { registryBaseURL, missionId, missionVersion } = params;
+    this.hostBaseWorkDir = ExtensionStore.getInstance().getMissionWorkdir() ?? '';
+    this.hostMissionDir = `${this.hostBaseWorkDir}/${missionId}`;
+    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 = `/project`;
+  }
+
+  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.writeFile(
+      this.hostMissionDevcontainerFileDir,
+      (() => {
+        const devcontainer: Partial<DockerfileSpecific & Base & VSCodespecific & LifecycleScripts> = {
+          name: `deadlock-${this.params.missionId}`,
+          image: this.dockerImageURL,
+          extensions: ['Deadlock.deadlock-coding'],
+          remoteUser: 'theia',
+          // mounts: [
+          //   `source=${this.hostMissionDir}/workspace,target=${'/workspace'},type=${'volume'},consistency=${'cached'}`,
+          // ],
+          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}`,
+          ...options,
+        };
+        return JSON.stringify(devcontainer, 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?;
+}