diff --git a/composer.json b/composer.json
index 049ce3610e..c8dd391f82 100644
--- a/composer.json
+++ b/composer.json
@@ -54,6 +54,10 @@
         "post-package-update": "Drupal\\Core\\Composer\\Composer::vendorTestCodeCleanup",
         "drupal-phpunit-upgrade-check": "Drupal\\Core\\Composer\\Composer::upgradePHPUnit",
         "drupal-phpunit-upgrade": "@composer update phpunit/phpunit --with-dependencies --no-progress",
+        "dev-site": [
+            "@php core/scripts/dev-site.php install",
+            "@php core/scripts/dev-site.php start"
+        ],
         "phpcs": "phpcs --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --",
         "phpcbf": "phpcbf --standard=core/phpcs.xml.dist --runtime-set installed_paths $($COMPOSER_BINARY config vendor-dir)/drupal/coder/coder_sniffer --"
     },
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index a869d90b53..a912c5a11b 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -111,7 +111,7 @@ function install_drupal($class_loader, $settings = []) {
   try {
     // Begin the page request. This adds information about the current state of
     // the Drupal installation to the passed-in array.
-    install_begin_request($class_loader, $install_state);
+    install_begin_request($class_loader, $install_state, $settings);
     // Based on the installation state, run the remaining tasks for this page
     // request, and collect any output.
     $output = install_run_tasks($install_state);
@@ -282,8 +282,12 @@ function install_state_defaults() {
  * @param $install_state
  *   An array of information about the current installation state. This is
  *   modified with information gleaned from the beginning of the page request.
+  * @param $settings
+ *   An optional array of installation settings.
+ *
+ * @see install_drupal()
  */
-function install_begin_request($class_loader, &$install_state) {
+function install_begin_request($class_loader, &$install_state, $settings = []) {
   $request = Request::createFromGlobals();
 
   // Add any installation parameters passed in via the URL.
@@ -327,7 +331,12 @@ function install_begin_request($class_loader, &$install_state) {
     date_default_timezone_set('Australia/Sydney');
   }
 
-  $site_path = DrupalKernel::findSitePath($request, FALSE);
+  if (!empty($settings['site_path'])) {
+    $site_path = $settings['site_path'];
+  }
+  else {
+    $site_path = DrupalKernel::findSitePath($request, FALSE);
+  }
   Settings::initialize(dirname(dirname(__DIR__)), $site_path, $class_loader);
 
   // Ensure that procedural dependencies are loaded as early as possible,
diff --git a/core/lib/Drupal/Core/Command/DevInstallCommand.php b/core/lib/Drupal/Core/Command/DevInstallCommand.php
new file mode 100644
index 0000000000..5b09aeb789
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/DevInstallCommand.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace Drupal\Core\Command;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\QuestionHelper;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\ConsoleOutputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Installs a Drupal site for local testing/development.
+ */
+class DevInstallCommand extends Command {
+
+  /**
+   * The class loader.
+   *
+   * @var \Composer\Autoload\ClassLoader
+   */
+  protected $classLoader;
+
+  /**
+   * Constructs a new DevInstallCommand command.
+   *
+   * @param object $class_loader
+   *   The class loader.
+   */
+  public function __construct($class_loader) {
+    parent::__construct('install');
+    $this->classLoader = $class_loader;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this->setName('install')
+      ->setDescription('Installs a Drupal dev site. This is not meant for production or any custom development. It is a quick and easy way to get Drupal running.')
+      ->addOption('install_profile', NULL, InputOption::VALUE_OPTIONAL, 'Install profile to install the site in. Defaults to standard', 'standard')
+      ->addOption('langcode', NULL, InputOption::VALUE_OPTIONAL, 'The language to install the site in. Defaults to en', 'en')
+      ->addOption('force', 'y', InputOption::VALUE_OPTIONAL, 'Force overriding the existing installation', FALSE)
+      ->addOption('site_path', NULL, InputOption::VALUE_OPTIONAL, 'Forces to use a specific site directory.')
+      ->addUsage('--install_profile demo_umami --langcode fr');
+
+    parent::configure();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    $this->changeRoot();
+
+    if (!$input->getOption('force') && $this->isDrupalInstalled()) {
+      $question_helper = new QuestionHelper();
+      if (!$question_helper->ask($input, $output, new ConfirmationQuestion('There is already an existing installation. Confirm whether you want to override it. (y/N) ', FALSE))) {
+        return;
+      }
+    }
+    // Check whether there is already an installation.
+    $this->install($this->classLoader, $output, $input->getOption('install_profile'), $input->getOption('langcode'), $input->getOption('site_path'));
+  }
+
+  /**
+   * Returns whether there is already an existing Drupal installation.
+   *
+   * @return bool
+   */
+  protected function isDrupalInstalled() {
+    $request = Request::createFromGlobals();
+    DrupalKernel::createFromRequest($request, $this->classLoader, 'prod');
+
+    return !empty(Database::getConnectionInfo());
+  }
+
+  /**
+   * Changes the directory to the Drupal root.
+   *
+   * @return string
+   *   Returns the path to the Drupal root.
+   */
+  protected function changeRoot() {
+    $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
+    chdir($root);
+    return $root;
+  }
+
+  /**
+   * Installs Drupal with specified installation profile.
+   *
+   * @param object $class_loader
+   *   The class loader.
+   * @param \Symfony\Component\Console\Output\ConsoleOutputInterface $output
+   *   The console output.
+   * @param string $profile
+   *   (optional) The installation profile to use.
+   * @param string $langcode
+   *   (optional) The language to install the site in.
+   * @param string $site_path
+   *   (optional) The path to install the site to, like 'sites/default'.
+   */
+  protected function install($class_loader, ConsoleOutputInterface $output, $profile = 'standard', $langcode = 'en', $site_path = NULL) {
+    // If we have a custom site path, we need to create the sites directory
+    // first.
+    if ($site_path && !file_exists($site_path)) {
+      mkdir($site_path, 0775);
+    }
+
+    $parameters = [
+      'interactive' => FALSE,
+      'site_path' => $site_path,
+      'parameters' => [
+        'profile' => $profile,
+        'langcode' => $langcode,
+      ],
+      'forms' => [
+        'install_settings_form' => [
+          'driver' => 'sqlite',
+          'sqlite' => [
+            'database' => 'sites/default/files/.sqlite',
+          ],
+        ],
+        'install_configure_form' => [
+          'site_name' => 'Drupal',
+          'site_mail' => 'simpletest@example.com',
+          'account' => [
+            'name' => 'admin',
+            'mail' => 'admin@localhost',
+            'pass' => [
+              'pass1' => 'test',
+              'pass2' => 'test',
+            ],
+          ],
+          // form_type_checkboxes_value() requires NULL instead of FALSE values
+          // for programmatic form submissions to disable a checkbox.
+          'enable_update_status_module' => NULL,
+          'enable_update_status_emails' => NULL,
+        ],
+      ],
+    ];
+
+    require_once 'core/includes/install.core.inc';
+
+    if (!extension_loaded('pdo_sqlite')) {
+      $output->getErrorOutput()->writeln('You need to have sqlite installed.');
+      return;
+    }
+    if (file_exists('sites/default/settings.php')) {
+      $result = unlink('sites/default/settings.php');
+      if ($result === FALSE) {
+        $output->getErrorOutput()->writeln('Removing settings.php failed, please do it manually ...');
+        return;
+      }
+    }
+    if (file_exists('sites/default/files/.sqlite')) {
+      $result = unlink('sites/default/files/.sqlite');
+      if ($result === FALSE) {
+        $output->getErrorOutput()->writeln('Removing .sqlite failed, please do it manually ...');
+        return;
+      }
+    }
+
+    $output->writeln('Drupal installation started.');
+    install_drupal($class_loader, $parameters);
+    $output->writeln('Drupal successfully installed.');
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Command/DevStartCommand.php b/core/lib/Drupal/Core/Command/DevStartCommand.php
new file mode 100644
index 0000000000..d042f4455e
--- /dev/null
+++ b/core/lib/Drupal/Core/Command/DevStartCommand.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Drupal\Core\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Process;
+
+/**
+ * Starts up a Drupal site for local testing/development.
+ */
+class DevStartCommand extends Command {
+
+  /**
+   * The class loader.
+   *
+   * @var object
+   */
+  protected $classLoader;
+
+  /**
+   * Constructs a new DevStartCommand command.
+   *
+   * @param object $class_loader
+   *   The class loader.
+   */
+  public function __construct($class_loader) {
+    parent::__construct('start');
+    $this->classLoader = $class_loader;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this->setDescription('Starts up a webserver for the dev site.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    $root = $this->boot();
+    $this->start($root, $output);
+  }
+
+  /**
+   * Boots up a Drupal environment.
+   *
+   * @return string
+   *   Returns the path to the Drupal root.
+   */
+  protected function boot() {
+    $root = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
+    chdir($root);
+    return $root;
+  }
+
+  /**
+   * Finds an available port.
+   *
+   * @return int
+   */
+  protected function getAvailablePort() {
+    $address = '0.0.0.0';
+    $port = 0;
+    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+    socket_bind($socket, $address, $port);
+    socket_listen($socket, 5);
+    socket_getsockname($socket, $address, $port);
+    return $port;
+  }
+
+  /**
+   * Starts up a webserver with a running Drupal.
+   *
+   * @param string $root
+   *   The Drupal root.
+   * @param \Symfony\Component\Console\Output\OutputInterface $output
+   *   The console output.
+   */
+  protected function start($root, OutputInterface $output) {
+    $finder = new PhpExecutableFinder();
+    $port = $this->getAvailablePort();
+    if (($binary = $finder->find()) && $binary === FALSE) {
+      throw new \RuntimeException('Unable to find the PHP binary.');
+    }
+    $process = new Process([
+      $binary,
+      '-S',
+      'localhost:' . $port,
+      '.ht.router.php',
+    ], $root, NULL, NULL, NULL);
+    $output->writeln('Starting webserver on http://localhost:' . $port);
+    $process->run();
+  }
+
+}
diff --git a/core/scripts/dev-site.php b/core/scripts/dev-site.php
new file mode 100644
index 0000000000..44f35e8877
--- /dev/null
+++ b/core/scripts/dev-site.php
@@ -0,0 +1,22 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * @file
+ * Installs and starts Drupal for a dev site.
+ */
+
+use Drupal\Core\Command\DevInstallCommand;
+use Drupal\Core\Command\DevStartCommand;
+use Symfony\Component\Console\Application;
+
+if (PHP_SAPI !== 'cli') {
+  return;
+}
+
+$classloader = require_once __DIR__ . '/../../autoload.php';
+
+$application = new Application('dev-start', '8.0.x');
+$application->add(new DevInstallCommand($classloader));
+$application->add(new DevStartCommand($classloader));
+$application->run();
diff --git a/core/tests/Drupal/Tests/Core/Command/DevSite.php b/core/tests/Drupal/Tests/Core/Command/DevSite.php
new file mode 100644
index 0000000000..013a0c678b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Command/DevSite.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\Tests\Core\Command;
+
+use GuzzleHttp\Client;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Process\PhpExecutableFinder;
+use Symfony\Component\Process\Process;
+
+/**
+ * Tests the dev-site command.
+ *
+ * @group Command
+ */
+class DevSite extends TestCase {
+
+  public function testInstall() {
+    $php_executale_finder = new PhpExecutableFinder();
+    $php = $php_executale_finder->find();
+
+    $command = "{$php} core/scripts/dev-site.php install --install_profile=testing --site_path=sites/test_dev_site";
+    $process = new Process($command);
+
+    $result = $process->run();
+    $this->assertSame(0, $result);
+    $this->assertContains('Drupal installation started.', $process->getOutput());
+    $this->assertContains('Drupal successfully installed.', $process->getOutput());
+
+    $process = new Process("{$php} core/scripts/dev-site.php start");
+
+    $listen_dev_start = function ($type, $output) {
+      if (preg_match('/localhost:(\d+)/', $output, $match)) {
+        $guzzle = new Client();
+        $response = $guzzle->get('http://localhost:' . $match[1]);
+        $content = (string) $response->getBody();
+        $a = 123;
+      }
+    };
+
+    $process->run($listen_dev_start);
+  }
+
+}
