diff --git a/errors/MissingTutorialDependency.md b/errors/MissingTutorialDependency.md
new file mode 100644
index 00000000..7e989371
--- /dev/null
+++ b/errors/MissingTutorialDependency.md
@@ -0,0 +1,3 @@
+### Missing Tutorial Dependency
+
+The tutorial cannot run because it a dependency is not yet installed. Install the dependency and click "Check Again".
diff --git a/errors/UnmetTutorialDependency.md b/errors/UnmetTutorialDependency.md
new file mode 100644
index 00000000..c3524680
--- /dev/null
+++ b/errors/UnmetTutorialDependency.md
@@ -0,0 +1,5 @@
+### Unmet Tutorial Dependency
+
+### Unmet Tutorial Dependency
+
+Tutorial cannot reun because a dependency version doesn't match. Install the correct dependency and click "Check Again".
diff --git a/package-lock.json b/package-lock.json
index e4cf03ca..c2782431 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -66,6 +66,11 @@
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
         },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        },
         "source-map": {
           "version": "0.5.7",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -1003,6 +1008,14 @@
       "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz",
       "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ=="
     },
+    "@types/semver": {
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.1.0.tgz",
+      "integrity": "sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/stack-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -1701,6 +1714,13 @@
         "semver": "^5.5.0",
         "shebang-command": "^1.2.0",
         "which": "^1.2.9"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+        }
       }
     },
     "cssom": {
@@ -5335,9 +5355,9 @@
       }
     },
     "semver": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.2.2.tgz",
+      "integrity": "sha512-Zo84u6o2PebMSK3zjJ6Zp5wi8VnQZnEaCP13Ul/lt1ANsLACxnJxq4EEm1PY94/por1Hm9+7xpIswdS5AkieMA=="
     },
     "set-blocking": {
       "version": "2.0.0",
@@ -6121,6 +6141,12 @@
             }
           }
         },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        },
         "vscode-test": {
           "version": "0.4.3",
           "resolved": "https://registry.npmjs.org/vscode-test/-/vscode-test-0.4.3.tgz",
diff --git a/package.json b/package.json
index a75f0ab3..ddc089bd 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
     "@types/jest": "^25.2.1",
     "@types/jsdom": "^16.2.0",
     "@types/node": "^13.11.0",
+    "@types/semver": "^7.1.0",
     "@typescript-eslint/eslint-plugin": "^2.26.0",
     "@typescript-eslint/parser": "^2.26.0",
     "chokidar": "^3.3.0",
@@ -49,6 +50,7 @@
     "jest": "^25.2.7",
     "jsdom": "^16.2.2",
     "prettier": "^2.0.2",
+    "semver": "^7.2.2",
     "ts-jest": "^25.3.1",
     "typescript": "^3.8.3"
   },
diff --git a/src/channel/index.ts b/src/channel/index.ts
index 3781f71c..629a735b 100644
--- a/src/channel/index.ts
+++ b/src/channel/index.ts
@@ -9,11 +9,12 @@ import tutorialConfig from '../actions/tutorialConfig'
 import { COMMANDS } from '../editor/commands'
 import logger from '../services/logger'
 import Context from './context'
-import { version as gitVersion } from '../services/git'
+import { version, compareVersions } from '../services/dependencies'
 import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
 import { readFile } from 'fs'
 import { join } from 'path'
 import { promisify } from 'util'
+import { compare } from 'semver'
 
 const readFileAsync = promisify(readFile)
 
@@ -94,6 +95,63 @@ class Channel implements Channel {
         // setup tutorial config (save watcher, test runner, etc)
         await this.context.setTutorial(this.workspaceState, data)
 
+        // validate dependencies
+        const dependencies = data.config.dependencies
+        if (dependencies && dependencies.length) {
+          for (const dep of dependencies) {
+            // check dependency is installed
+            const currentVersion: string | null = await version(dep.name)
+            if (!currentVersion) {
+              // use a custom error message
+              const error = {
+                type: 'MissingTutorialDependency',
+                message: dep.message || `Process "${dep.name}" is required but not found. It may need to be installed`,
+                actions: [
+                  {
+                    label: 'Check Again',
+                    transition: 'TRY_AGAIN',
+                  },
+                ],
+              }
+              this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+              return
+            }
+
+            // check dependency version
+            const satisfiedDependency = await compareVersions(currentVersion, dep.version)
+
+            if (!satisfiedDependency) {
+              const error = {
+                type: 'UnmetTutorialDependency',
+                message: `Expected ${dep.name} to have version ${dep.version}, but found version ${currentVersion}`,
+                actions: [
+                  {
+                    label: 'Check Again',
+                    transition: 'TRY_AGAIN',
+                  },
+                ],
+              }
+              this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+              return
+            }
+
+            if (satisfiedDependency !== true) {
+              const error = satisfiedDependency || {
+                type: 'UnknownError',
+                message: `Something went wrong comparing dependency for ${name}`,
+                actions: [
+                  {
+                    label: 'Try Again',
+                    transition: 'TRY_AGAIN',
+                  },
+                ],
+              }
+              this.send({ type: 'TUTORIAL_CONFIGURE_FAIL', payload: { error } })
+              return
+            }
+          }
+        }
+
         const error: E.ErrorMessage | void = await tutorialConfig({ config: data.config }).catch((error: Error) => ({
           type: 'UnknownError',
           message: `Location: tutorial config.\n\n${error.message}`,
@@ -144,7 +202,7 @@ class Channel implements Channel {
         }
         // 2. check Git is installed.
         // Should wait for workspace before running otherwise requires access to root folder
-        const isGitInstalled = await gitVersion()
+        const isGitInstalled = await version('git')
         if (!isGitInstalled) {
           const error: E.ErrorMessage = {
             type: 'GitNotFound',
@@ -197,7 +255,7 @@ class Channel implements Channel {
 
       if (errorMarkdown) {
         // add a clearer error message for the user
-        error.message = `${errorMarkdown}\n${error.message}`
+        error.message = `${errorMarkdown}\n\n${error.message}`
       }
     }
 
diff --git a/src/services/dependencies/index.ts b/src/services/dependencies/index.ts
new file mode 100644
index 00000000..83393fe9
--- /dev/null
+++ b/src/services/dependencies/index.ts
@@ -0,0 +1,24 @@
+import { satisfies } from 'semver'
+import node from '../node'
+
+const semverRegex = /(?<=^v?|\sv?)(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*)(?:\.(?:0|[1-9]\d*|[\da-z-]*[a-z-][\da-z-]*))*)?(?:\+[\da-z-]+(?:\.[\da-z-]+)*)?(?=$|\s)/gi
+
+export const version = async (name: string): Promise<string | null> => {
+  try {
+    const { stdout, stderr } = await node.exec(`${name} --version`)
+    if (!stderr) {
+      const match = stdout.match(semverRegex)
+      if (match) {
+        return match[0]
+      }
+    }
+    return null
+  } catch (error) {
+    return null
+  }
+}
+
+export const compareVersions = async (currentVersion: string, expectedVersion: string): Promise<never | boolean> => {
+  // see node-semver docs: https://github.com/npm/node-semver
+  return satisfies(currentVersion, expectedVersion)
+}
diff --git a/src/services/git/index.ts b/src/services/git/index.ts
index d66bd4e5..d6e422a2 100644
--- a/src/services/git/index.ts
+++ b/src/services/git/index.ts
@@ -69,19 +69,6 @@ export async function clear(): Promise<Error | void> {
   throw new Error('Error cleaning up current unsaved work')
 }
 
-export async function version(): Promise<string | null> {
-  const { stdout, stderr } = await node.exec('git --version')
-  if (!stderr) {
-    const match = stdout.match(/^git version (\d+\.)?(\d+\.)?(\*|\d+)/)
-    if (match) {
-      // eslint-disable-next-line
-      const [_, major, minor, patch] = match
-      return `${major}${minor}${patch}`
-    }
-  }
-  return null
-}
-
 async function init(): Promise<Error | void> {
   const { stderr } = await node.exec('git init')
   if (stderr) {
diff --git a/typings/tutorial.d.ts b/typings/tutorial.d.ts
index 313c386e..a604a85b 100644
--- a/typings/tutorial.d.ts
+++ b/typings/tutorial.d.ts
@@ -3,6 +3,7 @@ export type Maybe<T> = T | null
 export type TutorialConfig = {
   testRunner: TutorialTestRunner
   repo: TutorialRepo
+  dependencies?: TutorialDependency[]
 }
 
 /** Logical groupings of tasks */
@@ -57,3 +58,9 @@ export interface TutorialRepo {
   uri: string
   branch: string
 }
+
+export interface TutorialDependency {
+  name: string
+  version: string
+  message?: string
+}