diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php
index cd2b958..3ad21e9 100644
--- a/core/lib/Drupal/Core/Access/AccessResult.php
+++ b/core/lib/Drupal/Core/Access/AccessResult.php
@@ -319,10 +319,12 @@ public function cachePerUser() {
    *   The entity whose cache tag to set on the access result.
    *
    * @return $this
+   *
+   * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
+   *   this::addCacheableDependency() instead.
    */
   public function cacheUntilEntityChanges(EntityInterface $entity) {
-    $this->addCacheTags($entity->getCacheTags());
-    return $this;
+    return $this->addCacheableDependency($entity);
   }
 
   /**
@@ -332,9 +334,47 @@ public function cacheUntilEntityChanges(EntityInterface $entity) {
    *   The configuration object whose cache tag to set on the access result.
    *
    * @return $this
+   *
+   * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Use
+   *   this::addCacheableDependency() instead.
    */
   public function cacheUntilConfigurationChanges(ConfigBase $configuration) {
-    $this->addCacheTags($configuration->getCacheTags());
+    return $this->addCacheableDependency($entity);
+  }
+
+  /**
+   * Adds a dependency on an object: merges its cacheability metadata.
+   *
+   * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $other_object
+   *   The dependency. If the object implements CacheableDependencyInterface,
+   *   then its cacheability metadata will be used. Otherwise, the passed in
+   *   object must be assumed to be uncacheable, so max-age 0 is set.
+   *
+   * @return $this
+   */
+  public function addCacheableDependency(CacheableDependencyInterface $other_object) {
+    // This is called many times per request, so avoid merging unless absolutely
+    // necessary.
+    if (empty($this->contexts)) {
+      $this->contexts = $other_object->getCacheContexts();
+    }
+    elseif ($contexts = $other_object->getCacheContexts()) {
+      $this->contexts = Cache::mergeContexts($this->contexts, $contexts);
+    }
+
+    if (empty($this->tags)) {
+      $this->tags = $other_object->getCacheTags();
+    }
+    elseif ($tags = $other_object->getCacheTags()) {
+      $this->tags = Cache::mergeTags($this->tags, $tags);
+    }
+
+    if ($this->maxAge === Cache::PERMANENT) {
+      $this->maxAge = $other_object->getCacheMaxAge();
+    }
+    elseif (($max_age = $other_object->getCacheMaxAge()) && $max_age !== Cache::PERMANENT) {
+      $this->maxAge = Cache::mergeMaxAges($this->maxAge, $max_age);
+    }
     return $this;
   }
 
diff --git a/core/lib/Drupal/Core/Config/ConfigBase.php b/core/lib/Drupal/Core/Config/ConfigBase.php
index eee76a2..1ffe7c0 100644
--- a/core/lib/Drupal/Core/Config/ConfigBase.php
+++ b/core/lib/Drupal/Core/Config/ConfigBase.php
@@ -10,7 +10,8 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
 use \Drupal\Core\DependencyInjection\DependencySerializationTrait;
 
 /**
@@ -28,8 +29,9 @@
  * @see \Drupal\Core\Config\Config
  * @see \Drupal\Core\Theme\ThemeSettings
  */
-abstract class ConfigBase implements CacheableDependencyInterface {
+abstract class ConfigBase implements RefinableCacheableDependencyInterface {
   use DependencySerializationTrait;
+  use RefinableCacheableDependencyTrait;
 
   /**
    * The name of the configuration object.
@@ -269,21 +271,21 @@ public function merge(array $data_to_merge) {
    * {@inheritdoc}
    */
   public function getCacheContexts() {
-    return [];
+    return $this->cacheContexts;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getCacheTags() {
-    return ['config:' . $this->name];
+    return Cache::mergeTags(['config:' . $this->name], $this->cacheTags);
   }
 
   /**
    * {@inheritdoc}
    */
   public function getCacheMaxAge() {
-    return Cache::PERMANENT;
+    return $this->cacheMaxAge;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php
index cdfd951..cbc19c7 100644
--- a/core/lib/Drupal/Core/Config/ConfigFactory.php
+++ b/core/lib/Drupal/Core/Config/ConfigFactory.php
@@ -126,6 +126,10 @@ protected function doGet($name, $immutable = TRUE) {
           $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
         }
       }
+
+      // Propagate cache contexts to the config object.
+      $this->propagateCacheableDependencyOverrides($cache_key, $name);
+
       return $this->cache[$cache_key];
     }
   }
@@ -183,6 +187,10 @@ protected function doLoadMultiple(array $names, $immutable = TRUE) {
             $this->cache[$cache_key]->setSettingsOverride($GLOBALS['config'][$name]);
           }
         }
+
+        // Propagate cacheable dependencies to the config object.
+        $this->propagateCacheableDependencyOverrides($cache_key, $name);
+
         $list[$name] = $this->cache[$cache_key];
       }
     }
@@ -210,6 +218,23 @@ protected function loadOverrides(array $names) {
   }
 
   /**
+   * Propagates overridden cacheable dependencies to cached config objects.
+   *
+   * @param string $cache_key
+   *   The key of the cached config object to update.
+   * @param string $name
+   *   The name of the configuration object to construct.
+   */
+  protected function propagateCacheableDependencyOverrides($cache_key, $name) {
+    foreach ($this->configFactoryOverrides as $override) {
+      $this->cache[$cache_key]
+        ->addCacheContexts($override->getCacheContexts($name))
+        ->addCacheTags($override->getCacheTags($name))
+        ->mergeCacheMaxAge($override->getCacheMaxAge($name));
+    }
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function reset($name = NULL) {
diff --git a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php
index 56dbc19..15723ec 100644
--- a/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php
+++ b/core/lib/Drupal/Core/Config/ConfigFactoryOverrideInterface.php
@@ -58,4 +58,51 @@ public function getCacheSuffix();
    */
   public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION);
 
+  /**
+   * The cache contexts associated with this config factory override.
+   *
+   * These identify a specific variation/representation of the object.
+   *
+   * Cache contexts are tokens: placeholders that are converted to cache keys by
+   * the @cache_contexts_manager service. The replacement value depends on the
+   * request context (the current URL, language, and so on). They're converted
+   * before storing or retrieving an object in cache.
+   *
+   * @param string $name
+   *   The name of the configuration object that is being constructed.
+   *
+   * @return string[]
+   *   An array of cache context tokens, used to generate a cache ID.
+   *
+   * @see \Drupal\Core\Cache\Context\CacheContextsManager::convertTokensToKeys()
+   */
+  public function getCacheContexts($name);
+
+  /**
+   * The cache tags associated with this config factory override.
+   *
+   * When this object is modified, these cache tags will be invalidated.
+   *
+   * Since a cache tag is already associated with every config object this
+   * should only be used if multiple config objects are being overridden.
+   *
+   * @param string $name
+   *   The name of the configuration object that is being constructed.
+   *
+   * @return string[]
+   *  A set of cache tags.
+   */
+  public function getCacheTags($name);
+
+  /**
+   * The maximum age for which this object may be cached.
+   *
+   * @param string $name
+   *   The name of the configuration object that is being constructed.
+   *
+   * @return int
+   *   The maximum time in seconds that this object may be cached.
+   */
+  public function getCacheMaxAge($name);
+
 }
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
index 59776aa..6153900 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
@@ -184,11 +184,35 @@ protected function doLoadMultiple(array $ids = NULL) {
     }
 
     // Load all of the configuration entities.
-    $records = array();
+    /** @var Config[] $configs */
+    $configs = [];
+    $records = [];
     foreach ($this->configFactory->loadMultiple($names) as $config) {
-      $records[$config->get($this->idKey)] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
+      $id = $config->get($this->idKey);
+      $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get();
+      $configs[$id] = $config;
     }
-    return $this->mapFromStorageRecords($records);
+    $entities = $this->mapFromStorageRecords($records, $configs);
+
+    // Add cacheability metadata to the entities.
+    foreach ($entities as $id => $entity) {
+      $entity->addCacheContexts($configs[$id]->getCacheContexts());
+      $entity->mergeCacheMaxAge($configs[$id]->getCacheMaxAge());
+
+      // Remove the self-referring cache tag that is present on Config objects
+      // before setting it. A ConfigEntity doesn't need this since it will be
+      // dynamically generated in EntityInterface::getCacheTagsToInvalidate().
+      // The cache tags are merged during rendering, and having fewer tags
+      // available improves performance.
+      $cache_tags = $configs[$id]->getCacheTags();
+      $key = array_search('config:' . $configs[$id]->getName(), $cache_tags);
+      if ($key !== FALSE) {
+        unset($cache_tags[$key]);
+      }
+      $entity->addCacheTags($cache_tags);
+    }
+
+    return $entities;
   }
 
   /**
diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php
index a68b6eb..8a9cb35 100644
--- a/core/modules/block/src/BlockViewBuilder.php
+++ b/core/modules/block/src/BlockViewBuilder.php
@@ -67,7 +67,10 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
         '#id' => $entity->id(),
         '#cache' => [
           'keys' => ['entity_view', 'block', $entity->id()],
-          'contexts' => $plugin->getCacheContexts(),
+          'contexts' => Cache::mergeContexts(
+            $entity->getCacheContexts(),
+            $plugin->getCacheContexts()
+          ),
           'tags' => Cache::mergeTags(
             $this->getCacheTags(), // Block view builder cache tag.
             $entity->getCacheTags(), // Block entity cache tag.
diff --git a/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideIntegrationTest.php b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideIntegrationTest.php
new file mode 100644
index 0000000..22d0b11
--- /dev/null
+++ b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideIntegrationTest.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Tests\CacheabilityMetadataConfigOverrideIntegrationTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests if configuration overrides correctly affect cacheability metadata.
+ *
+ * @group config
+ */
+class CacheabilityMetadataConfigOverrideIntegrationTest extends WebTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'block_test',
+    'config_override_integration_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // @todo If our block does not contain any content then the cache context
+    //   is not bubbling up and the test fails. Remove this line once the cache
+    //   contexts are properly set. See https://www.drupal.org/node/2529980.
+    \Drupal::state()->set('block_test.content', 'Needs to have some content');
+
+    $this->drupalLogin($this->drupalCreateUser());
+  }
+
+  /**
+   * Tests if config overrides correctly set cacheability metadata.
+   */
+  public function testConfigOverride() {
+    // Check the default (disabled) state of the cache context. The block label
+    // should not be overridden.
+    $this->drupalGet('<front>');
+    $this->assertNoText('Overridden block label');
+
+    // Both the cache context and tag should be present.
+    $this->assertCacheContext('config_override_integration_test');
+    $this->assertCacheTag('config_override_integration_test_tag');
+
+    // Flip the state of the cache context. The block label should now be
+    // overridden.
+    \Drupal::state()->set('config_override_integration_test.enabled', TRUE);
+    $this->drupalGet('<front>');
+    $this->assertText('Overridden block label');
+
+    // Both the cache context and tag should still be present.
+    $this->assertCacheContext('config_override_integration_test');
+    $this->assertCacheTag('config_override_integration_test_tag');
+  }
+
+}
diff --git a/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php
new file mode 100644
index 0000000..cc6e024
--- /dev/null
+++ b/core/modules/config/src/Tests/CacheabilityMetadataConfigOverrideTest.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config\Tests\CacheabilityMetadataConfigOverrideTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\config_override_test\Cache\PirateDayCacheContext;
+use Drupal\simpletest\KernelTestBase;
+
+/**
+ * Tests if configuration overrides correctly affect cacheability metadata.
+ *
+ * @group config
+ */
+class CacheabilityMetadataConfigOverrideTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'block',
+    'block_content',
+    'config',
+    'config_override_test',
+    'system',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('block_content');
+    $this->installConfig(['config_override_test']);
+  }
+
+  /**
+   * Tests if config overrides correctly set cacheability metadata.
+   */
+  public function testConfigOverride() {
+    // It's pirate day today!
+    $GLOBALS['it_is_pirate_day'] = TRUE;
+
+    $config_factory = $this->container->get('config.factory');
+    $config = $config_factory->get('system.theme');
+
+    // Check that we are using the Pirate theme.
+    $theme = $config->get('default');
+    $this->assertEqual('pirate', $theme);
+
+    // Check that the cacheability metadata is correct.
+    $this->assertEqual(['pirate_day'], $config->getCacheContexts());
+    $this->assertEqual(['config:system.theme', 'pirate-day-tag'], $config->getCacheTags());
+    $this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $config->getCacheMaxAge());
+  }
+
+  /**
+   * Tests if config overrides set cacheability metadata on config entities.
+   */
+  public function testConfigEntityOverride() {
+    // It's pirate day today!
+    $GLOBALS['it_is_pirate_day'] = TRUE;
+
+    // Load the User login block and check that its cacheability metadata is
+    // overridden correctly. This verifies that the metadata is correctly
+    // applied to config entities.
+    /** @var EntityManagerInterface $entity_manager */
+    $entity_manager = $this->container->get('entity.manager');
+    $block = $entity_manager->getStorage('block')->load('call_to_action');
+
+    // Check that our call to action message is appealing to filibusters.
+    $this->assertEqual($block->label(), 'Draw yer cutlasses!');
+
+    // Check that the cacheability metadata is correct.
+    $this->assertEqual(['pirate_day'], $block->getCacheContexts());
+    $this->assertEqual(['config:block.block.call_to_action', 'pirate-day-tag'], $block->getCacheTags());
+    $this->assertEqual(PirateDayCacheContext::PIRATE_DAY_MAX_AGE, $block->getCacheMaxAge());
+  }
+
+}
diff --git a/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php b/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php
index 2867c7d..93837cc 100644
--- a/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php
+++ b/core/modules/config/tests/config_entity_static_cache_test/src/ConfigOverrider.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\config_entity_static_cache_test;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigFactoryOverrideInterface;
 use Drupal\Core\Config\StorageInterface;
 
@@ -40,4 +41,25 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL
     return NULL;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge($name) {
+    return Cache::PERMANENT;
+  }
+
 }
diff --git a/core/modules/config/tests/config_override_integration_test/config/install/block.block.config_override_test.yml b/core/modules/config/tests/config_override_integration_test/config/install/block.block.config_override_test.yml
new file mode 100644
index 0000000..be0616f
--- /dev/null
+++ b/core/modules/config/tests/config_override_integration_test/config/install/block.block.config_override_test.yml
@@ -0,0 +1,24 @@
+id: config_override_test
+theme: classy
+weight: 0
+status: true
+langcode: en
+region: content
+plugin: test_cache
+settings:
+  label: 'Test HTML block'
+  provider: block_test
+  label_display: visible
+  status: true
+  info: ''
+  view_mode: default
+dependencies:
+  module:
+    - block_test
+  theme:
+    - classy
+visibility:
+  request_path:
+    id: request_path
+    pages: ''
+    negate: false
diff --git a/core/modules/config/tests/config_override_integration_test/config_override_integration_test.info.yml b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.info.yml
new file mode 100644
index 0000000..22b1e94
--- /dev/null
+++ b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.info.yml
@@ -0,0 +1,9 @@
+name: 'Configuration override integration test'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
+
+dependencies:
+  - block
+  - block_test
diff --git a/core/modules/config/tests/config_override_integration_test/config_override_integration_test.services.yml b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.services.yml
new file mode 100644
index 0000000..147874e
--- /dev/null
+++ b/core/modules/config/tests/config_override_integration_test/config_override_integration_test.services.yml
@@ -0,0 +1,9 @@
+services:
+  cache_context.config_override_integration_test:
+    class: Drupal\config_override_integration_test\Cache\ConfigOverrideIntegrationTestCacheContext
+    tags:
+      - { name: cache.context }
+  config_override_integration_test.config_override:
+    class: Drupal\config_override_integration_test\CacheabilityMetadataConfigOverride
+    tags:
+      - { name: config.factory.override }
diff --git a/core/modules/config/tests/config_override_integration_test/src/Cache/ConfigOverrideIntegrationTestCacheContext.php b/core/modules/config/tests/config_override_integration_test/src/Cache/ConfigOverrideIntegrationTestCacheContext.php
new file mode 100644
index 0000000..31ec3d3
--- /dev/null
+++ b/core/modules/config/tests/config_override_integration_test/src/Cache/ConfigOverrideIntegrationTestCacheContext.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_override_integration_test\Cache\ConfigOverrideIntegrationTestCacheContext.
+ */
+
+namespace Drupal\config_override_integration_test\Cache;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\Context\CacheContextInterface;
+
+/**
+ * A cache context service intended for the config override integration test.
+ *
+ * Cache context ID: 'config_override_integration_test'.
+ */
+class ConfigOverrideIntegrationTestCacheContext implements CacheContextInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Config override integration test');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    // Default to the 'disabled' state.
+    $state = \Drupal::state()->get('config_override_integration_test.enabled', FALSE) ? 'yes' : 'no';
+    return 'config_override_integration_test.' . $state;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return new CacheableMetadata();
+  }
+
+}
diff --git a/core/modules/config/tests/config_override_integration_test/src/CacheabilityMetadataConfigOverride.php b/core/modules/config/tests/config_override_integration_test/src/CacheabilityMetadataConfigOverride.php
new file mode 100644
index 0000000..c04089f
--- /dev/null
+++ b/core/modules/config/tests/config_override_integration_test/src/CacheabilityMetadataConfigOverride.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_override_integration_test\CacheabilityMetadataConfigOverride.
+ */
+
+namespace Drupal\config_override_integration_test;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Config\ConfigFactoryOverrideInterface;
+use Drupal\Core\Config\StorageInterface;
+
+/**
+ * Test implementation of a config override that provides cacheability metadata.
+ */
+class CacheabilityMetadataConfigOverride implements ConfigFactoryOverrideInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadOverrides($names) {
+    $overrides = [];
+
+    // Override the test block depending on the state set in the test.
+    $state = \Drupal::state()->get('config_override_integration_test.enabled', FALSE);
+    if (in_array('block.block.config_override_test', $names) && $state !== FALSE) {
+      $overrides = $overrides + [
+        'block.block.config_override_test' => [
+          'settings' => ['label' => 'Overridden block label'],
+        ],
+      ];
+    }
+
+    return $overrides;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheSuffix() {
+    return 'config_override_integration_test';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts($name) {
+    if ($name === 'block.block.config_override_test') {
+      return ['config_override_integration_test'];
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags($name) {
+    if ($name === 'block.block.config_override_test') {
+      return ['config_override_integration_test_tag'];
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge($name) {
+    return Cache::PERMANENT;
+  }
+
+}
diff --git a/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml
new file mode 100644
index 0000000..8951c0d
--- /dev/null
+++ b/core/modules/config/tests/config_override_test/config/install/block.block.call_to_action.yml
@@ -0,0 +1,26 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - block_content
+  theme:
+    - classy
+id: call_to_action
+theme: classy
+region: content
+weight: null
+provider: null
+plugin: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653'
+settings:
+  id: 'block_content:d7c9d8ba-663f-41b4-8756-86bc55c44653'
+  label: 'Shop for cheap now!'
+  provider: block_content
+  label_display: visible
+  status: true
+  info: ''
+  view_mode: default
+visibility:
+  request_path:
+    id: request_path
+    pages: ''
+    negate: false
diff --git a/core/modules/config/tests/config_override_test/config_override_test.info.yml b/core/modules/config/tests/config_override_test/config_override_test.info.yml
index f1f1109..051729d 100644
--- a/core/modules/config/tests/config_override_test/config_override_test.info.yml
+++ b/core/modules/config/tests/config_override_test/config_override_test.info.yml
@@ -3,3 +3,7 @@ type: module
 package: Testing
 version: VERSION
 core: 8.x
+
+dependencies:
+  - block
+  - block_content
diff --git a/core/modules/config/tests/config_override_test/config_override_test.services.yml b/core/modules/config/tests/config_override_test/config_override_test.services.yml
index c3fae64..1a07412 100644
--- a/core/modules/config/tests/config_override_test/config_override_test.services.yml
+++ b/core/modules/config/tests/config_override_test/config_override_test.services.yml
@@ -1,4 +1,8 @@
 services:
+  cache_context.pirate_day:
+    class: Drupal\config_override_test\Cache\PirateDayCacheContext
+    tags:
+      - { name: cache.context }
   config_override_test.overrider:
     class: Drupal\config_override_test\ConfigOverrider
     tags:
@@ -7,3 +11,7 @@ services:
     class: Drupal\config_override_test\ConfigOverriderLowPriority
     tags:
       - { name: config.factory.override, priority: -100 }
+  config_override_test.pirate_day_cacheability_metadata_override:
+    class: Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride
+    tags:
+      - { name: config.factory.override }
diff --git a/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php
new file mode 100644
index 0000000..7ef6f2d
--- /dev/null
+++ b/core/modules/config/tests/config_override_test/src/Cache/PirateDayCacheContext.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_override_test\Cache\PirateDayCacheContext.
+ */
+
+namespace Drupal\config_override_test\Cache;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\Context\CacheContextInterface;
+
+/**
+ * Defines the PirateDayCacheContext service that allows to cache the booty.
+ *
+ * Cache context ID: 'pirate_day'.
+ */
+class PirateDayCacheContext implements CacheContextInterface {
+
+  /**
+   * The length of Pirate Day. It lasts 24 hours.
+   *
+   * This is a simplified test implementation. In a real life Pirate Day module
+   * this data wouldn't be defined in a constant, but calculated in a static
+   * method. If it were Pirate Day it should return the number of seconds until
+   * midnight, and on all other days it should return the number of seconds
+   * until the start of the next Pirate Day.
+   */
+  const PIRATE_DAY_MAX_AGE = 86400;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getLabel() {
+    return t('Pirate day');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContext() {
+    $is_pirate_day = static::isPirateDay() ? 'yarr' : 'nay';
+    return "pirate_day." . $is_pirate_day;
+  }
+
+  /**
+   * Returns whether or not it is Pirate Day.
+   *
+   * To ease testing this is determined with a global variable rather than using
+   * the traditional compass and sextant.
+   *
+   * @return bool
+   *   Returns TRUE if it is Pirate Day today.
+   */
+  public static function isPirateDay() {
+    return !empty($GLOBALS['it_is_pirate_day']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheableMetadata() {
+    return new CacheableMetadata();
+  }
+}
diff --git a/core/modules/config/tests/config_override_test/src/ConfigOverrider.php b/core/modules/config/tests/config_override_test/src/ConfigOverrider.php
index 8eb3836..a06cb2e 100644
--- a/core/modules/config/tests/config_override_test/src/ConfigOverrider.php
+++ b/core/modules/config/tests/config_override_test/src/ConfigOverrider.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\config_override_test;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigFactoryOverrideInterface;
 
 /**
@@ -44,5 +45,26 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL
     return NULL;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge($name) {
+    return Cache::PERMANENT;
+  }
+
 }
 
diff --git a/core/modules/config/tests/config_override_test/src/ConfigOverriderLowPriority.php b/core/modules/config/tests/config_override_test/src/ConfigOverriderLowPriority.php
index 6b11e0a..ca4f5bf 100644
--- a/core/modules/config/tests/config_override_test/src/ConfigOverriderLowPriority.php
+++ b/core/modules/config/tests/config_override_test/src/ConfigOverriderLowPriority.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\config_override_test;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigFactoryOverrideInterface;
 use Drupal\Core\Config\StorageInterface;
 
@@ -49,5 +50,25 @@ public function createConfigObject($name, $collection = StorageInterface::DEFAUL
     return NULL;
   }
 
-}
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge($name) {
+    return Cache::PERMANENT;
+  }
 
+}
diff --git a/core/modules/config/tests/config_override_test/src/PirateDayCacheabilityMetadataConfigOverride.php b/core/modules/config/tests/config_override_test/src/PirateDayCacheabilityMetadataConfigOverride.php
new file mode 100644
index 0000000..2cb421d
--- /dev/null
+++ b/core/modules/config/tests/config_override_test/src/PirateDayCacheabilityMetadataConfigOverride.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\config_override_test\PirateDayCacheabilityMetadataConfigOverride.
+ */
+
+namespace Drupal\config_override_test;
+
+use Drupal\config_override_test\Cache\PirateDayCacheContext;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Config\ConfigFactoryOverrideInterface;
+use Drupal\Core\Config\StorageInterface;
+
+/**
+ * Test implementation of a config override that provides cacheability metadata.
+ */
+class PirateDayCacheabilityMetadataConfigOverride implements ConfigFactoryOverrideInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadOverrides($names) {
+    $overrides = [];
+
+    // Override the theme and the 'call_to_action' block on Pirate Day.
+    if (PirateDayCacheContext::isPirateDay()) {
+      if (in_array('system.theme', $names)) {
+        $overrides = $overrides + ['system.theme' => ['default' => 'pirate']];
+      }
+      if (in_array('block.block.call_to_action', $names)) {
+        $overrides = $overrides + [
+          'block.block.call_to_action' => [
+            'settings' => ['label' => 'Draw yer cutlasses!'],
+          ],
+        ];
+      }
+    }
+
+    return $overrides;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheSuffix() {
+    return 'PirateDayConfigOverrider';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts($name) {
+    if ($this->isCacheabilityMetadataApplicable($name)) {
+      return ['pirate_day'];
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags($name) {
+    if ($this->isCacheabilityMetadataApplicable($name)) {
+      return ['pirate-day-tag'];
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge($name) {
+    if ($this->isCacheabilityMetadataApplicable($name)) {
+      return PirateDayCacheContext::PIRATE_DAY_MAX_AGE;
+    }
+    return Cache::PERMANENT;
+  }
+
+  /**
+   * Returns whether or not our overrides are potentially applicable.
+   *
+   * @param string $name
+   *   The name of the config object that is being constructed.
+   *
+   * @return bool
+   *   TRUE if the merchant ship will be boarded. FALSE if we drink rum instead.
+   */
+  protected function isCacheabilityMetadataApplicable($name) {
+    return in_array($name, ['system.theme', 'block.block.call_to_action']);
+  }
+
+}
diff --git a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php
index 3d0a67b..4015573 100644
--- a/core/modules/language/src/Config/LanguageConfigFactoryOverride.php
+++ b/core/modules/language/src/Config/LanguageConfigFactoryOverride.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\language\Config;
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\ConfigCollectionInfo;
 use Drupal\Core\Config\ConfigCrudEvent;
 use Drupal\Core\Config\ConfigFactoryOverrideBase;
@@ -222,4 +223,28 @@ public function onConfigDelete(ConfigCrudEvent $event) {
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts($name) {
+    if ($this->language) {
+      return ['languages:language_interface'];
+    }
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags($name) {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge($name) {
+    return Cache::PERMANENT;
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
index c4413eb..7e0564f 100644
--- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Config\Entity {
 
+use Drupal\Core\Cache\Cache;
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Language\Language;
@@ -104,6 +105,13 @@ class ConfigEntityStorageTest extends UnitTestCase {
   protected $configManager;
 
   /**
+   * The cache contexts manager.
+   *
+   * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cacheContextsManager;
+
+  /**
    * {@inheritdoc}
    *
    * @covers ::__construct
@@ -170,12 +178,17 @@ protected function setUp() {
 
     $this->configManager = $this->getMock('Drupal\Core\Config\ConfigManagerInterface');
 
+    $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
     $container = new ContainerBuilder();
     $container->set('entity.manager', $this->entityManager);
     $container->set('config.typed', $this->typedConfigManager);
     $container->set('cache_tags.invalidator', $this->cacheTagsInvalidator);
     $container->set('config.manager', $this->configManager);
     $container->set('language_manager', $this->languageManager);
+    $container->set('cache_contexts_manager', $this->cacheContextsManager);
     \Drupal::setContainer($container);
 
   }
@@ -611,6 +624,18 @@ public function testSaveChangedUuid() {
         array('', array('id' => 'foo')),
         array('id', 'foo'),
       )));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheContexts')
+      ->will($this->returnValue([]));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheTags')
+      ->will($this->returnValue(['config:foo']));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheMaxAge')
+      ->will($this->returnValue(Cache::PERMANENT));
+    $config_object->expects($this->exactly(1))
+      ->method('getName')
+      ->will($this->returnValue('foo'));
 
     $this->cacheTagsInvalidator->expects($this->never())
       ->method('invalidateTags');
@@ -664,6 +689,18 @@ public function testLoad() {
         array('', array('id' => 'foo')),
         array('id', 'foo'),
       )));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheContexts')
+      ->will($this->returnValue([]));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheTags')
+      ->will($this->returnValue(['config:foo']));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheMaxAge')
+      ->will($this->returnValue(Cache::PERMANENT));
+    $config_object->expects($this->exactly(1))
+      ->method('getName')
+      ->will($this->returnValue('foo'));
 
     $this->configFactory->expects($this->once())
       ->method('loadMultiple')
@@ -694,6 +731,19 @@ public function testLoadMultipleAll() {
         array('', array('id' => 'foo')),
         array('id', 'foo'),
       )));
+    $foo_config_object->expects($this->exactly(1))
+      ->method('getCacheContexts')
+      ->will($this->returnValue([]));
+    $foo_config_object->expects($this->exactly(1))
+      ->method('getCacheTags')
+      ->will($this->returnValue(['config:foo']));
+    $foo_config_object->expects($this->exactly(1))
+      ->method('getCacheMaxAge')
+      ->will($this->returnValue(Cache::PERMANENT));
+    $foo_config_object->expects($this->exactly(1))
+      ->method('getName')
+      ->will($this->returnValue('foo'));
+
     $bar_config_object = $this->getMockBuilder('Drupal\Core\Config\Config')
       ->disableOriginalConstructor()
       ->getMock();
@@ -703,6 +753,18 @@ public function testLoadMultipleAll() {
         array('', array('id' => 'bar')),
         array('id', 'bar'),
       )));
+    $bar_config_object->expects($this->exactly(1))
+      ->method('getCacheContexts')
+      ->will($this->returnValue([]));
+    $bar_config_object->expects($this->exactly(1))
+      ->method('getCacheTags')
+      ->will($this->returnValue(['config:bar']));
+    $bar_config_object->expects($this->exactly(1))
+      ->method('getCacheMaxAge')
+      ->will($this->returnValue(Cache::PERMANENT));
+    $bar_config_object->expects($this->exactly(1))
+      ->method('getName')
+      ->will($this->returnValue('foo'));
 
     $this->configFactory->expects($this->once())
       ->method('listAll')
@@ -742,6 +804,18 @@ public function testLoadMultipleIds() {
         array('', array('id' => 'foo')),
         array('id', 'foo'),
       )));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheContexts')
+      ->will($this->returnValue([]));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheTags')
+      ->will($this->returnValue(['config:foo']));
+    $config_object->expects($this->exactly(1))
+      ->method('getCacheMaxAge')
+      ->will($this->returnValue(Cache::PERMANENT));
+    $config_object->expects($this->exactly(1))
+      ->method('getName')
+      ->will($this->returnValue('foo'));
 
     $this->configFactory->expects($this->once())
       ->method('loadMultiple')
