diff --git a/CHANGELOG.md b/CHANGELOG.md
index e21e31019..cf3507068 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Create/check data directory on Island init. #1170
 - The formatting of some log messages to make them more readable. #1283
 - Some unit tests to run faster. #1125
+- Moved appimage/ to build_scripts/appimage/. #1140
 
 ### Removed
 - Relevant dead code as reported by Vulture. #1149
diff --git a/appimage/README.md b/appimage/README.md
deleted file mode 100644
index bbf6a61e8..000000000
--- a/appimage/README.md
+++ /dev/null
@@ -1,27 +0,0 @@
-# Monkey Island AppImage
-
-## About
-
-This directory contains the necessary artifacts for building an Infection
-Monkey AppImage
-
-## Building an AppImage
-
-1. Create a clean VM or LXC (not docker!) based on Ubuntu 18.04.
-1. Copy the `deployment_scripts/appimage` directory to `$HOME/` in the VM.
-1. On the VM, `cd $HOME/appimage`
-1. Run `sudo -v`.
-1. Execute `./build_appimage.sh`. This will pull all necessary dependencies
-   and build the AppImage.
-
-NOTE: This script is intended to be run from a clean VM. You can also manually
-remove build artifacts by running `appimage/clean.sh`
-
-## Running the AppImage
-
-The build script will produce an AppImage executable named
-`Infection_Monkey-x86_64.AppImage`. Simply execute this file and you're off to
-the races.
-
-A new directory, `$HOME/.monkey_island` will be created to store runtime
-artifacts.
diff --git a/appimage/build_appimage.sh b/appimage/build_appimage.sh
deleted file mode 100755
index 5ed46dfea..000000000
--- a/appimage/build_appimage.sh
+++ /dev/null
@@ -1,372 +0,0 @@
-#!/bin/bash
-
-WORKSPACE=${WORKSPACE:-$HOME}
-
-APPDIR="$PWD/squashfs-root"
-INSTALL_DIR="$APPDIR/usr/src"
-
-GIT=$WORKSPACE/git
-
-DEFAULT_REPO_MONKEY_HOME=$GIT/monkey
-
-ISLAND_PATH="$INSTALL_DIR/monkey_island"
-MONGO_PATH="$ISLAND_PATH/bin/mongodb"
-ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
-
-MONKEY_ORIGIN_URL="https://github.com/guardicore/monkey.git"
-CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config"
-NODE_SRC=https://deb.nodesource.com/setup_12.x
-APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage
-PYTHON_VERSION="3.7.11"
-PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage"
-ISLAND_DIR_COPY_TIMEOUT=60 #Seconds
-
-exit_if_missing_argument() {
-  if [ -z "$2" ] || [ "${2:0:1}" == "-" ]; then
-    echo "Error: Argument for $1 is missing" >&2
-    exit 1
-  fi
-}
-
-echo_help() {
-  echo "usage: build_appimage.sh [--help] [--agent-binary-dir <PATH>] [--branch <BRANCH>]"
-  echo "                         [--monkey-repo <PATH>] [--version <MONKEY_VERSION>]"
-  echo ""
-  echo "Creates an AppImage package for Infection Monkey."
-  echo ""
-  echo "--agent-binary-dir             A directory containing the agent binaries that"
-  echo "                               you'd like to include with the AppImage. If this"
-  echo "                               parameter is unspecified, the latest release"
-  echo "                               binaries will be downloaded from GitHub."
-  echo ""
-  echo "--as-root                      Throw caution to the wind and allow this script"
-  echo "                               to be run as root."
-  echo ""
-  echo "--branch                       The git branch you'd like the AppImage to be"
-  echo "                               built from. (Default: develop)"
-  echo ""
-  echo "--monkey-repo                  A directory containing the Infection Monkey git"
-  echo "                               repository. If the directory is empty or does"
-  echo "                               not exist, a new repo will be cloned from GitHub."
-  echo "                               If the directory is already a valid GitHub repo,"
-  echo "                               it will be used as-is and the --branch parameter"
-  echo "                               will have no effect."
-  echo "                               (Default: $DEFAULT_REPO_MONKEY_HOME)"
-  echo ""
-  echo "--version                      A version number for the AppImage package."
-  echo "                               (Default: dev)"
-
-  exit 0
-}
-
-is_root() {
-  return "$(id -u)"
-}
-
-has_sudo() {
-  # 0 true, 1 false
-  sudo -nv > /dev/null 2>&1
-  return $?
-}
-
-handle_error() {
-  echo "Fix the errors above and rerun the script"
-  exit 1
-}
-
-log_message() {
-  echo -e "\n\n"
-  echo -e "APPIMAGE BUILDER: $1"
-}
-
-install_nodejs() {
-  log_message "Installing nodejs"
-
-  curl -sL $NODE_SRC | sudo -E bash -
-  sudo apt-get install -y nodejs
-}
-
-install_build_prereqs() {
-  sudo apt-get update
-  sudo apt-get upgrade -y
-
-  # monkey island prereqs
-  sudo apt-get install -y curl libcurl4 openssl git build-essential moreutils
-  install_nodejs
-}
-
-install_appimage_tool() {
-  log_message "Installing appimagetool"
-  APP_TOOL_BIN=$WORKSPACE/bin/appimagetool
-
-  mkdir -p "$WORKSPACE"/bin
-  curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL"
-  chmod u+x "$APP_TOOL_BIN"
-
-  PATH=$PATH:$WORKSPACE/bin
-}
-
-is_valid_git_repo() {
-  pushd "$1" 2>/dev/null || return 1
-  git status >/dev/null 2>&1
-  success="$?"
-  popd || exit 1
-
-  return $success
-}
-
-clone_monkey_repo() {
-  local repo_dir=$1
-  local branch=$2
-
-  if [[ ! -d "$repo_dir" ]]; then
-    mkdir -p "$repo_dir"
-  fi
-
-  log_message "Cloning files from git"
-  git clone -c core.autocrlf=false --single-branch --recurse-submodules -b "$branch" "$MONKEY_ORIGIN_URL" "$repo_dir" 2>&1 || handle_error
-}
-
-setup_appdir() {
-  local agent_binary_dir=$1
-  local monkey_repo=$2
-
-  setup_python_37_appdir
-
-  copy_monkey_island_to_appdir "$monkey_repo"/monkey
-  add_agent_binaries_to_appdir "$agent_binary_dir"
-
-  install_monkey_island_python_dependencies
-  install_mongodb
-
-  generate_ssl_cert
-  build_frontend
-
-  add_monkey_icon "$monkey_repo"/monkey
-  add_desktop_file
-  add_apprun
-}
-
-setup_python_37_appdir() {
-  PYTHON_APPIMAGE="python${PYTHON_VERSION}_x86_64.AppImage"
-  rm -rf "$APPDIR" || true
-
-  log_message "downloading Python3.7 Appimage"
-  curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL"
-
-  chmod u+x "$PYTHON_APPIMAGE"
-
-  ./"$PYTHON_APPIMAGE" --appimage-extract
-  rm "$PYTHON_APPIMAGE"
-  mkdir -p "$INSTALL_DIR"
-}
-
-copy_monkey_island_to_appdir() {
-  cp "$1"/__init__.py "$INSTALL_DIR"
-  cp "$1"/monkey_island.py "$INSTALL_DIR"
-  cp -r "$1"/common "$INSTALL_DIR/"
-  if ! timeout "${ISLAND_DIR_COPY_TIMEOUT}" cp -r "$1"/monkey_island "$INSTALL_DIR/"; then
-    log_message "Copying island files takes too long. Maybe you're copying a dev folder instead of a fresh repository?"
-    exit 1
-  fi
-  cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/
-
-  # TODO: This is a workaround that may be able to be removed after PR #848 is
-  # merged. See monkey_island/cc/environment_singleton.py for more information.
-  cp ./server_config.json.standard "$INSTALL_DIR"/monkey_island/cc/server_config.json
-}
-
-install_monkey_island_python_dependencies() {
-  log_message "Installing island requirements"
-
-  log_message "Installing pipenv"
-  "$APPDIR"/AppRun -m pip install pipenv || handle_error
-
-  requirements_island="$ISLAND_PATH/requirements.txt"
-  generate_requirements_from_pipenv_lock "$requirements_island"
-
-  log_message "Installing island python requirements"
-  "$APPDIR"/AppRun -m pip install -r "${requirements_island}"  --ignore-installed || handle_error
-}
-
-generate_requirements_from_pipenv_lock () {
-  log_message "Generating a requirements.txt file with 'pipenv lock -r'"
-  cd "$ISLAND_PATH" || exit 1
-  "$APPDIR"/AppRun -m pipenv --python "$APPDIR/AppRun" lock -r > "$1" || handle_error
-  cd - || exit 1
-}
-
-add_agent_binaries_to_appdir() {
-  if [ -z "$1" ]; then
-    download_monkey_agent_binaries_to_appdir
-  else
-    copy_agent_binaries_to_appdir "$1"
-  fi
-
-  make_linux_binaries_executable
-}
-
-download_monkey_agent_binaries_to_appdir() {
-  log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}"
-
-  load_monkey_binary_config
-
-  mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error
-  curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}"
-  curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}"
-  curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}"
-  curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}"
-}
-
-copy_agent_binaries_to_appdir() {
-  cp "$1"/* "$ISLAND_BINARIES_PATH/"
-}
-
-make_linux_binaries_executable() {
-  chmod a+x "$ISLAND_BINARIES_PATH"/monkey-linux-*
-}
-
-load_monkey_binary_config() {
-  tmpfile=$(mktemp)
-
-  log_message "Downloading prebuilt binary configuration"
-  curl -L -s -o "$tmpfile" "$CONFIG_URL"
-
-  log_message "Loading configuration"
-  source "$tmpfile"
-}
-
-install_mongodb() {
-  log_message "Installing MongoDB"
-
-  mkdir -p "$MONGO_PATH"
-  "${ISLAND_PATH}"/linux/install_mongo.sh "${MONGO_PATH}" || handle_error
-}
-
-generate_ssl_cert() {
-  log_message "Generating certificate"
-
-  chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh
-  "${ISLAND_PATH}"/linux/create_certificate.sh "${ISLAND_PATH}"/cc
-}
-
-build_frontend() {
-  pushd "$ISLAND_PATH/cc/ui" || handle_error
-
-  log_message "Generating front end"
-  npm ci
-  npm run dist
-
-  popd || handle_error
-
-  remove_node_modules
-}
-
-remove_node_modules() {
-  # Node has served its purpose. We don't need to deliver the node modules with
-  # the AppImage.
-  rm -rf "$ISLAND_PATH"/cc/ui/node_modules
-}
-
-add_monkey_icon() {
-  unlink "$APPDIR"/python.png
-  mkdir -p "$APPDIR"/usr/share/icons
-  cp "$1"/monkey_island/cc/ui/src/images/monkey-icon.svg "$APPDIR"/usr/share/icons/infection-monkey.svg
-  ln -s "$APPDIR"/usr/share/icons/infection-monkey.svg "$APPDIR"/infection-monkey.svg
-}
-
-add_desktop_file() {
-  unlink "$APPDIR/python${PYTHON_VERSION}.desktop"
-  cp ./infection-monkey.desktop "$APPDIR"/usr/share/applications
-  ln -s "$APPDIR"/usr/share/applications/infection-monkey.desktop "$APPDIR"/infection-monkey.desktop
-}
-
-add_apprun() {
-  cp ./AppRun "$APPDIR"
-}
-
-build_appimage() {
-  log_message "Building AppImage"
-  ARCH="x86_64" appimagetool "$APPDIR"
-  apply_version_to_appimage "$1"
-}
-
-apply_version_to_appimage() {
-  log_message "Renaming Infection_Monkey-x86_64.AppImage -> Infection_Monkey-$1-x86_64.AppImage"
-  mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage"
-}
-
-agent_binary_dir=""
-as_root=false
-branch="develop"
-monkey_repo="$DEFAULT_REPO_MONKEY_HOME"
-monkey_version="dev"
-
-
-while (( "$#" )); do
-  case "$1" in
-    --agent-binary-dir)
-      exit_if_missing_argument "$1" "$2"
-
-      agent_binary_dir=$2
-      shift 2
-      ;;
-    --as-root)
-      as_root=true
-      shift
-      ;;
-    --branch)
-      exit_if_missing_argument "$1" "$2"
-
-      branch=$2
-      shift 2
-      ;;
-    -h|--help)
-      echo_help
-      ;;
-    --monkey-repo)
-      exit_if_missing_argument "$1" "$2"
-
-      monkey_repo=$2
-      shift 2
-      ;;
-    --version)
-      exit_if_missing_argument "$1" "$2"
-
-      monkey_version=$2
-      shift 2
-      ;;
-    *)
-      echo "Error: Unsupported parameter $1" >&2
-      exit 1
-      ;;
-  esac
-done
-
-log_message "Building Monkey Island AppImage package."
-
-if ! $as_root && is_root; then
-  log_message "Please don't run this script as root"
-  exit 1
-fi
-
-if ! has_sudo; then
-  log_message "You need root permissions for some of this script operations. \
-Run \`sudo -v\`, enter your password, and then re-run this script."
-  exit 1
-fi
-
-
-install_build_prereqs
-install_appimage_tool
-
-if ! is_valid_git_repo "$monkey_repo"; then
-  clone_monkey_repo "$monkey_repo" "$branch"
-fi
-
-setup_appdir "$agent_binary_dir" "$monkey_repo"
-
-build_appimage "$monkey_version"
-
-log_message "AppImage build script finished."
-exit 0
diff --git a/build_scripts/README.md b/build_scripts/README.md
new file mode 100644
index 000000000..e08b7ea86
--- /dev/null
+++ b/build_scripts/README.md
@@ -0,0 +1,46 @@
+# Infection Monkey Linux Package Builder
+
+## About
+
+This directory contains the necessary artifacts for building an Infection
+Monkey packages for Linux.
+
+## AppImage
+
+### Building an AppImage
+
+1. Create a clean VM or LXC (not docker!) based on Ubuntu 18.04.
+1. Copy the `build_scipts/` directory to `$HOME/` in the VM.
+1. On the VM, `cd $HOME/build_scripts`
+1. Run `sudo -v`.
+1. Execute `./build_appimage.sh`. This will pull all necessary dependencies
+   and build the AppImage.
+
+NOTE: This script is intended to be run from a clean VM. You can also manually
+remove build artifacts by running `appimage/clean.sh`
+
+### Running the AppImage
+
+The build script will produce an AppImage executable named
+`./dist/Infection_Monkey-x86_64.AppImage`. Simply execute this file and you're off to
+the races.
+
+A new directory, `$HOME/.monkey_island` will be created to store runtime
+artifacts.
+
+## Docker
+
+### Building a Docker image
+1. Create a clean Ubuntu 18.04 VM (not WSL).
+1. Copy the `build_scipts/` directory to `$HOME/` in the VM.
+1. On the VM, `cd $HOME/build_scripts`
+1. Run `sudo -v`.
+1. Execute `./build_docker.sh --package docker`. This will pull all necessary dependencies
+   and build the Docker image.
+
+NOTE: This script is intended to be run from a clean VM. You can also manually
+remove build artifacts by running `docker/clean.sh`
+
+### Running the Docker Image
+The build script will produce a `.tgz` file in `./dist/`. See
+`docker/DOCKER_README.md` for instructions on running the docker image.
diff --git a/appimage/.gitignore b/build_scripts/appimage/.gitignore
similarity index 100%
rename from appimage/.gitignore
rename to build_scripts/appimage/.gitignore
diff --git a/appimage/AppRun b/build_scripts/appimage/AppRun
similarity index 100%
rename from appimage/AppRun
rename to build_scripts/appimage/AppRun
diff --git a/build_scripts/appimage/appimage.sh b/build_scripts/appimage/appimage.sh
new file mode 100755
index 000000000..c1cf3b922
--- /dev/null
+++ b/build_scripts/appimage/appimage.sh
@@ -0,0 +1,145 @@
+#!/bin/bash
+
+APP_TOOL_URL=https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage
+PYTHON_VERSION="3.7.11"
+PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage"
+APPIMAGE_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
+
+source "$APPIMAGE_DIR/../common.sh"
+
+install_package_specific_build_prereqs() {
+  log_message "Installing appimagetool"
+  WORKSPACE_BIN_DIR="$1/bin"
+  APP_TOOL_BIN="$WORKSPACE_BIN_DIR/appimagetool"
+
+  mkdir -p "$WORKSPACE_BIN_DIR"
+  curl -L -o "$APP_TOOL_BIN" "$APP_TOOL_URL"
+  chmod u+x "$APP_TOOL_BIN"
+
+  PATH=$PATH:$WORKSPACE_BIN_DIR
+}
+
+setup_build_dir() {
+  local agent_binary_dir=$1
+  local monkey_repo=$2
+  local appdir=$APPIMAGE_DIR/squashfs-root
+  local build_dir="$appdir/usr/src"
+
+  pushd $APPIMAGE_DIR
+
+  setup_python_37_appdir $build_dir
+
+  mkdir -p "$build_dir"
+
+  copy_monkey_island_to_build_dir "$monkey_repo/monkey" $build_dir
+  copy_server_config_to_build_dir $build_dir
+  add_agent_binaries_to_build_dir "$agent_binary_dir" "$build_dir"
+
+  install_monkey_island_python_dependencies "$appdir" "$build_dir"
+  install_mongodb "$build_dir"
+
+  generate_ssl_cert "$build_dir"
+  build_frontend "$build_dir"
+
+  add_monkey_icon "$appdir" "$monkey_repo"
+  add_desktop_file "$appdir"
+  add_apprun "$appdir"
+
+  popd
+}
+
+setup_python_37_appdir() {
+  PYTHON_APPIMAGE="python${PYTHON_VERSION}_x86_64.AppImage"
+
+  log_message "downloading Python3.7 Appimage"
+  curl -L -o "$PYTHON_APPIMAGE" "$PYTHON_APPIMAGE_URL"
+
+  chmod u+x "$PYTHON_APPIMAGE"
+
+  "./$PYTHON_APPIMAGE" --appimage-extract
+  rm "$PYTHON_APPIMAGE"
+}
+
+copy_server_config_to_build_dir() {
+    cp "$APPIMAGE_DIR"/server_config.json.standard "$1"/monkey_island/cc/server_config.json
+}
+
+install_monkey_island_python_dependencies() {
+  local appdir=$1
+  local build_dir=$2
+  log_message "Installing island requirements"
+
+  log_message "Installing pipenv"
+  "$appdir"/AppRun -m pip install pipenv || handle_error
+
+  requirements_island="$build_dir/monkey_island/requirements.txt"
+  generate_requirements_from_pipenv_lock "$appdir" "$build_dir" "$requirements_island"
+
+  log_message "Installing island python requirements"
+  "$appdir"/AppRun -m pip install -r "${requirements_island}"  --ignore-installed || handle_error
+}
+
+generate_requirements_from_pipenv_lock () {
+  local appdir=$1
+  local build_dir=$2
+  local requirements_island=$3
+
+  log_message "Generating a requirements.txt file with 'pipenv lock -r'"
+  pushd "$build_dir/monkey_island"
+  "$appdir"/AppRun -m pipenv --python "$appdir/AppRun" lock -r > "$requirements_island" || handle_error
+  popd
+}
+
+install_mongodb() {
+  local build_dir=$1
+  local mongo_path="$build_dir/monkey_island/bin/mongodb"
+  log_message "Installing MongoDB"
+
+  mkdir -p "$mongo_path"
+  "$build_dir/monkey_island/linux/install_mongo.sh" "${mongo_path}" || handle_error
+}
+
+add_monkey_icon() {
+  local appdir=$1
+  local monkey_repo=$2
+
+  unlink "$appdir"/python.png
+  mkdir -p "$appdir"/usr/share/icons
+  cp "$monkey_repo"/monkey/monkey_island/cc/ui/src/images/monkey-icon.svg "$appdir"/usr/share/icons/infection-monkey.svg
+  ln -s "$appdir"/usr/share/icons/infection-monkey.svg "$appdir"/infection-monkey.svg
+}
+
+add_desktop_file() {
+  local appdir=$1
+
+  unlink "$appdir"/python*.desktop
+  cp ./infection-monkey.desktop "$appdir"/usr/share/applications
+  ln -s "$appdir"/usr/share/applications/infection-monkey.desktop "$appdir"/infection-monkey.desktop
+}
+
+add_apprun() {
+  cp ./AppRun "$1"
+}
+
+build_package() {
+  local version=$1
+  local dist_dir=$2
+  log_message "Building AppImage"
+  pushd "$APPIMAGE_DIR"
+
+  ARCH="x86_64" appimagetool "$APPIMAGE_DIR/squashfs-root"
+  apply_version_to_appimage "$version"
+
+  move_package_to_dist_dir $dist_dir
+
+  popd
+}
+
+apply_version_to_appimage() {
+  log_message "Renaming Infection_Monkey-x86_64.AppImage -> Infection_Monkey-$1-x86_64.AppImage"
+  mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage"
+}
+
+move_package_to_dist_dir() {
+    mv Infection_Monkey*.AppImage "$1/"
+}
diff --git a/appimage/clean.sh b/build_scripts/appimage/clean.sh
similarity index 58%
rename from appimage/clean.sh
rename to build_scripts/appimage/clean.sh
index f48837d37..ee9303a10 100755
--- a/appimage/clean.sh
+++ b/build_scripts/appimage/clean.sh
@@ -3,7 +3,9 @@
 # This is a utility script to clean up after a failed or successful AppImage build
 # in order to speed up development and debugging.
 
-rm -rf "$HOME/.monkey_island"
-rm -rf "$HOME/appimage/squashfs-root"
+APPIMAGE_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
+
 rm -rf "$HOME/git/monkey"
-rm $HOME/appimage/Infection_Monkey*x86_64.AppImage
+rm -rf "$HOME/.monkey_island"
+rm -rf "$APPIMAGE_DIR/squashfs-root"
+rm "$APPIMAGE_DIR"/Infection_Monkey*x86_64.AppImage
diff --git a/appimage/infection-monkey.desktop b/build_scripts/appimage/infection-monkey.desktop
similarity index 100%
rename from appimage/infection-monkey.desktop
rename to build_scripts/appimage/infection-monkey.desktop
diff --git a/appimage/server_config.json.standard b/build_scripts/appimage/server_config.json.standard
similarity index 100%
rename from appimage/server_config.json.standard
rename to build_scripts/appimage/server_config.json.standard
diff --git a/build_scripts/build_appimage.sh b/build_scripts/build_appimage.sh
new file mode 100755
index 000000000..5744336e9
--- /dev/null
+++ b/build_scripts/build_appimage.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+./build_package.sh --package appimage $@
diff --git a/build_scripts/build_docker.sh b/build_scripts/build_docker.sh
new file mode 100755
index 000000000..585f51278
--- /dev/null
+++ b/build_scripts/build_docker.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+./build_package.sh --package docker $@
diff --git a/docker/build_docker.sh b/build_scripts/build_package.sh
similarity index 50%
rename from docker/build_docker.sh
rename to build_scripts/build_package.sh
index 797e8d06d..957c43e1a 100755
--- a/docker/build_docker.sh
+++ b/build_scripts/build_package.sh
@@ -1,20 +1,14 @@
 WORKSPACE=${WORKSPACE:-$HOME}
-
-BUILD_DIR="$PWD/monkey"
-
-GIT=$WORKSPACE/git
-
-DEFAULT_REPO_MONKEY_HOME=$GIT/monkey
-
-ISLAND_PATH="$BUILD_DIR/monkey_island"
-ISLAND_BINARIES_PATH="$ISLAND_PATH/cc/binaries"
-
+DEFAULT_REPO_MONKEY_HOME=$WORKSPACE/git/monkey
 MONKEY_ORIGIN_URL="https://github.com/guardicore/monkey.git"
-CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config"
 NODE_SRC=https://deb.nodesource.com/setup_12.x
-ISLAND_DIR_COPY_TIMEOUT=60 #Seconds
+BUILD_SCRIPTS_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
+DIST_DIR="$BUILD_SCRIPTS_DIR/dist"
 
-OUTPUT_NAME_TGZ="$PWD/infection_monkey_docker_$(date +%Y%m%d_%H%M%S).tgz"
+log_message() {
+  echo -e "\n\n"
+  echo -e "MONKEY ISLAND BUILDER: $1"
+}
 
 exit_if_missing_argument() {
   if [ -z "$2" ] || [ "${2:0:1}" == "-" ]; then
@@ -24,20 +18,20 @@ exit_if_missing_argument() {
 }
 
 echo_help() {
-  echo "usage: build_appimage.sh [--help] [--agent-binary-dir <PATH>] [--branch <BRANCH>]"
+  echo "usage: build_package.sh [--help] [--agent-binary-dir <PATH>] [--branch <BRANCH>]"
   echo "                         [--monkey-repo <PATH>] [--version <MONKEY_VERSION>]"
   echo ""
-  echo "Creates an AppImage package for Infection Monkey."
+  echo "Creates a package for Infection Monkey."
   echo ""
   echo "--agent-binary-dir             A directory containing the agent binaries that"
-  echo "                               you'd like to include with the AppImage. If this"
+  echo "                               you'd like to include with the package. If this"
   echo "                               parameter is unspecified, the latest release"
   echo "                               binaries will be downloaded from GitHub."
   echo ""
   echo "--as-root                      Throw caution to the wind and allow this script"
   echo "                               to be run as root."
   echo ""
-  echo "--branch                       The git branch you'd like the AppImage to be"
+  echo "--branch                       The git branch you'd like the package to be"
   echo "                               built from. (Default: develop)"
   echo ""
   echo "--monkey-repo                  A directory containing the Infection Monkey git"
@@ -48,8 +42,10 @@ echo_help() {
   echo "                               will have no effect."
   echo "                               (Default: $DEFAULT_REPO_MONKEY_HOME)"
   echo ""
-  echo "--version                      A version number for the AppImage package."
+  echo "--version                      A version number for the package."
   echo "                               (Default: dev)"
+  echo ""
+  echo "--package                      Which package to build (\"appimage\" or \"docker.\""
 
   exit 0
 }
@@ -64,11 +60,6 @@ has_sudo() {
   return $?
 }
 
-log_message() {
-  echo -e "\n\n"
-  echo -e "DOCKER IMAGE BUILDER: $1"
-}
-
 handle_error() {
   echo "Fix the errors above and rerun the script"
   exit 1
@@ -81,7 +72,7 @@ install_nodejs() {
   sudo apt-get install -y nodejs
 }
 
-install_build_prereqs() {
+install_common_build_prereqs() {
   sudo apt-get update
   sudo apt-get upgrade -y
 
@@ -90,8 +81,13 @@ install_build_prereqs() {
   install_nodejs
 }
 
-install_docker() {
-    sudo apt-get install -y docker.io
+is_valid_git_repo() {
+  pushd "$1" 2>/dev/null || return 1
+  git status >/dev/null 2>&1
+  success="$?"
+  popd || exit 1
+
+  return $success
 }
 
 clone_monkey_repo() {
@@ -106,140 +102,13 @@ clone_monkey_repo() {
   git clone -c core.autocrlf=false --single-branch --recurse-submodules -b "$branch" "$MONKEY_ORIGIN_URL" "$repo_dir" 2>&1 || handle_error
 }
 
-is_valid_git_repo() {
-  pushd "$1" 2>/dev/null || return 1
-  git status >/dev/null 2>&1
-  success="$?"
-  popd || exit 1
+install_build_prereqs() {
+  sudo apt-get update
+  sudo apt-get upgrade -y
 
-  return $success
-}
-
-setup_build_dir() {
-  local agent_binary_dir=$1
-  local monkey_repo=$2
-
-  mkdir "$BUILD_DIR"
-
-  copy_entrypoint_to_build_dir
-
-  copy_monkey_island_to_build_dir "$monkey_repo/monkey"
-  add_agent_binaries_to_build_dir "$agent_binary_dir"
-
-  generate_ssl_cert
-
-  build_frontend
-}
-
-copy_entrypoint_to_build_dir() {
-    cp ./entrypoint.sh "$BUILD_DIR"
-    chmod 755 "$BUILD_DIR/entrypoint.sh"
-}
-
-copy_monkey_island_to_build_dir() {
-  local src=$1
-
-  cp "$src"/__init__.py "$BUILD_DIR"
-  cp "$src"/monkey_island.py "$BUILD_DIR"
-  cp -v -r "$src"/common "$BUILD_DIR/"
-
-  rsync \
-      -avr \
-      --exclude=monkey_island/cc/ui/node_modules \
-      --exclude=monkey_island/cc/ui/.npm \
-      "$src"/monkey_island "$BUILD_DIR/"
-
-  cp ./server_config.json "$BUILD_DIR"/monkey_island/cc/
-}
-
-add_agent_binaries_to_build_dir() {
-  local agent_binary_dir=$1
-
-  if [ -z "$agent_binary_dir" ]; then
-    download_monkey_agent_binaries
-  else
-    copy_agent_binaries_to_appdir "$agent_binary_dir"
-  fi
-
-  make_linux_binaries_executable
-}
-
-download_monkey_agent_binaries() {
-  log_message "Downloading monkey agent binaries to ${ISLAND_BINARIES_PATH}"
-
-  load_monkey_binary_config
-
-  mkdir -p "${ISLAND_BINARIES_PATH}" || handle_error
-  curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}"
-  curl -L -o "${ISLAND_BINARIES_PATH}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}"
-  curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}"
-  curl -L -o "${ISLAND_BINARIES_PATH}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}"
-}
-
-load_monkey_binary_config() {
-  tmpfile=$(mktemp)
-
-  log_message "Downloading prebuilt binary configuration"
-  curl -L -s -o "$tmpfile" "$CONFIG_URL"
-
-  log_message "Loading configuration"
-  source "$tmpfile"
-}
-
-copy_agent_binaries_to_appdir() {
-  cp "$1"/* "$ISLAND_BINARIES_PATH/"
-}
-
-make_linux_binaries_executable() {
-  chmod a+x "$ISLAND_BINARIES_PATH"/monkey-linux-*
-}
-
-generate_ssl_cert() {
-  log_message "Generating certificate"
-
-  chmod u+x "${ISLAND_PATH}"/linux/create_certificate.sh
-  "${ISLAND_PATH}"/linux/create_certificate.sh "${ISLAND_PATH}"/cc
-}
-
-build_frontend() {
-  pushd "$ISLAND_PATH/cc/ui" || handle_error
-
-  log_message "Generating front end"
-  npm ci
-  npm run dist
-
-  popd || handle_error
-
-  remove_node_modules
-}
-
-remove_node_modules() {
-  # Node has served its purpose. We don't need to deliver the node modules with
-  # the AppImage.
-  rm -rf "$ISLAND_PATH"/cc/ui/node_modules
-  rm -rf "$ISLAND_PATH"/cc/ui/.npm
-}
-
-build_docker_image() {
-  local version=$1
-
-  docker_image_name=guardicore/monkey-island:$version
-  tar_name=./dk.monkeyisland.$version.tar
-
-  build_docker_image_tar "$docker_image_name" "$tar_name"
-  build_docker_image_tgz "$tar_name" "$version"
-}
-
-build_docker_image_tar() {
-  sudo docker build . -t "$1"
-  sudo docker save "$1" > "$2"
-}
-
-build_docker_image_tgz() {
-  mkdir tgz
-  cp "$1" ./tgz
-  cp ./DOCKER_README.md ./tgz/README.md
-  tar -C ./tgz -cvf "$OUTPUT_NAME_TGZ" --gzip .
+  # monkey island prereqs
+  sudo apt-get install -y curl libcurl4 openssl git build-essential moreutils
+  install_nodejs
 }
 
 agent_binary_dir=""
@@ -247,6 +116,7 @@ as_root=false
 branch="develop"
 monkey_repo="$DEFAULT_REPO_MONKEY_HOME"
 monkey_version="dev"
+package=""
 
 
 while (( "$#" )); do
@@ -282,6 +152,12 @@ while (( "$#" )); do
       monkey_version=$2
       shift 2
       ;;
+    --package)
+      exit_if_missing_argument "$1" "$2"
+
+      package=$2
+      shift 2
+      ;;
     *)
       echo "Error: Unsupported parameter $1" >&2
       exit 1
@@ -289,7 +165,10 @@ while (( "$#" )); do
   esac
 done
 
-log_message "Building Monkey Island Docker image."
+if ! [[ $package =~ ^(appimage|docker)$ ]]; then
+    log_message "Invalid package: $package."
+    exit 1
+fi
 
 if ! $as_root && is_root; then
   log_message "Please don't run this script as root"
@@ -302,15 +181,24 @@ Run \`sudo -v\`, enter your password, and then re-run this script."
   exit 1
 fi
 
-install_build_prereqs
-install_docker
+log_message "Building Monkey Island: $package"
+
+source "./$package/$package.sh"
 
 if ! is_valid_git_repo "$monkey_repo"; then
   clone_monkey_repo "$monkey_repo" "$branch"
 fi
 
-setup_build_dir "$agent_binary_dir" "$monkey_repo"
-build_docker_image "$monkey_version"
+if [ ! -d "$DIST_DIR" ]; then
+    mkdir "$DIST_DIR"
+fi
 
-log_message "Docker build script finished."
+install_build_prereqs
+install_package_specific_build_prereqs "$WORKSPACE"
+
+
+setup_build_dir "$agent_binary_dir" "$monkey_repo"
+build_package "$monkey_version" "$DIST_DIR"
+
+log_message "Finished building package: $package"
 exit 0
diff --git a/build_scripts/common.sh b/build_scripts/common.sh
new file mode 100644
index 000000000..85f794128
--- /dev/null
+++ b/build_scripts/common.sh
@@ -0,0 +1,88 @@
+CONFIG_URL="https://raw.githubusercontent.com/guardicore/monkey/develop/deployment_scripts/config"
+
+copy_monkey_island_to_build_dir() {
+  local src=$1
+  local build_dir=$2
+
+  cp "$src"/__init__.py "$build_dir"
+  cp "$src"/monkey_island.py "$build_dir"
+  cp -r "$src"/common "$build_dir/"
+
+  rsync \
+      -ar \
+      --exclude=monkey_island/cc/ui/node_modules \
+      --exclude=monkey_island/cc/ui/.npm \
+      "$src"/monkey_island "$build_dir/"
+}
+
+add_agent_binaries_to_build_dir() {
+  local agent_binary_dir=$1
+  local island_binaries_path="$2/monkey_island/cc/binaries/"
+
+  if [ -z "$agent_binary_dir" ]; then
+    download_monkey_agent_binaries $island_binaries_path
+  else
+    copy_agent_binaries_to_build_dir "$agent_binary_dir" "$island_binaries_path"
+  fi
+
+  make_linux_binaries_executable "$island_binaries_path"
+}
+
+download_monkey_agent_binaries() {
+  local island_binaries_path=$1
+  log_message "Downloading monkey agent binaries to ${island_binaries_path}"
+
+  load_monkey_binary_config
+
+  mkdir -p "${island_binaries_path}" || handle_error
+  curl -L -o "${island_binaries_path}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}"
+  curl -L -o "${island_binaries_path}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}"
+  curl -L -o "${island_binaries_path}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}"
+  curl -L -o "${island_binaries_path}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}"
+}
+
+load_monkey_binary_config() {
+  tmpfile=$(mktemp)
+
+  log_message "Downloading prebuilt binary configuration"
+  curl -L -s -o "$tmpfile" "$CONFIG_URL"
+
+  log_message "Loading configuration"
+  source "$tmpfile"
+}
+
+copy_agent_binaries_to_build_dir() {
+  cp "$1"/* "$2/"
+}
+
+make_linux_binaries_executable() {
+  chmod a+x "$1"/monkey-linux-*
+}
+
+generate_ssl_cert() {
+  local island_path="$1/monkey_island"
+  log_message "Generating certificate"
+
+   chmod u+x "$island_path"/linux/create_certificate.sh
+  "$island_path"/linux/create_certificate.sh "$island_path"/cc
+}
+
+build_frontend() {
+  local ui_dir="$1/monkey_island/cc/ui"
+  pushd "$ui_dir" || handle_error
+
+  log_message "Generating front end"
+  npm ci
+  npm run dist
+
+  popd || handle_error
+
+  remove_node_modules "$ui_dir"
+}
+
+remove_node_modules() {
+  # Node has served its purpose. We don't need to deliver the node modules with
+  # the package.
+  rm -rf "$1/node_modules"
+  rm -rf "$1/.npm"
+}
diff --git a/build_scripts/docker/.gitignore b/build_scripts/docker/.gitignore
new file mode 100644
index 000000000..2edc32417
--- /dev/null
+++ b/build_scripts/docker/.gitignore
@@ -0,0 +1,3 @@
+dk.monkeyisland*.tar
+infection_monkey_docker_*.tgz
+tgz/
diff --git a/docker/DOCKER_README.md b/build_scripts/docker/DOCKER_README.md
similarity index 100%
rename from docker/DOCKER_README.md
rename to build_scripts/docker/DOCKER_README.md
diff --git a/docker/Dockerfile b/build_scripts/docker/Dockerfile
similarity index 100%
rename from docker/Dockerfile
rename to build_scripts/docker/Dockerfile
diff --git a/build_scripts/docker/clean.sh b/build_scripts/docker/clean.sh
new file mode 100755
index 000000000..82c769c02
--- /dev/null
+++ b/build_scripts/docker/clean.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# This is a utility script to clean up after a failed or successful Docker
+# image build in order to speed up development and debugging
+
+DOCKER_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
+
+
+rm -rf "$HOME/git/monkey"
+rm -rf "$DOCKER_DIR/monkey"
+rm -rf "$DOCKER_DIR/tgz"
+rm "$DOCKER_DIR"/dk.monkeyisland.*.tar
+rm "$DOCKER_DIR"/infection_monkey_docker*.tgz
diff --git a/build_scripts/docker/docker.sh b/build_scripts/docker/docker.sh
new file mode 100755
index 000000000..bf7e78cee
--- /dev/null
+++ b/build_scripts/docker/docker.sh
@@ -0,0 +1,67 @@
+DOCKER_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
+OUTPUT_NAME_TGZ="$DOCKER_DIR/infection_monkey_docker_$(date +%Y%m%d_%H%M%S).tgz"
+
+source "$DOCKER_DIR/../common.sh"
+
+install_package_specific_build_prereqs() {
+    sudo apt-get install -y docker.io
+}
+
+setup_build_dir() {
+  local agent_binary_dir=$1
+  local monkey_repo=$2
+  local build_dir=$DOCKER_DIR/monkey
+
+  mkdir "$build_dir"
+
+  copy_entrypoint_to_build_dir "$build_dir"
+
+  copy_monkey_island_to_build_dir "$monkey_repo/monkey" "$build_dir"
+  copy_server_config_to_build_dir "$build_dir"
+  add_agent_binaries_to_build_dir "$agent_binary_dir" "$build_dir"
+
+  generate_ssl_cert "$build_dir"
+
+  build_frontend "$build_dir"
+}
+
+copy_entrypoint_to_build_dir() {
+  cp "$DOCKER_DIR"/entrypoint.sh "$1"
+  chmod 755 "$1/entrypoint.sh"
+}
+
+copy_server_config_to_build_dir() {
+  cp "$DOCKER_DIR"/server_config.json "$1"/monkey_island/cc
+}
+
+build_package() {
+  local version=$1
+  local dist_dir=$2
+  pushd ./docker
+
+  docker_image_name="guardicore/monkey-island:$version"
+  tar_name="$DOCKER_DIR/dk.monkeyisland.$version.tar"
+
+  build_docker_image_tar "$docker_image_name" "$tar_name"
+  build_docker_image_tgz "$tar_name" "$version"
+
+  move_package_to_dist_dir $dist_dir
+
+  popd
+}
+
+build_docker_image_tar() {
+  sudo docker build . -t "$1"
+  sudo docker save "$1" > "$2"
+}
+
+build_docker_image_tgz() {
+  mkdir tgz
+  mv "$1" ./tgz
+  cp ./DOCKER_README.md ./tgz/README.md
+  tar -C ./tgz -cvf "$OUTPUT_NAME_TGZ" --gzip .
+}
+
+move_package_to_dist_dir() {
+    mv $OUTPUT_NAME_TGZ "$1/"
+}
diff --git a/docker/entrypoint.sh b/build_scripts/docker/entrypoint.sh
similarity index 100%
rename from docker/entrypoint.sh
rename to build_scripts/docker/entrypoint.sh
diff --git a/docker/server_config.json b/build_scripts/docker/server_config.json
similarity index 100%
rename from docker/server_config.json
rename to build_scripts/docker/server_config.json
diff --git a/docker/README.md b/docker/README.md
deleted file mode 100644
index d01890b23..000000000
--- a/docker/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Monkey Island Docker Image
-
-## About
-
-This directory contains the necessary artifacts for building an Infection
-Monkey Docker image.
-
-## Building a Docker image
-1. Create a clean Ubuntu 18.04 VM (not WSL).
-1. Copy the `docker/` directory to `$HOME/` in the VM.
-1. On the VM, `cd $HOME/docker`
-1. Run `sudo -v`.
-1. Execute `./build_docker.sh`. This will pull all necessary dependencies
-   and build the Docker image.
-
-NOTE: This script is intended to be run from a clean VM. You can also manually
-remove build rtifacts by running `docker/clean.sh`
-
-## Running the Docker Image
-See `docker/DOCKER_README.md` for instructions on running the docker image.
diff --git a/docker/clean.sh b/docker/clean.sh
deleted file mode 100755
index f5cfacc28..000000000
--- a/docker/clean.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-
-# This is a utility script to clean up after a failed or successful Docker
-# image build in order to speed up development and debugging
-
-BUILD_DIR=$HOME/docker
-
-rm -rf $HOME/git/monkey
-rm -rf $BUILD_DIR/monkey
-rm -rf $BUILD_DIR/tgz
-rm $BUILD_DIR/dk.monkeyisland.*.tar
-rm $BUILD_DIR/infection_monkey_docker*.tgz