diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 46fbcbce4fe99b8e2ab78b856a9f26c7772945d8..6dfc2700e3b004c99f534a781370a09cfd5b0273 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -7,6 +7,7 @@
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\CoreServiceProvider;
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\Database\Install\TaskException;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
@@ -156,14 +157,15 @@ function install_state_defaults() {
     // The last task that was completed during the previous installation
     // request.
     'completed_task' => NULL,
-    // This becomes TRUE only when a valid config directory is created or
-    // detected.
+    // TRUE when there are valid config directories.
     'config_verified' => FALSE,
-    // This becomes TRUE only when Drupal's system module is installed.
-    'database_tables_exist' => FALSE,
-    // This becomes TRUE only when a valid database connection can be
-    // established.
+    // TRUE when there is a valid database connection.
     'database_verified' => FALSE,
+    // TRUE when a valid settings.php exists (containing both database
+    // connection information and config directory names).
+    'settings_verified' => FALSE,
+    // TRUE when the base system has been installed and is ready to operate.
+    'base_system_verified' => FALSE,
     // Whether a translation file for the selected language will be downloaded
     // from the translation server.
     'download_translation' => FALSE,
@@ -209,10 +211,6 @@ function install_state_defaults() {
     // Tokens in the pattern will be replaced by appropriate values for the
     // required translation file.
     'server_pattern' => 'http://ftp.drupal.org/files/translations/%core/%project/%project-%version.%language.po',
-    // This becomes TRUE only when a valid settings.php file is written
-    // (containing both valid database connection information and a valid
-    // config directory).
-    'settings_verified' => FALSE,
     // Installation tasks can set this to TRUE to force the page request to
     // end (even if there is no themable output), in the case of an interactive
     // installation. This is needed only rarely; for example, it would be used
@@ -322,48 +320,43 @@ function install_begin_request(&$install_state) {
   require_once __DIR__ . '/install.inc';
   require_once __DIR__ . '/schema.inc';
   require_once __DIR__ . '/../../' . settings()->get('path_inc', 'core/includes/path.inc');
+  require_once __DIR__ . '/cache.inc';
+  require_once __DIR__ . '/database.inc';
+  require_once __DIR__ . '/form.inc';
+  require_once __DIR__ . '/batch.inc';
+  require_once __DIR__ . '/ajax.inc';
 
   // Load module basics (needed for hook invokes).
   include_once __DIR__ . '/module.inc';
   include_once __DIR__ . '/session.inc';
   require_once __DIR__ . '/entity.inc';
 
-  // Determine whether the configuration system is ready to operate.
-  $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY);
-
   // Register the translation services.
   install_register_translation_service($container);
   \Drupal::setContainer($container);
 
-  // Check existing settings.php.
+  // Determine whether base system services are ready to operate.
+  $install_state['config_verified'] = install_verify_config_directory(CONFIG_ACTIVE_DIRECTORY) && install_verify_config_directory(CONFIG_STAGING_DIRECTORY);
   $install_state['database_verified'] = install_verify_database_settings();
   $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified'];
 
-  // If it is not, replace the configuration storage with the InstallStorage
-  // implementation, for the following reasons:
-  // - The first call to $kernel->getContainer() will try to set up the regular
-  //   runtime configuration storage, using the CachedStorage by default. It
-  //   calls config_get_config_directory() to retrieve the config directory to
-  //   use, but that throws an exception, since $config_directories is not
-  //   defined since there is no settings.php yet. If there is a prepared
-  //   settings.php already, then the returned directory still cannot be used,
-  //   because it does not necessarily exist. The installer ensures that it
-  //   exists and is writeable in a later step.
-  // - The installer outputs maintenance theme pages and performs many other
-  //   operations, which try to load configuration. Since there is no active
-  //   configuration yet, and because the configuration system does not have a
-  //   notion of default values at runtime, data is missing in many places. The
-  //   lack of data does not trigger errors, but results in a broken user
-  //   interface (e.g., missing page title, etc).
-  // - The actual configuration data to read during installation is essentially
-  //   the default configuration provided by the installation profile and
-  //   modules (most notably System module). The InstallStorage therefore reads
-  //   from the default configuration directories of extensions.
-  // This override is reverted as soon as the config directory and the
-  // database has been set up successfully.
-  // @see drupal_install_config_directories()
-  // @see install_settings_form_submit()
   if ($install_state['settings_verified']) {
+    try {
+      $system_schema = system_schema();
+      end($system_schema);
+      $table = key($system_schema);
+      $install_state['base_system_verified'] = Database::getConnection()->schema()->tableExists($table);
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      // The last defined table of the base system_schema() does not exist yet.
+      // $install_state['base_system_verified'] defaults to FALSE, so the code
+      // following below will use the minimal installer service container.
+      // As soon as the base system is verified here, the installer operates in
+      // a full and regular Drupal environment, without any kind of exceptions.
+    }
+  }
+
+  if ($install_state['base_system_verified']) {
     $kernel = new DrupalKernel('install', drupal_classloader(), FALSE);
     $kernel->boot();
     $container = $kernel->getContainer();
@@ -371,6 +364,9 @@ function install_begin_request(&$install_state) {
     $container->set('string_translator.file_translation', install_file_translation_service());
     $container->get('string_translation')->addTranslator($container->get('string_translator.file_translation'));
   }
+  // Replace services with in-memory implementations and specialized installer
+  // implementations. This service container is reverted to a regular
+  // DrupalKernel in install_bootstrap_full().
   else {
     // @todo Move into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder.
     $container = new ContainerBuilder();
@@ -494,8 +490,6 @@ function install_begin_request(&$install_state) {
     \Drupal::translation()->setDefaultLangcode($install_state['parameters']['langcode']);
   }
 
-  require_once __DIR__ . '/ajax.inc';
-
   $module_handler = \Drupal::moduleHandler();
   if (!$module_handler->moduleExists('system')) {
     // Override the module list with a minimal set of modules.
@@ -503,8 +497,6 @@ function install_begin_request(&$install_state) {
   }
   $module_handler->load('system');
 
-  require_once __DIR__ . '/cache.inc';
-
   // Prepare for themed output. We need to run this at the beginning of the
   // page request to avoid a different theme accidentally getting set. (We also
   // need to run it even in the case of command-line installations, to prevent
@@ -513,10 +505,6 @@ function install_begin_request(&$install_state) {
   drupal_maintenance_theme();
 
   if ($install_state['database_verified']) {
-    // Initialize the database system. Note that the connection
-    // won't be initialized until it is actually requested.
-    require_once __DIR__ . '/database.inc';
-
     // Verify the last completed task in the database, if there is one.
     $task = install_verify_completed_task();
   }
@@ -541,7 +529,6 @@ function install_begin_request(&$install_state) {
 
   // Modify the installation state as appropriate.
   $install_state['completed_task'] = $task;
-  $install_state['database_tables_exist'] = !empty($task);
 
   // Add the list of available profiles to the installation state.
   $install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
@@ -585,7 +572,7 @@ function install_run_tasks(&$install_state) {
     if (!$install_state['task_not_complete']) {
       $install_state['tasks_performed'][] = $task_name;
       $install_state['installation_finished'] = empty($tasks_to_perform);
-      if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) {
+      if ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished']) {
         \Drupal::state()->set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
       }
     }
@@ -614,7 +601,6 @@ function install_run_task($task, &$install_state) {
   $function = $task['function'];
 
   if ($task['type'] == 'form') {
-    require_once __DIR__ . '/form.inc';
     if ($install_state['interactive']) {
       // For interactive forms, build the form and ensure that it will not
       // redirect, since the installer handles its own redirection only after
@@ -692,7 +678,6 @@ function install_run_task($task, &$install_state) {
     // If we are in the middle of processing this batch, keep sending back
     // any output from the batch process, until the task is complete.
     elseif ($current_batch == $function) {
-      include_once __DIR__ . '/batch.inc';
       $output = _batch_page(\Drupal::request());
       // Because Batch API now returns a JSON response for intermediary steps,
       // but the installer doesn't handle Response objects yet, just send the
@@ -813,7 +798,9 @@ function install_tasks($install_state) {
       'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
     ),
     'install_base_system' => array(
+      'run' => $install_state['base_system_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
     ),
+    // All tasks below are executed in a regular, full Drupal environment.
     'install_bootstrap_full' => array(
       'run' => INSTALL_TASK_RUN_IF_REACHED,
     ),
@@ -1047,7 +1034,7 @@ function install_base_system(&$install_state) {
   $modules[] = drupal_get_profile();
 
   \Drupal::state()->set('install_profile_modules', array_diff($modules, array('system')));
-  $install_state['database_tables_exist'] = TRUE;
+  $install_state['base_system_verified'] = TRUE;
 }
 
 /**