diff --git a/core/config/install/core.extension.yml b/core/config/install/core.extension.yml
index ce74baed1829fe104bc1dc9df9543f24be8100d5..659446cc64c88900a59666f480bf3a475678b5dc 100644
--- a/core/config/install/core.extension.yml
+++ b/core/config/install/core.extension.yml
@@ -1,2 +1,3 @@
 module: {}
 theme: {}
+profile: ''
diff --git a/core/config/schema/core.extension.schema.yml b/core/config/schema/core.extension.schema.yml
index bdc84b9fee89f69e3c44dadd9105a3766001f545..087e2e3b9497f75ca86794bbd34796bb8b302350 100644
--- a/core/config/schema/core.extension.schema.yml
+++ b/core/config/schema/core.extension.schema.yml
@@ -14,3 +14,6 @@ core.extension:
       sequence:
         type: integer
         label: 'Weight'
+    profile:
+      type: string
+      label: 'Install profile'
diff --git a/core/core.services.yml b/core/core.services.yml
index 5b0de757832a4d07bbd2ed5f6577dba3a0dff0fc..d67a6fa099ad0996535fbf63d455fb863b33684a 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -303,7 +303,7 @@ services:
       - { name: event_subscriber }
   config.installer:
     class: Drupal\Core\Config\ConfigInstaller
-    arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher']
+    arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher', '%install_profile%']
     lazy: true
   config.storage:
     class: Drupal\Core\Config\CachedStorage
@@ -328,7 +328,7 @@ services:
       - { name: backend_overridable }
   config.storage.schema:
     class: Drupal\Core\Config\ExtensionInstallStorage
-    arguments: ['@config.storage', 'config/schema']
+    arguments: ['@config.storage', '%install_profile%', 'config/schema']
   config.typed:
     class: Drupal\Core\Config\TypedConfigManager
     arguments: ['@config.storage', '@config.storage.schema', '@cache.discovery', '@module_handler']
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index f74859e17185f93f9d459f9c7c830418f1b0f1c8..5b18c5cce02fdc864e575aba7d3bf5587f03d714 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -9,6 +9,7 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Config\BootstrapConfigStorageFactory;
 use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Render\Markup;
 use Drupal\Component\Render\MarkupInterface;
@@ -727,12 +728,20 @@ function drupal_installation_attempted() {
  * When this function is called during Drupal's initial installation process,
  * the name of the profile that's about to be installed is stored in the global
  * installation state. At all other times, the "install_profile" setting will be
- * available in settings.php.
+ * available in container as a parameter.
  *
  * @return string|null $profile
  *   The name of the installation profile or NULL if no installation profile is
  *   currently active. This is the case for example during the first steps of
  *   the installer or during unit tests.
+ *
+ * @deprecated in Drupal 8.3.0, will be removed before Drupal 9.0.0.
+ *   Use the install_profile container parameter or \Drupal::installProfile()
+ *   instead. If you are accessing the value before it is written to
+ *   configuration during the installer use the $install_state global. If you
+ *   need to access the value before container is available you can use
+ *   BootstrapConfigStorageFactory to load the value directly from
+ *   configuration.
  */
 function drupal_get_profile() {
   global $install_state;
@@ -747,8 +756,18 @@ function drupal_get_profile() {
     }
   }
   else {
-    // Fall back to NULL, if there is no 'install_profile' setting.
-    $profile = Settings::get('install_profile');
+    if (\Drupal::hasContainer()) {
+      $profile = \Drupal::installProfile();
+    }
+    else {
+      $profile = BootstrapConfigStorageFactory::getDatabaseStorage()->read('core.extension')['profile'];
+    }
+
+    // A BC layer just in in case this only exists in Settings. Introduced in
+    // Drupal 8.3.x and will be removed before Drupal 9.0.0.
+    if (empty($profile)) {
+      $profile = Settings::get('install_profile');
+    }
   }
 
   return $profile;
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 1616f1891e1e44b66ca9f06cbceb4c8a93f4dc1c..af64bc4e14edb81bcfce72b45e4ffdcc83529730 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -12,6 +12,7 @@
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Installer\Exception\AlreadyInstalledException;
 use Drupal\Core\Installer\Exception\InstallerException;
+use Drupal\Core\Installer\Exception\InstallProfileMismatchException;
 use Drupal\Core\Installer\Exception\NoProfilesException;
 use Drupal\Core\Installer\InstallerKernel;
 use Drupal\Core\Language\Language;
@@ -2172,13 +2173,23 @@ function install_display_requirements($install_state, $requirements) {
 }
 
 /**
- * Installation task; ensures install profile is written to settings.php.
+ * Installation task; writes profile to settings.php if possible.
  *
  * @param array $install_state
  *   An array of information about the current installation state.
+ *
+ * @see _install_select_profile()
+ *
+ * @throws \Drupal\Core\Installer\Exception\InstallProfileMismatchException
+ *
+ * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. The
+ *    install profile is written to core.extension.
  */
 function install_write_profile($install_state) {
-  if (Settings::get('install_profile') !== $install_state['parameters']['profile']) {
+  // Only need to write to settings.php if it is possible. The primary storage
+  // for the install profile is the core.extension configuration.
+  $settings_path = \Drupal::service('site.path') . '/settings.php';
+  if (is_writable($settings_path)) {
     // Remember the profile which was used.
     $settings['settings']['install_profile'] = (object) array(
       'value' => $install_state['parameters']['profile'],
@@ -2186,4 +2197,7 @@ function install_write_profile($install_state) {
     );
     drupal_rewrite_settings($settings);
   }
+  elseif (($settings_profile = Settings::get('install_profile')) && $settings_profile !== $install_state['parameters']['profile']) {
+    throw new InstallProfileMismatchException($install_state['parameters']['profile'], $settings_profile, $settings_path, \Drupal::translation());
+  }
 }
diff --git a/core/includes/install.inc b/core/includes/install.inc
index e243c184f0bb60f165bc29253e14019768f2ecd5..c49625c2c16aa7841c34f7c76338180bb96c4ed2 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -621,6 +621,12 @@ function drupal_install_system($install_state) {
   // Install base system configuration.
   \Drupal::service('config.installer')->installDefaultConfig('core', 'core');
 
+  // Store the installation profile in configuration to populate the
+  // 'install_profile' container parameter.
+  \Drupal::configFactory()->getEditable('core.extension')
+    ->set('profile', $install_state['parameters']['profile'])
+    ->save();
+
   // Install System module and rebuild the newly available routes.
   $kernel->getContainer()->get('module_installer')->install(array('system'), FALSE);
   \Drupal::service('router.builder')->rebuild();
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 81c72db081aabdff9c51a95e25bd10e2695a385d..b9388fc100d1dc7bf71ec5aa9e536fd094a88fd3 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -181,6 +181,16 @@ public static function root() {
     return static::getContainer()->get('app.root');
   }
 
+  /**
+   * Gets the active install profile.
+   *
+   * @return string|null
+   *   The name of the active install profile.
+   */
+  public static function installProfile() {
+    return static::getContainer()->getParameter('install_profile');
+  }
+
   /**
    * Indicates if there is a currently active request object.
    *
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index f98691f05866bd716dca2a28fdb5906bc7b1037f..46080557cd0a1cfd052e40d80f3da687634ed2b7 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -6,7 +6,6 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Config\Entity\ConfigDependencyManager;
 use Drupal\Core\Config\Entity\ConfigEntityDependency;
-use Drupal\Core\Site\Settings;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 class ConfigInstaller implements ConfigInstallerInterface {
@@ -60,6 +59,13 @@ class ConfigInstaller implements ConfigInstallerInterface {
    */
   protected $isSyncing = FALSE;
 
+  /**
+   * The name of the currently active installation profile.
+   *
+   * @var string
+   */
+  protected $installProfile;
+
   /**
    * Constructs the configuration installer.
    *
@@ -73,13 +79,16 @@ class ConfigInstaller implements ConfigInstallerInterface {
    *   The configuration manager.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
    *   The event dispatcher.
+   * @param string $install_profile
+   *   The name of the currently active installation profile.
    */
-  public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher) {
+  public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile) {
     $this->configFactory = $config_factory;
     $this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
     $this->typedConfig = $typed_config;
     $this->configManager = $config_manager;
     $this->eventDispatcher = $event_dispatcher;
+    $this->installProfile = $install_profile;
   }
 
   /**
@@ -140,7 +149,7 @@ public function installDefaultConfig($type, $name) {
       // Install any optional configuration entities whose dependencies can now
       // be met. This searches all the installed modules config/optional
       // directories.
-      $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE);
+      $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), $this->installProfile, InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE);
       $this->installOptionalConfig($storage, [$type => $name]);
     }
 
@@ -156,11 +165,11 @@ public function installOptionalConfig(StorageInterface $storage = NULL, $depende
     $optional_profile_config = [];
     if (!$storage) {
       // Search the install profile's optional configuration too.
-      $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE);
+      $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), $this->installProfile, InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE);
       // The extension install storage ensures that overrides are used.
       $profile_storage = NULL;
     }
-    elseif (isset($profile)) {
+    elseif (!empty($profile)) {
       // Creates a profile storage to search for overrides.
       $profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
       $profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
@@ -331,7 +340,7 @@ protected function createConfiguration($collection, array $config_to_create) {
    * {@inheritdoc}
    */
   public function installCollectionDefaultConfig($collection) {
-    $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted());
+    $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), $this->installProfile, InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted());
     // Only install configuration for enabled extensions.
     $enabled_extensions = $this->getEnabledExtensions();
     $config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
@@ -671,9 +680,7 @@ protected function drupalGetPath($type, $name) {
    *   of the installer or during unit tests.
    */
   protected function drupalGetProfile() {
-    // Settings is safe to use because settings.php is written before any module
-    // is installed.
-    return Settings::get('install_profile');
+    return $this->installProfile;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
index 14e80dd8869efd1edd1e16d8abf08d756e44890f..96e1e382240428200d8fb59c57fc1f8718111e19 100644
--- a/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
+++ b/core/lib/Drupal/Core/Config/ExtensionInstallStorage.php
@@ -2,7 +2,6 @@
 
 namespace Drupal\Core\Config;
 
-use Drupal\Core\Site\Settings;
 use Drupal\Core\Extension\ExtensionDiscovery;
 
 /**
@@ -27,12 +26,21 @@ class ExtensionInstallStorage extends InstallStorage {
    */
   protected $includeProfile = TRUE;
 
+  /**
+   * The name of the currently active installation profile.
+   *
+   * @var string
+   */
+  protected $installProfile;
+
   /**
    * Overrides \Drupal\Core\Config\InstallStorage::__construct().
    *
    * @param \Drupal\Core\Config\StorageInterface $config_storage
    *   The active configuration store where the list of enabled modules and
    *   themes is stored.
+   * @param string $profile
+   *   The current installation profile.
    * @param string $directory
    *   The directory to scan in each extension to scan for files. Defaults to
    *   'config/install'.
@@ -43,10 +51,12 @@ class ExtensionInstallStorage extends InstallStorage {
    *   (optional) Whether to include the install profile in extensions to
    *   search and to get overrides from.
    */
-  public function __construct(StorageInterface $config_storage, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE) {
+  public function __construct(StorageInterface $config_storage, $profile, $directory = self::CONFIG_INSTALL_DIRECTORY, $collection = StorageInterface::DEFAULT_COLLECTION, $include_profile = TRUE) {
     parent::__construct($directory, $collection);
+
     $this->configStorage = $config_storage;
     $this->includeProfile = $include_profile;
+    $this->installProfile = $profile;
   }
 
   /**
@@ -77,22 +87,20 @@ protected function getAllFolders() {
       $this->folders = array();
       $this->folders += $this->getCoreNames();
 
-      $install_profile = Settings::get('install_profile');
-      $profile = drupal_get_profile();
       $extensions = $this->configStorage->read('core.extension');
       // @todo Remove this scan as part of https://www.drupal.org/node/2186491
       $listing = new ExtensionDiscovery(\Drupal::root());
       if (!empty($extensions['module'])) {
         $modules = $extensions['module'];
         // Remove the install profile as this is handled later.
-        unset($modules[$install_profile]);
+        unset($modules[$this->installProfile]);
         $profile_list = $listing->scan('profile');
-        if ($profile && isset($profile_list[$profile])) {
+        if ($this->installProfile && isset($profile_list[$this->installProfile])) {
           // Prime the drupal_get_filename() static cache with the profile info
           // file location so we can use drupal_get_path() on the active profile
           // during the module scan.
           // @todo Remove as part of https://www.drupal.org/node/2186491
-          drupal_get_filename('profile', $profile, $profile_list[$profile]->getPathname());
+          drupal_get_filename('profile', $this->installProfile, $profile_list[$this->installProfile]->getPathname());
         }
         $module_list_scan = $listing->scan('module');
         $module_list = array();
@@ -117,12 +125,12 @@ protected function getAllFolders() {
         // The install profile can override module default configuration. We do
         // this by replacing the config file path from the module/theme with the
         // install profile version if there are any duplicates.
-        if (isset($profile)) {
+        if ($this->installProfile) {
           if (!isset($profile_list)) {
             $profile_list = $listing->scan('profile');
           }
-          if (isset($profile_list[$profile])) {
-            $profile_folders = $this->getComponentNames(array($profile_list[$profile]));
+          if (isset($profile_list[$this->installProfile])) {
+            $profile_folders = $this->getComponentNames(array($profile_list[$this->installProfile]));
             $this->folders = $profile_folders + $this->folders;
           }
         }
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index 274a3fa71e6353564f17972127b957e1f8313879..2366576c647c1676be1a13aac45d2a84896051b6 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -1195,6 +1195,7 @@ protected function compileContainer() {
     $container = $this->getContainerBuilder();
     $container->set('kernel', $this);
     $container->setParameter('container.modules', $this->getModulesParameter());
+    $container->setParameter('install_profile', $this->getInstallProfile());
 
     // Get a list of namespaces and put it onto the container.
     $namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames());
@@ -1552,4 +1553,25 @@ protected function addServiceFiles(array $service_yamls) {
     $this->serviceYamls['site'] = array_filter($service_yamls, 'file_exists');
   }
 
+  /**
+   * Gets the active install profile.
+   *
+   * @return string|null
+   *   The name of the any active install profile or distribution.
+   */
+  protected function getInstallProfile() {
+    $config = $this->getConfigStorage()->read('core.extension');
+    if (!empty($config['profile'])) {
+      $install_profile = $config['profile'];
+    }
+    // @todo https://www.drupal.org/node/2831065 remove the BC layer.
+    else {
+      // If system_update_8300() has not yet run fallback to using settings.
+      $install_profile = Settings::get('install_profile');
+    }
+
+    // Normalize an empty string to a NULL value.
+    return empty($install_profile) ? NULL : $install_profile;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
index ad2eb5e04e092ae5f02cf0e27066f0e3e0bc2110..24b4c70d5fb612ae10986296c7f7d1a5c5eeceaf 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ConfigImportSubscriber.php
@@ -8,7 +8,6 @@
 use Drupal\Core\Config\ConfigImportValidateEventSubscriberBase;
 use Drupal\Core\Config\ConfigNameException;
 use Drupal\Core\Extension\ThemeHandlerInterface;
-use Drupal\Core\Site\Settings;
 
 /**
  * Config import subscriber for config import events.
@@ -112,9 +111,10 @@ protected function validateModules(ConfigImporter $config_importer) {
       }
     }
 
-    // Settings is safe to use because settings.php is written before any module
-    // is installed.
-    $install_profile = Settings::get('install_profile');
+    // Get the install profile from the site's configuration.
+    $current_core_extension = $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension');
+    $install_profile = isset($current_core_extension['profile']) ? $current_core_extension['profile'] : NULL;
+
     // Ensure that all modules being uninstalled are not required by modules
     // that will be installed after the import.
     $uninstalls = $config_importer->getExtensionChangelist('module', 'uninstall');
@@ -129,10 +129,15 @@ protected function validateModules(ConfigImporter $config_importer) {
     }
 
     // Ensure that the install profile is not being uninstalled.
-    if (in_array($install_profile, $uninstalls)) {
+    if (in_array($install_profile, $uninstalls, TRUE)) {
       $profile_name = $module_data[$install_profile]->info['name'];
       $config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the install profile.', array('%profile' => $profile_name)));
     }
+
+    // Ensure the profile is not changing.
+    if ($install_profile !== $core_extension['profile']) {
+      $config_importer->logError($this->t('Cannot change the install profile from %new_profile to %profile once Drupal is installed.', array('%profile' => $install_profile, '%new_profile' => $core_extension['profile'])));
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Installer/Exception/InstallProfileMismatchException.php b/core/lib/Drupal/Core/Installer/Exception/InstallProfileMismatchException.php
new file mode 100644
index 0000000000000000000000000000000000000000..d67978986abbaf552e8effe3aeafaf8561ccb27e
--- /dev/null
+++ b/core/lib/Drupal/Core/Installer/Exception/InstallProfileMismatchException.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Drupal\Core\Installer\Exception;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+
+/**
+ * Exception thrown if settings.php cannot be written and the chosen profile does not match.
+ */
+class InstallProfileMismatchException extends InstallerException {
+
+  /**
+   * Constructs a new InstallProfileMismatchException exception.
+   *
+   * @param string $selected_profile
+   *   The profile selected by _install_select_profile().
+   * @param string $settings_profile
+   *   The profile in settings.php.
+   * @param string $settings_file
+   *   The path to settigns.php.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation manager.
+   *
+   * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. The
+   *    install profile is written to core.extension.
+   *
+   * @see _install_select_profile()
+   * @see install_write_profile
+   */
+  public function __construct($selected_profile, $settings_profile, $settings_file, TranslationInterface $string_translation) {
+    $this->stringTranslation = $string_translation;
+
+    $title = $this->t('Install profile mismatch');
+    $message = $this->t(
+      'The selected profile %profile does not match the install_profile setting, which is %settings_profile. Cannot write updated setting to %settings_file.',
+      [
+        '%profile' => $selected_profile,
+        '%settings_profile' => $settings_profile,
+        '%settings_file' => $settings_file,
+      ]
+    );
+    parent::__construct($message, $title);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Installer/InstallerKernel.php b/core/lib/Drupal/Core/Installer/InstallerKernel.php
index e00c215c09e82cd7ca6e1095b265cf53e6a6f289..adeb5c53eed62b1935dcd43c079b8d10a6abbe45 100644
--- a/core/lib/Drupal/Core/Installer/InstallerKernel.php
+++ b/core/lib/Drupal/Core/Installer/InstallerKernel.php
@@ -46,4 +46,24 @@ public function getConfigStorage() {
     return parent::getConfigStorage();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getInstallProfile() {
+    global $install_state;
+    if ($install_state && empty($install_state['installation_finished'])) {
+      // If the profile has been selected return it.
+      if (isset($install_state['parameters']['profile'])) {
+        $profile = $install_state['parameters']['profile'];
+      }
+      else {
+        $profile = NULL;
+      }
+    }
+    else {
+      $profile = parent::getInstallProfile();
+    }
+    return $profile;
+  }
+
 }
diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml
index e37cb48d1b554626e3cf8b153d08576c2b106dd2..b9f8fb807e7b72780b9d4ca086eca75256d12a40 100644
--- a/core/modules/locale/locale.services.yml
+++ b/core/modules/locale/locale.services.yml
@@ -1,7 +1,7 @@
 services:
   locale.default.config.storage:
     class: Drupal\locale\LocaleDefaultConfigStorage
-    arguments: ['@config.storage', '@language_manager']
+    arguments: ['@config.storage', '@language_manager', '%install_profile%']
     public: false
   locale.config_manager:
     class: Drupal\locale\LocaleConfigManager
diff --git a/core/modules/locale/src/LocaleDefaultConfigStorage.php b/core/modules/locale/src/LocaleDefaultConfigStorage.php
index 29697a107653571713623a3d2530390a2ca6c314..bcb242b28b1d7478372dfa3785391c5a0bf55166 100644
--- a/core/modules/locale/src/LocaleDefaultConfigStorage.php
+++ b/core/modules/locale/src/LocaleDefaultConfigStorage.php
@@ -57,12 +57,12 @@ class LocaleDefaultConfigStorage {
    * @param \Drupal\language\ConfigurableLanguageManagerInterface $language_manager
    *   The language manager.
    */
-  public function __construct(StorageInterface $config_storage, ConfigurableLanguageManagerInterface $language_manager) {
+  public function __construct(StorageInterface $config_storage, ConfigurableLanguageManagerInterface $language_manager, $install_profile) {
     $this->configStorage = $config_storage;
     $this->languageManager = $language_manager;
 
-    $this->requiredInstallStorage = new ExtensionInstallStorage($this->configStorage);
-    $this->optionalInstallStorage = new ExtensionInstallStorage($this->configStorage, ExtensionInstallStorage::CONFIG_OPTIONAL_DIRECTORY);
+    $this->requiredInstallStorage = new ExtensionInstallStorage($this->configStorage, $install_profile);
+    $this->optionalInstallStorage = new ExtensionInstallStorage($this->configStorage, $install_profile, ExtensionInstallStorage::CONFIG_OPTIONAL_DIRECTORY);
   }
 
   /**
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 0b5792f5fe0eb173cef266e80f243dbe848fdf46..c804218af60d81ea092a8107372cd96409a0e62d 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -232,7 +232,7 @@ protected function setUp() {
     // \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
     // Write directly to active storage to avoid early instantiation of
     // the event dispatcher which can prevent modules from registering events.
-    \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array()));
+    \Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array(), 'profile' => ''));
 
     // Collect and set a fixed module list.
     $class = get_class($this);
diff --git a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
index 15ecf1fe75b17197703a8d9a08f369efd3e2312a..472fdf9e45bc715cd387ea31f7dcc65f3634ee56 100644
--- a/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
+++ b/core/modules/simpletest/src/Tests/KernelTestBaseTest.php
@@ -323,13 +323,13 @@ function testNoThemeByDefault() {
   }
 
   /**
-   * Tests that drupal_get_profile() returns NULL.
+   * Tests that \Drupal::installProfile() returns NULL.
    *
    * As the currently active installation profile is used when installing
    * configuration, for example, this is essential to ensure test isolation.
    */
   public function testDrupalGetProfile() {
-    $this->assertNull(drupal_get_profile());
+    $this->assertNull(\Drupal::installProfile());
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Installer/DistributionProfileExistingSettingsTest.php b/core/modules/system/src/Tests/Installer/DistributionProfileExistingSettingsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fe5d4f1bc33450c465b0f974972645d156e7865
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/DistributionProfileExistingSettingsTest.php
@@ -0,0 +1,132 @@
+<?php
+
+namespace Drupal\system\Tests\Installer;
+
+use Drupal\Component\Serialization\Yaml;
+use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Site\Settings;
+use Drupal\simpletest\InstallerTestBase;
+use Symfony\Component\HttpFoundation\Request;
+
+
+/**
+ * Tests distribution profile support with existing settings.
+ *
+ * @group Installer
+ */
+class DistributionProfileExistingSettingsTest extends InstallerTestBase {
+
+  /**
+   * The distribution profile info.
+   *
+   * @var array
+   */
+  protected $info;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->info = [
+      'type' => 'profile',
+      'core' => \Drupal::CORE_COMPATIBILITY,
+      'name' => 'Distribution profile',
+      'distribution' => [
+        'name' => 'My Distribution',
+        'install' => [
+          'theme' => 'bartik',
+        ],
+      ],
+    ];
+    // File API functions are not available yet.
+    $path = $this->siteDirectory . '/profiles/mydistro';
+    mkdir($path, 0777, TRUE);
+    file_put_contents("$path/mydistro.info.yml", Yaml::encode($this->info));
+
+    // Pre-configure hash salt.
+    // Any string is valid, so simply use the class name of this test.
+    $this->settings['settings']['hash_salt'] = (object) [
+      'value' => __CLASS__,
+      'required' => TRUE,
+    ];
+
+    // Pre-configure database credentials.
+    $connection_info = Database::getConnectionInfo();
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
+
+    $this->settings['databases']['default'] = (object) [
+      'value' => $connection_info,
+      'required' => TRUE,
+    ];
+
+    // Use the kernel to find the site path because the site.path service should
+    // not be available at this point in the install process.
+    $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
+    // Pre-configure config directories.
+    $this->settings['config_directories'] = [
+      CONFIG_SYNC_DIRECTORY => (object) [
+        'value' => $site_path . '/files/config_staging',
+        'required' => TRUE,
+      ],
+    ];
+    mkdir($this->settings['config_directories'][CONFIG_SYNC_DIRECTORY]->value, 0777, TRUE);
+    parent::setUp();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpLanguage() {
+    // Make settings file not writable.
+    $filename = $this->siteDirectory . '/settings.php';
+    // Make the settings file read-only.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod($filename, 0444);
+
+    // Verify that the distribution name appears.
+    $this->assertRaw($this->info['distribution']['name']);
+    // Verify that the requested theme is used.
+    $this->assertRaw($this->info['distribution']['install']['theme']);
+    // Verify that the "Choose profile" step does not appear.
+    $this->assertNoText('profile');
+
+    parent::setUpLanguage();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpProfile() {
+    // This step is skipped, because there is a distribution profile.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpSettings() {
+    // This step should not appear, since settings.php is fully configured
+    // already.
+  }
+
+  /**
+   * Confirms that the installation succeeded.
+   */
+  public function testInstalled() {
+    $this->assertUrl('user/1');
+    $this->assertResponse(200);
+    // Confirm that we are logged-in after installation.
+    $this->assertText($this->rootUser->getUsername());
+
+    // Confirm that Drupal recognizes this distribution as the current profile.
+    $this->assertEqual(\Drupal::installProfile(), 'mydistro');
+    $this->assertNull(Settings::get('install_profile'), 'The install profile has not been written to settings.php.');
+    $this->assertEqual($this->config('core.extension')->get('profile'), 'mydistro', 'The install profile has been written to core.extension configuration.');
+
+    $this->rebuildContainer();
+    $this->pass('Container can be rebuilt even though distribution is not written to settings.php.');
+    $this->assertEqual(\Drupal::installProfile(), 'mydistro');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Installer/DistributionProfileTest.php b/core/modules/system/src/Tests/Installer/DistributionProfileTest.php
index 0bb47acb44efcddff3c9e30e8f20187eb05ecaa5..569fe36212c2c98fa553bf9140039ce5fdaee490 100644
--- a/core/modules/system/src/Tests/Installer/DistributionProfileTest.php
+++ b/core/modules/system/src/Tests/Installer/DistributionProfileTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\system\Tests\Installer;
 
 use Drupal\Core\Serialization\Yaml;
+use Drupal\Core\Site\Settings;
 use Drupal\simpletest\InstallerTestBase;
 
 /**
@@ -68,6 +69,11 @@ public function testInstalled() {
     $this->assertResponse(200);
     // Confirm that we are logged-in after installation.
     $this->assertText($this->rootUser->getUsername());
+
+    // Confirm that Drupal recognizes this distribution as the current profile.
+    $this->assertEqual(\Drupal::installProfile(), 'mydistro');
+    $this->assertEqual(Settings::get('install_profile'), 'mydistro', 'The install profile has been written to settings.php.');
+    $this->assertEqual($this->config('core.extension')->get('profile'), 'mydistro', 'The install profile has been written to core.extension configuration.');
   }
 
 }
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileBrokenTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileBrokenTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d866f0cf834c6118ca46a0008c3f736eb02bbca
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileBrokenTest.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Drupal\system\Tests\Installer;
+
+use Drupal\Core\DrupalKernel;
+use Drupal\simpletest\InstallerTestBase;
+use Drupal\Core\Database\Database;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Tests installer breaks with a profile mismatch and a read-only settings.php.
+ *
+ * @group Installer
+ */
+class InstallerExistingSettingsMismatchProfileBrokenTest extends InstallerTestBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Configures a preexisting settings.php file without an install_profile
+   * setting before invoking the interactive installer.
+   */
+  protected function setUp() {
+    // Pre-configure hash salt.
+    // Any string is valid, so simply use the class name of this test.
+    $this->settings['settings']['hash_salt'] = (object) [
+      'value' => __CLASS__,
+      'required' => TRUE,
+    ];
+
+    // Pre-configure database credentials.
+    $connection_info = Database::getConnectionInfo();
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
+
+    $this->settings['databases']['default'] = (object) [
+      'value' => $connection_info,
+      'required' => TRUE,
+    ];
+
+    // During interactive install we'll change this to a different profile and
+    // this test will ensure that the new value is written to settings.php.
+    $this->settings['settings']['install_profile'] = (object) [
+      'value' => 'minimal',
+      'required' => TRUE,
+    ];
+
+    // Pre-configure config directories.
+    $site_path = DrupalKernel::findSitePath(Request::createFromGlobals());
+    $this->settings['config_directories'] = [
+      CONFIG_SYNC_DIRECTORY => (object) [
+        'value' => $site_path . '/files/config_staging',
+        'required' => TRUE,
+      ],
+    ];
+    mkdir($this->settings['config_directories'][CONFIG_SYNC_DIRECTORY]->value, 0777, TRUE);
+
+    parent::setUp();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function visitInstaller() {
+    // Make settings file not writable. This will break the installer.
+    $filename = $this->siteDirectory . '/settings.php';
+    // Make the settings file read-only.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod($filename, 0444);
+
+    $this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=testing');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpLanguage() {
+    // This step is skipped, because there is a lagcode as a query param.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpProfile() {
+    // This step is skipped, because there is a profile as a query param.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpSettings() {
+    // This step should not appear, since settings.php is fully configured
+    // already.
+  }
+
+  protected function setUpSite() {
+    // This step should not appear, since settings.php could not be written.
+  }
+
+  /**
+   * Verifies that installation did not succeed.
+   */
+  public function testBrokenInstaller() {
+    $this->assertTitle('Install profile mismatch | Drupal');
+    $this->assertText("The selected profile testing does not match the install_profile setting, which is minimal. Cannot write updated setting to {$this->siteDirectory}/settings.php.");
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a179039d0f92b7629a0d0fab54064be08d5e86e7
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsMismatchProfileTest.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Drupal\system\Tests\Installer;
+
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Site\Settings;
+use Drupal\simpletest\InstallerTestBase;
+use Drupal\Core\Database\Database;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Tests the installer with an existing settings file but no install profile.
+ *
+ * @group Installer
+ */
+class InstallerExistingSettingsMismatchProfileTest extends InstallerTestBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Configures a preexisting settings.php file without an install_profile
+   * setting before invoking the interactive installer.
+   */
+  protected function setUp() {
+    // Pre-configure hash salt.
+    // Any string is valid, so simply use the class name of this test.
+    $this->settings['settings']['hash_salt'] = (object) array(
+      'value' => __CLASS__,
+      'required' => TRUE,
+    );
+
+    // Pre-configure database credentials.
+    $connection_info = Database::getConnectionInfo();
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
+
+    $this->settings['databases']['default'] = (object) array(
+      'value' => $connection_info,
+      'required' => TRUE,
+    );
+
+    // During interactive install we'll change this to a different profile and
+    // this test will ensure that the new value is written to settings.php.
+    $this->settings['settings']['install_profile'] = (object) [
+      'value' => 'minimal',
+      'required' => TRUE,
+    ];
+
+    // Pre-configure config directories.
+    $this->settings['config_directories'] = array(
+      CONFIG_SYNC_DIRECTORY => (object) array(
+        'value' => DrupalKernel::findSitePath(Request::createFromGlobals()) . '/files/config_sync',
+        'required' => TRUE,
+      ),
+    );
+    mkdir($this->settings['config_directories'][CONFIG_SYNC_DIRECTORY]->value, 0777, TRUE);
+
+    parent::setUp();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function visitInstaller() {
+    // Provide profile and language in query string to skip these pages.
+    $this->drupalGet($GLOBALS['base_url'] . '/core/install.php?langcode=en&profile=testing');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpLanguage() {
+    // This step is skipped, because there is a lagcode as a query param.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpProfile() {
+    // This step is skipped, because there is a profile as a query param.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpSettings() {
+    // This step should not appear, since settings.php is fully configured
+    // already.
+  }
+
+  /**
+   * Verifies that installation succeeded.
+   */
+  public function testInstaller() {
+    $this->assertUrl('user/1');
+    $this->assertResponse(200);
+    $this->assertEqual('testing', \Drupal::installProfile());
+    $this->assertEqual('testing', Settings::get('install_profile'), 'Profile was correctly changed to testing in Settings.php');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
index 84f861a751491d7823f71d0a14835d73b47b83cf..95173085ff9ab191017950d3fee3272e5ed1172b 100644
--- a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsNoProfileTest.php
@@ -3,7 +3,6 @@
 namespace Drupal\system\Tests\Installer;
 
 use Drupal\Core\DrupalKernel;
-use Drupal\Core\Site\Settings;
 use Drupal\simpletest\InstallerTestBase;
 use Drupal\Core\Database\Database;
 use Symfony\Component\HttpFoundation\Request;
@@ -65,7 +64,7 @@ protected function setUpSettings() {
   public function testInstaller() {
     $this->assertUrl('user/1');
     $this->assertResponse(200);
-    $this->assertEqual('testing', Settings::get('install_profile'));
+    $this->assertEqual('testing', \Drupal::installProfile());
   }
 
 }
diff --git a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php
index 6ecd9fdf34936e1f808e500039285d3cc1ccc5d1..eaca8fa3a4d91d532eae27c8f9f5fd30c80c102c 100644
--- a/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php
+++ b/core/modules/system/src/Tests/Installer/InstallerExistingSettingsTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\system\Tests\Installer;
 
+use Drupal\Core\Site\Settings;
 use Drupal\simpletest\InstallerTestBase;
 use Drupal\Core\Database\Database;
 use Drupal\Core\DrupalKernel;
@@ -74,7 +75,8 @@ protected function setUpSettings() {
   public function testInstaller() {
     $this->assertUrl('user/1');
     $this->assertResponse(200);
-    $this->assertEqual('testing', drupal_get_profile(), 'Profile was changed from minimal to testing during interactive install.');
+    $this->assertEqual('testing', \Drupal::installProfile(), 'Profile was changed from minimal to testing during interactive install.');
+    $this->assertEqual('testing', Settings::get('install_profile'), 'Profile was correctly changed to testing in Settings.php');
   }
 
 }
diff --git a/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php b/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..171a0afc317edf9280e4e1b6d5877dd6a7679eff
--- /dev/null
+++ b/core/modules/system/src/Tests/Installer/MultipleDistributionsProfileTest.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\system\Tests\Installer;
+
+use Drupal\Component\Serialization\Yaml;
+use Drupal\Core\Site\Settings;
+use Drupal\simpletest\InstallerTestBase;
+
+/**
+ * Tests multiple distribution profile support.
+ *
+ * @group Installer
+ */
+class MultipleDistributionsProfileTest extends InstallerTestBase {
+
+  /**
+   * The distribution profile info.
+   *
+   * @var array
+   */
+  protected $info;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    // Create two distributions.
+    foreach (['distribution_one', 'distribution_two'] as $name) {
+      $info = [
+        'type' => 'profile',
+        'core' => \Drupal::CORE_COMPATIBILITY,
+        'name' => $name . ' profile',
+        'distribution' => [
+          'name' => $name,
+          'install' => [
+            'theme' => 'bartik',
+          ],
+        ],
+      ];
+      // File API functions are not available yet.
+      $path = $this->siteDirectory . '/profiles/' . $name;
+      mkdir($path, 0777, TRUE);
+      file_put_contents("$path/$name.info.yml", Yaml::encode($info));
+    }
+    // Install the first distribution.
+    $this->profile = 'distribution_one';
+
+    parent::setUp();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpLanguage() {
+    // Verify that the distribution name appears.
+    $this->assertRaw('distribution_one');
+    // Verify that the requested theme is used.
+    $this->assertRaw('bartik');
+    // Verify that the "Choose profile" step does not appear.
+    $this->assertNoText('profile');
+
+    parent::setUpLanguage();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpProfile() {
+    // This step is skipped, because there is a distribution profile.
+  }
+
+  /**
+   * Confirms that the installation succeeded.
+   */
+  public function testInstalled() {
+    $this->assertUrl('user/1');
+    $this->assertResponse(200);
+    // Confirm that we are logged-in after installation.
+    $this->assertText($this->rootUser->getUsername());
+
+    // Confirm that Drupal recognizes this distribution as the current profile.
+    $this->assertEqual(\Drupal::installProfile(), 'distribution_one');
+    $this->assertEqual(Settings::get('install_profile'), 'distribution_one', 'The install profile has been written to settings.php.');
+    $this->assertEqual($this->config('core.extension')->get('profile'), 'distribution_one', 'The install profile has been written to core.extension configuration.');
+
+    $this->rebuildContainer();
+    $this->pass('Container can be rebuilt as distribution is written to configuration.');
+    $this->assertEqual(\Drupal::installProfile(), 'distribution_one');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Update/InstallProfileSystemInstall8300Test.php b/core/modules/system/src/Tests/Update/InstallProfileSystemInstall8300Test.php
new file mode 100644
index 0000000000000000000000000000000000000000..f0eabd62ecfb195ef2baa023096c4d69cbcad96e
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/InstallProfileSystemInstall8300Test.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\system\Tests\Update;
+
+use Drupal\Core\Site\Settings;
+
+/**
+ * Tests system_update_8300().
+ *
+ * @group Update
+ */
+class InstallProfileSystemInstall8300Test extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../tests/fixtures/update/drupal-8.bare.standard.php.gz',
+    ];
+  }
+
+  /**
+   * Ensures that the system_update_8300() runs as expected.
+   */
+  public function testUpdate() {
+    // Ensure the BC layers work and settings.php and configuration is in the
+    // expected state before updating.
+    $this->assertEqual('standard', \Drupal::installProfile());
+    $this->assertEqual('standard', Settings::get('install_profile'), 'The install profile has not been written to settings.php.');
+    $this->assertFalse($this->config('core.extension')->get('profile'), 'The install profile is not present in core.extension configuration.');
+
+    $this->runUpdates();
+    // Confirm that Drupal recognizes this distribution as the current profile.
+    $this->assertEqual('standard', \Drupal::installProfile());
+    $this->assertEqual('standard', Settings::get('install_profile'), 'The install profile has not been written to settings.php.');
+    $this->assertEqual('standard', $this->config('core.extension')->get('profile'), 'The install profile has been written to core.extension configuration.');
+  }
+
+}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 6dc2663d1b41a365a14f90ad29340a9995a89eb5..df284b608a099245ce15f6b48932322d8f94b886 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1794,3 +1794,21 @@ function system_update_8203() {
     ->set('logging', 1)
     ->save(TRUE);
 }
+
+/**
+ * @addtogroup updates-8.3.x
+ * @{
+ */
+
+/**
+ * Add install profile to core.extension configuration.
+ */
+function system_update_8300() {
+  \Drupal::configFactory()->getEditable('core.extension')
+    ->set('profile', \Drupal::installProfile())
+    ->save();
+}
+
+/**
+ * @} End of "addtogroup updates-8.3.x".
+ */
diff --git a/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php b/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php
index 6cdc4b39ead2529e813bec1f10b9fc38dce4339d..e2108b3cc09cf6a812972fd5d9c5f7e50d9de9a7 100644
--- a/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Bootstrap/GetFilenameTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\KernelTests\Core\Bootstrap;
 
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\KernelTests\KernelTestBase;
 
 /**
@@ -16,15 +17,19 @@ class GetFilenameTest extends KernelTestBase {
    */
   public static $modules = ['system'];
 
+  /**
+   * {@inheritdoc}
+   */
+  public function register(ContainerBuilder $container) {
+    parent::register($container);
+    // Use the testing install profile.
+    $container->setParameter('install_profile', 'testing');
+  }
+
   /**
    * Tests that drupal_get_filename() works when the file is not in database.
    */
   function testDrupalGetFilename() {
-    // drupal_get_profile() is using obtaining the profile from state if the
-    // install_state global is not set.
-    global $install_state;
-    $install_state['parameters']['profile'] = 'testing';
-
     // Rebuild system.module.files state data.
     // @todo Remove as part of https://www.drupal.org/node/2186491
     drupal_static_reset('system_rebuild_module_data');
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
index 945e994e3ba7665866a25228193ad9a80e6151c8..bde5644ec6afed9002729ae9440c8fbec9e0b688 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterTest.php
@@ -668,6 +668,34 @@ public function testInstallProfile() {
     }
   }
 
+  /**
+   * Tests install profile validation during configuration import.
+   *
+   * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber
+   */
+  public function testInstallProfileMisMatch() {
+    $sync = $this->container->get('config.storage.sync');
+
+    $extensions = $sync->read('core.extension');
+    // Change the install profile.
+    $extensions['profile'] = 'this_will_not_work';
+    $sync->write('core.extension', $extensions);
+
+    try {
+      $this->configImporter->reset()->import();
+      $this->fail('ConfigImporterException not thrown; an invalid import was not stopped due to missing dependencies.');
+    }
+    catch (ConfigImporterException $e) {
+      $this->assertEqual($e->getMessage(), 'There were errors validating the config synchronization.');
+      $error_log = $this->configImporter->getErrors();
+      // Install profiles can not be changed. Note that KernelTestBase currently
+      // does not use an install profile. This situation should be impossible
+      // to get in but site's can removed the install profile setting from
+      // settings.php so the test is valid.
+      $this->assertEqual(['Cannot change the install profile from <em class="placeholder">this_will_not_work</em> to <em class="placeholder"></em> once Drupal is installed.'], $error_log);
+    }
+  }
+
   /**
    * Tests config_get_config_directory().
    */
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index f10cb14d099f92789fd6c7d93c766b1ceb5a500c..9f6b975d8e8401833fb568290fed5ca93b6a94ac 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -392,6 +392,7 @@ private function bootKernel() {
     $this->container->get('config.storage')->write('core.extension', array(
       'module' => array_fill_keys($modules, 0),
       'theme' => array(),
+      'profile' => '',
     ));
 
     $settings = Settings::getAll();
diff --git a/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
index 879e7a00222182bcf718700a2d72019612a45019..56f913ae250eba8f2e30b0e6bea50b34ad9ba8bf 100644
--- a/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/DefaultConfigTest.php
@@ -27,6 +27,7 @@ public function testConfigIsEmpty() {
     $expected = array(
       'module' => array(),
       'theme' => array(),
+      'profile' => '',
     );
     $this->assertEquals($expected, $config);
   }
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 9ee73605217fb2b96068bb5b913772e21f6cdd2b..25d498e6f1d711cadb7f92d25668598f520a713f 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -270,6 +270,11 @@
  * by the user.
  *
  * @see install_select_profile()
+ *
+ * @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. The
+ *   install profile is written to the core.extension configuration. If a
+ *   service requires the install profile use the 'install_profile' container
+ *   parameter. Functional code can use \Drupal::installProfile().
  */
 # $settings['install_profile'] = '';