diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 7f4b26b..70e752a 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -146,14 +146,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 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,
@@ -197,10 +198,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
@@ -282,84 +279,97 @@ function install_begin_request(&$install_state) {
   // This must go after drupal_bootstrap(), which unsets globals!
   global $conf;
 
-  require_once DRUPAL_ROOT . '/core/modules/system/system.install';
-  require_once DRUPAL_ROOT . '/core/includes/common.inc';
+  // Load include files containing procedural service helpers.
+  require_once DRUPAL_ROOT . '/core/includes/cache.inc';
+  require_once DRUPAL_ROOT . '/core/includes/database.inc';
+
+  // Required for module_list() override in early installer environment.
   require_once DRUPAL_ROOT . '/core/includes/file.inc';
+  require_once DRUPAL_ROOT . '/core/includes/module.inc';
+
+  // Required for various installer operations.
   require_once DRUPAL_ROOT . '/core/includes/install.inc';
-  require_once DRUPAL_ROOT . '/core/includes/schema.inc';
-  require_once DRUPAL_ROOT . '/' . settings()->get('path_inc', 'core/includes/path.inc');
 
-  // Load module basics (needed for hook invokes).
-  include_once DRUPAL_ROOT . '/core/includes/module.inc';
-  include_once DRUPAL_ROOT . '/core/includes/session.inc';
-  require_once DRUPAL_ROOT . '/core/includes/entity.inc';
+  // Required for running install tasks.
+  require_once DRUPAL_ROOT . '/core/includes/form.inc';
+  require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
+  require_once DRUPAL_ROOT . '/core/includes/batch.inc';
+
+  // Determine bootstrap status.
+  if (!empty($install_state['parameters']['bootstrap'])) {
+    $install_state['parameters']['bootstrap'] = (int) $install_state['parameters']['bootstrap'];
+    $install_state['base_system_verified'] = TRUE;
+  }
 
-  // Determine whether the configuration system is ready to operate.
+  // 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);
-  // Check existing settings.php.
   $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 into drupal_container() 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']) {
-    $kernel = new DrupalKernel('install', FALSE, drupal_classloader(), FALSE);
-    $kernel->boot();
-  }
-  else {
+  // If they are not, replace services with in-memory implementations and
+  // specialized installer implementations. This override is reverted as soon as
+  // the install_system_rebuild() phase is reached.
+  if (!$install_state['base_system_verified']) {
     // @todo Move into a proper Drupal\Core\DependencyInjection\InstallContainerBuilder.
     $container = new ContainerBuilder();
 
+    // 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.
     $container->register('event_dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher');
-
     $container->register('config.storage', 'Drupal\Core\Config\InstallStorage');
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
       ->addArgument(new Reference('event_dispatcher'));
-    // The install process cannot use the database lock backend since the database
-    // is not fully up, so we use a null backend implementation during the
-    // installation process. This will also speed up the installation process.
-    // The site being installed will use the real lock backend when doing AJAX
-    // requests but, except for a WSOD, there is no chance for a a lock to stall
-    // (as opposed to the cache backend) so we can afford having a null
-    // implementation here.
+
+    $conf['keyvalue_default'] = 'keyvalue.memory';
+    $container
+      ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
+      ->addArgument(new Reference('service_container'));
+    $container
+      ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
     $container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
+    $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
+
     drupal_container($container);
-  }
 
-  // Set up $language, so t() caller functions will still work.
-  drupal_language_initialize();
+    // Override the module list with a minimal set of modules.
+    $module_list['system']['filename'] = 'core/modules/system/system.module';
+    module_list(NULL, $module_list);
+    drupal_load('module', 'system');
 
-  require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
-  // Override the module list with a minimal set of modules.
-  $module_list['system']['filename'] = 'core/modules/system/system.module';
-  module_list(NULL, $module_list);
-  drupal_load('module', 'system');
+    // Set up $language, so t() caller functions will still work.
+    drupal_language_initialize();
+  }
+  // We have reached the install_system_rebuild() install task and need to
+  // install System module as well as all the rest of the system in a full
+  // environment.
+  else {
+    $kernel = new DrupalKernel('prod', TRUE, drupal_classloader(), FALSE);
+    $kernel->boot();
 
-  require_once DRUPAL_ROOT . '/core/includes/cache.inc';
-  $conf['cache_classes'] = array('cache' => 'Drupal\Core\Cache\MemoryBackend');
+    // DRUPAL_BOOTSTRAP_SESSION is incompatible with the installer, as it tries
+    // to read and write sessions before the {users} table has been installed.
+    // Therefore, the install_base_system() task needs a custom bootstrap that
+    // avoids session initialization but is otherwise still a full environment.
+    // @todo Convert session handling into a swappable service.
+    // @see http://drupal.org/node/335411
+    if ($install_state['parameters']['bootstrap'] == DRUPAL_BOOTSTRAP_VARIABLES) {
+      require_once DRUPAL_ROOT . '/core/includes/common.inc';
+      drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
+      _drupal_bootstrap_code();
+    }
+    else {
+      drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
+    }
+
+    // We are anonymous until we reach install_configure_form_submit().
+    global $user;
+    if (empty($user)) {
+      $user = drupal_anonymous_user();
+    }
+  }
 
   // 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
@@ -368,17 +378,7 @@ function install_begin_request(&$install_state) {
   // accessing the database before it is set up yet.)
   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 DRUPAL_ROOT . '/core/includes/database.inc';
-
-    // Verify the last completed task in the database, if there is one.
-    $task = install_verify_completed_task();
-  }
-  else {
-    $task = NULL;
-
+  if (!$install_state['database_verified']) {
     // Do not install over a configured settings.php. Check the 'db_url'
     // variable in addition to 'databases', since previous versions of Drupal
     // used that (and we do not want to allow installations on an existing site
@@ -389,8 +389,7 @@ function install_begin_request(&$install_state) {
   }
 
   // Modify the installation state as appropriate.
-  $install_state['completed_task'] = $task;
-  $install_state['database_tables_exist'] = !empty($task);
+  $install_state['completed_task'] = install_verify_completed_task();
 
   // Add the list of available profiles to the installation state.
   $install_state['profiles'] += drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.profile$/', 'profiles');
@@ -434,8 +433,8 @@ 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'])) {
-        variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name);
+      if ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished']) {
+        state()->set('system.install_task', $install_state['installation_finished'] ? 'done' : $task_name);
       }
     }
     // Stop when there are no tasks left. In the case of an interactive
@@ -463,7 +462,6 @@ function install_run_task($task, &$install_state) {
   $function = $task['function'];
 
   if ($task['type'] == 'form') {
-    require_once DRUPAL_ROOT . '/core/includes/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
@@ -508,7 +506,7 @@ function install_run_task($task, &$install_state) {
   elseif ($task['type'] == 'batch') {
     // Start a new batch based on the task function, if one is not running
     // already.
-    $current_batch = variable_get('install_current_batch');
+    $current_batch = state()->get('system.install_current_batch');
     if (!$install_state['interactive'] || !$current_batch) {
       $batch = $function($install_state);
       if (empty($batch)) {
@@ -521,7 +519,7 @@ function install_run_task($task, &$install_state) {
       // task is currently running. Otherwise, we need to make sure the batch
       // will complete in one page request.
       if ($install_state['interactive']) {
-        variable_set('install_current_batch', $function);
+        state()->set('system.install_current_batch', $function);
       }
       else {
         $batch =& batch_get();
@@ -534,7 +532,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 DRUPAL_ROOT . '/core/includes/batch.inc';
       $output = _batch_page();
       // Because Batch API now returns a JSON response for intermediary steps,
       // but the installer doesn't handle Response objects yet, just send the
@@ -550,7 +547,7 @@ function install_run_task($task, &$install_state) {
       // longer requesting a batch ID.
       if ($output === FALSE) {
         // Return nothing so the next task will run in the same request.
-        variable_del('install_current_batch');
+        state()->delete('system.install_current_batch');
         return;
       }
       else {
@@ -630,7 +627,6 @@ function install_tasks($install_state) {
   $tasks = array(
     'install_select_language' => array(
       'display_name' => st('Choose language'),
-      'run' => INSTALL_TASK_RUN_IF_REACHED,
     ),
     'install_download_translation' => array(
       'run' => $needs_download ? INSTALL_TASK_RUN_IF_REACHED : INSTALL_TASK_SKIP,
@@ -638,7 +634,6 @@ function install_tasks($install_state) {
     'install_select_profile' => array(
       'display_name' => st('Choose profile'),
       'display' => count($install_state['profiles']) != 1,
-      'run' => INSTALL_TASK_RUN_IF_REACHED,
     ),
     'install_load_profile' => array(
       'run' => INSTALL_TASK_RUN_IF_REACHED,
@@ -654,10 +649,14 @@ function install_tasks($install_state) {
       // since the form submit handler is where settings.php is rewritten.
       'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
     ),
-    'install_base_system' => array(
+    'install_system_schema' => array(
+      'run' => $install_state['base_system_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED,
     ),
-    'install_bootstrap_full' => array(
-      'run' => INSTALL_TASK_RUN_IF_REACHED,
+    'install_system_rebuild' => 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_base_system' => array(
     ),
     'install_profile_modules' => array(
       'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Installation profile'),
@@ -852,15 +851,82 @@ function install_verify_requirements(&$install_state) {
 }
 
 /**
+ * Install task: Installs the System module schema.
+ */
+function install_system_schema(&$install_state) {
+  require_once DRUPAL_ROOT . '/core/includes/schema.inc';
+  require_once DRUPAL_ROOT . '/core/modules/system/system.install';
+  // This task is the one that sets up persistent storage tables. Since this
+  // task is executed in the early installer environment, install_run_tasks()
+  // is not able to record that this task has been completed. Thus, the
+  // interactive installer invokes this task a second time and the fact that it
+  // completed can only be recorded after the second invocation.
+  $schema = drupal_get_schema_unprocessed('system');
+  $table = key($schema);
+  if (!db_table_exists($table)) {
+    drupal_install_schema('system');
+  }
+
+  // If we are in the interactive installer, all we need to do is to set the
+  // 'bootstrap' parameter. Doing so will trigger a redirect and the next page
+  // request will perform a full boostrap into the new environment, skipping
+  // install_system_rebuild().
+  $install_state['parameters']['bootstrap'] = DRUPAL_BOOTSTRAP_VARIABLES;
+}
+
+/**
+ * Install task: Performs a full bootstrap into the new environment.
+ */
+function install_system_rebuild(&$install_state) {
+  global $conf;
+
+  // This phase is reached in the non-interactive installer only.
+  // We need to unset all overrides and rebuild all necessary facilities to get
+  // into a regular bootstrap state.
+  unset($conf['cache_classes']);
+  unset($conf['keyvalue_default']);
+
+  // Ensure that custom service definitions in a prepared settings.php are
+  // applied. This effectively allows to install Drupal with custom Cache,
+  // KeyValue, and Lock backends in the non-interactive installer.
+  $conf_path = conf_path();
+  if (is_readable(DRUPAL_ROOT . '/' . $conf_path . '/settings.php')) {
+    include DRUPAL_ROOT . '/' . $conf_path . '/settings.php';
+  }
+
+  // Reset module list and hook implementation statics.
+  module_list_reset();
+  module_load_all(FALSE, TRUE);
+  system_list_reset();
+  module_implements_reset();
+
+  // Bootstrap, but avoid session initialization.
+  // @see install_begin_request()
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES);
+  _drupal_bootstrap_code();
+  $kernel = new DrupalKernel('prod', TRUE, drupal_classloader(), FALSE);
+  $kernel->boot();
+
+  $install_state['base_system_verified'] = TRUE;
+}
+
+/**
  * Installation task; install the base functionality Drupal needs to bootstrap.
  *
  * @param $install_state
  *   An array of information about the current installation state.
  */
 function install_base_system(&$install_state) {
-  // Install system.module.
+  // Install System module.
+  // This cannot use module_enable(), since System module's database tables have
+  // to be created separately in install_system_schema(), so we are able to
+  // switch to a full Drupal environment in install_system_rebuild().
   drupal_install_system();
 
+  // Something in the installer bootstrap primes file_get_stream_wrappers() too
+  // early. Resetting that static alone doesn't resolve it.
+  drupal_static_reset();
+
   // Call file_ensure_htaccess() to ensure that all of Drupal's standard
   // directories (e.g., the public files directory and config directory) have
   // appropriate .htaccess files. These directories will have already been
@@ -883,7 +949,13 @@ function install_base_system(&$install_state) {
   $modules[] = drupal_get_profile();
 
   state()->set('install_profile_modules', array_diff($modules, array('system')));
-  $install_state['database_tables_exist'] = TRUE;
+
+  // Now that the {users} table exists, the session can be initialized.
+  $install_state['parameters']['bootstrap'] = DRUPAL_BOOTSTRAP_CODE;
+  if ($install_state['interactive']) {
+    return;
+  }
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
 }
 
 /**
@@ -894,16 +966,8 @@ function install_base_system(&$install_state) {
  *   is already installed.
  */
 function install_verify_completed_task() {
-  try {
-    if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) {
-      $task = unserialize($result->fetchField());
-    }
-  }
-  // Do not trigger an error if the database query fails, since the database
-  // might not be set up yet.
-  catch (Exception $e) {
-  }
-  if (isset($task)) {
+  $task = state()->get('system.install_task');
+  if ($task) {
     if ($task == 'done') {
       throw new Exception(install_already_done_error());
     }
@@ -1079,18 +1143,12 @@ function install_settings_form_submit($form, &$form_state) {
   );
 
   drupal_rewrite_settings($settings);
+  $install_state['database_verified'] = TRUE;
+  $install_state['settings_verified'] = TRUE;
 
   // Add the config directories to settings.php.
   drupal_install_config_directories();
-
-  // Indicate that the settings file has been verified, and check the database
-  // for the last completed task, now that we have a valid connection. This
-  // last step is important since we want to trigger an error if the new
-  // database already has Drupal installed.
-  $install_state['settings_verified'] = TRUE;
   $install_state['config_verified'] = TRUE;
-  $install_state['database_verified'] = TRUE;
-  $install_state['completed_task'] = install_verify_completed_task();
 }
 
 /**
@@ -1559,24 +1617,6 @@ function install_load_profile(&$install_state) {
 }
 
 /**
- * Performs a full bootstrap of Drupal during installation.
- *
- * @param $install_state
- *   An array of information about the current installation state.
- */
-function install_bootstrap_full(&$install_state) {
-  // The early stages of the installer override the cache backend since Drupal
-  // isn't fully set up yet. Here the override is removed so that the standard
-  // cache backend will be used again.
-  unset($GLOBALS['conf']['cache_classes']['cache']);
-  drupal_static_reset('cache');
-  // Clear the module list that was overriden earlier in the process.
-  // This will allow all freshly installed modules to be loaded.
-  module_list_reset();
-  drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-}
-
-/**
  * Installs required modules via a batch process.
  *
  * @param $install_state
@@ -1586,6 +1626,12 @@ function install_bootstrap_full(&$install_state) {
  *   The batch definition.
  */
 function install_profile_modules(&$install_state) {
+  // Batch API relies on drupal_get_token() and thus an initialized session.
+  // Up until now, the entire installation process happened without a user.
+  // Now that we are in a full environment, we need to enforce the session to be
+  // regenerated, so we have a new session ID and it is stored for the user.
+  drupal_session_regenerate();
+
   $modules = state()->get('install_profile_modules') ?: array();
   $files = system_rebuild_module_data();
   state()->delete('install_profile_modules');
@@ -1732,15 +1778,6 @@ function install_configure_form($form, &$form_state, &$install_state) {
   // work during installation.
   drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting');
 
-  // Cache a fully-built schema. This is necessary for any invocation of
-  // index.php because: (1) setting cache table entries requires schema
-  // information, (2) that occurs during bootstrap before any module are
-  // loaded, so (3) if there is no cached schema, drupal_get_schema() will
-  // try to generate one but with no loaded modules will return nothing.
-  //
-  // @todo Move this to the 'install_finished' task?
-  drupal_get_schema(NULL, TRUE);
-
   // Return the form.
   return _install_configure_form($form, $form_state, $install_state);
 }
@@ -2351,5 +2388,5 @@ function install_configure_form_submit($form, &$form_state) {
   user_login_finalize();
 
   // Record when this install ran.
-  variable_set('install_time', $_SERVER['REQUEST_TIME']);
+  state()->set('system.install_time', $_SERVER['REQUEST_TIME']);
 }
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 9223b9c..37468df 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -395,47 +395,35 @@ function drupal_verify_profile($install_state) {
 }
 
 /**
- * Installs the system module.
- *
- * Separated from the installation of other modules so core system
- * functions can be made available while other modules are installed.
+ * Installs the System module.
+ *
+ * This is identical to module_enable(), with two exceptions:
+ * - System module's database schema has been installed in
+ *   install_system_schema() already, as it is required for booting a kernel
+ *   with regular services. module_enable() installs a module's database schema
+ *   without a failsafe "IF NOT EXISTS" SQL condition, so it would bail out with
+ *   database query errors.
+ * - The module entry for System module in the system.module:enabled
+ *   configuration can only be written after the default configuration of System
+ *   module has been installed, since the default config overwrites any
+ *   potentially pre-existing config files.
+ *
+ * @see install_base_system()
+ * @see install_system_schema()
  */
 function drupal_install_system() {
-  // Create tables.
-  drupal_install_schema('system');
-  // Immediately boot a kernel to have real services ready.
-  $kernel = new DrupalKernel('install', FALSE, drupal_classloader(), FALSE);
-  $kernel->boot();
-
-  $system_path = drupal_get_path('module', 'system');
-  require_once DRUPAL_ROOT . '/' . $system_path . '/system.install';
+  // The tables from system are already installed. As this fact can only be
+  // recorded in one of those tables, this needs special code.
   $system_versions = drupal_get_schema_versions('system');
   $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
   drupal_container()
     ->get('keyvalue')
     ->get('system.schema')
     ->set('system', $system_version);
-
-  // System module needs to be enabled and the system/module lists need to be
-  // reset first in order to allow config_install_default_config() to invoke
-  // config import callbacks.
-  // @todo Installation profiles may override the system.module config object.
-  config('system.module')
-    ->set('enabled.system', 0)
-    ->save();
-
-  // Clear out module list and hook implementation statics.
-  system_list_reset();
-  module_list_reset();
-  module_implements_reset();
-
-  // To ensure that the system module can be found by the plugin system, warm
-  // the module list cache.
-  // @todo Remove this in http://drupal.org/node/1798732.
-  module_list();
+  // module_enable() will treat system as an installed but disabled module
+  // and so installing the default configuration needs to happen separately.
   config_install_default_config('module', 'system');
-
-  module_invoke('system', 'install');
+  module_enable(array('system'), FALSE);
 }
 
 /**
diff --git a/core/includes/module.inc b/core/includes/module.inc
index f6d47b6..f89f0a1 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -518,13 +518,11 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
       system_list_reset();
       module_implements_reset();
       _system_update_bootstrap_status();
-      $module_filenames[$module] = drupal_get_filename('module', $module);
+
       // Update the kernel to include it.
-      // @todo The if statement is here because install_begin_request() creates
-      //   a container without a kernel. It probably shouldn't.
-      if ($kernel = drupal_container()->get('kernel', ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
-        $kernel->updateModules(module_list(), $module_filenames);
-      }
+      $module_filenames[$module] = drupal_get_filename('module', $module);
+      drupal_container()->get('kernel')->updateModules(module_list(), $module_filenames);
+
       // Refresh the schema to include it.
       drupal_get_schema(NULL, TRUE);
       // Update the theme registry to include it.
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index 3d43a8c..97a3e74 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -145,7 +145,7 @@ public function encode($data) {
    */
   public function decode($raw) {
     $data = @unserialize($raw);
-    return is_array($data) ? $data : FALSE;
+    return is_array($data) ? $data : array();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 6422e9d..6ca5a88 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -173,9 +173,10 @@ public function encode($data) {
    */
   public function decode($raw) {
     $data = $this->getParser()->parse($raw);
-    // A simple string is valid YAML for any reason.
+    // A simple string is valid YAML for any reason. Also, a config file may
+    // exist but can be empty, in which case $data is NULL.
     if (!is_array($data)) {
-      return FALSE;
+      return array();
     }
     return $data;
   }
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
index a2b3448..7a6f75b 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
@@ -39,14 +39,14 @@ function testCRUD() {
     $data = $this->storage->read($name);
     $this->assertIdentical($data, FALSE);
 
-    // Reading a name containing non-decodeable data returns FALSE.
+    // Reading a name containing non-decodeable data returns an array.
     $this->insert($name, '');
     $data = $this->storage->read($name);
-    $this->assertIdentical($data, FALSE);
+    $this->assertIdentical($data, array());
 
     $this->update($name, 'foo');
     $data = $this->storage->read($name);
-    $this->assertIdentical($data, FALSE);
+    $this->assertIdentical($data, array());
 
     $this->delete($name);
 
diff --git a/core/modules/system/config/system.module.disabled.yml b/core/modules/system/config/system.module.disabled.yml
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/system/config/system.module.yml b/core/modules/system/config/system.module.yml
index 696bedc..01151d1 100644
--- a/core/modules/system/config/system.module.yml
+++ b/core/modules/system/config/system.module.yml
@@ -1,2 +1 @@
-enabled:
-  system: '0'
+enabled: {  }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php
index 4fb1f68..47cd712 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/StateSystemUpgradePathTest.php
@@ -35,6 +35,14 @@ public function testSystemVariableUpgrade() {
 
     $expected_state = array();
 
+    $expected_state['system.install_time'] = array(
+      'value' => 1304208000,
+      'variable_name' => 'install_time',
+    );
+    $expected_state['system.install_task'] = array(
+      'value' => 'done',
+      'variable_name' => 'install_task',
+    );
     $expected_state['node.node_access_needs_rebuild'] = array(
       'value' => TRUE,
       'variable_name' => 'node_access_needs_rebuild',
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index ca5d6de..46ba2ff 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -274,7 +274,7 @@ function system_requirements($phase) {
     // Determine when cron last ran.
     $cron_last = state()->get('system.cron_last');
     if (!is_numeric($cron_last)) {
-      $cron_last = variable_get('install_time', 0);
+      $cron_last = state()->get('system.install_time') ?: 0;
     }
 
     // Determine severity based on time since cron last ran.
@@ -1952,6 +1952,18 @@ function system_update_8025() {
 }
 
 /**
+ * Migrates install_task and install_time variables to State API.
+ *
+ * @ingroup config_upgrade
+ */
+function system_update_8026() {
+  update_variables_to_state(array(
+    'install_task' => 'system.install_task',
+    'install_time' => 'system.install_time',
+  ));
+}
+
+/**
  * Cleans up javascript_parsed variable.
  *
  * @ingroup system_upgrade
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index cdf7b90..7305d70 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3729,7 +3729,7 @@ function system_run_automated_cron() {
   // If the site is not fully installed, suppress the automated cron run.
   // Otherwise it could be triggered prematurely by Ajax requests during
   // installation.
-  if (($threshold = config('system.cron')->get('threshold.autorun')) > 0 && variable_get('install_task') == 'done') {
+  if (($threshold = config('system.cron')->get('threshold.autorun')) > 0 && state()->get('system.install_task') == 'done') {
     $cron_last = state()->get('system.cron_last') ?: NULL;
     if (!isset($cron_last) || (REQUEST_TIME - $cron_last > $threshold)) {
       drupal_cron_run();
diff --git a/core/modules/system/tests/upgrade/drupal-7.state.system.database.php b/core/modules/system/tests/upgrade/drupal-7.state.system.database.php
index a6f47ef..1c40289 100644
--- a/core/modules/system/tests/upgrade/drupal-7.state.system.database.php
+++ b/core/modules/system/tests/upgrade/drupal-7.state.system.database.php
@@ -12,6 +12,9 @@
 
 // Update system settings to known values.
 db_merge('variable')
+  ->key(array('name' => 'install_time'))->fields(array('value' => serialize(1304208000)))
+  ->execute();
+db_merge('variable')
   ->key(array('name' => 'node_cron_views_scale'))
   ->fields(array('value' => serialize(1.0 / 2000)))
   ->execute();
