diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py
index 3f24ab8cf1..4eca2c98d8 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -9,7 +9,6 @@ from __future__ import unicode_literals
 
 from collections import namedtuple
 
-from django.apps import apps
 from django.core.exceptions import FieldDoesNotExist
 from django.db.backends import utils
 from django.db.models.constants import LOOKUP_SEP
@@ -215,12 +214,13 @@ def deferred_class_factory(model, attrs):
     """
     if not attrs:
         return model
+    opts = model._meta
     # Never create deferred models based on deferred model
     if model._deferred:
         # Deferred models are proxies for the non-deferred model. We never
         # create chains of defers => proxy_for_model is the non-deferred
         # model.
-        model = model._meta.proxy_for_model
+        model = opts.proxy_for_model
     # The app registry wants a unique name for each model, otherwise the new
     # class won't be created (we get an exception). Therefore, we generate
     # the name using the passed in attrs. It's OK to reuse an existing class
@@ -229,13 +229,14 @@ def deferred_class_factory(model, attrs):
     name = utils.truncate_name(name, 80, 32)
 
     try:
-        return apps.get_model(model._meta.app_label, name)
+        return opts.apps.get_model(model._meta.app_label, name)
 
     except LookupError:
 
         class Meta:
             proxy = True
-            app_label = model._meta.app_label
+            apps = opts.apps
+            app_label = opts.app_label
 
         overrides = {attr: DeferredAttribute(attr, model) for attr in attrs}
         overrides["Meta"] = Meta
diff --git a/docs/releases/1.8.6.txt b/docs/releases/1.8.6.txt
index 8fb9e16be5..1c2af01c7d 100644
--- a/docs/releases/1.8.6.txt
+++ b/docs/releases/1.8.6.txt
@@ -29,3 +29,8 @@ Bugfixes
 * Avoided a confusing stack trace when starting :djadmin:`runserver` with an
   invalid :setting:`INSTALLED_APPS` setting (:ticket:`25510`). This regression
   appeared in 1.8.5 as a side effect of fixing :ticket:`24704`.
+
+* Made deferred models use their proxied model's ``_meta.apps`` for caching
+  and retrieval (:ticket:`25563`). This prevents any models generated in data
+  migrations using ``QuerySet.defer()`` from leaking to test and application
+  code.
diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py
index d8c6e01884..cc1658ce58 100644
--- a/tests/defer_regress/tests.py
+++ b/tests/defer_regress/tests.py
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
 from operator import attrgetter
 
 from django.apps import apps
+from django.apps.registry import Apps
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.sessions.backends.db import SessionStore
+from django.db import models
 from django.db.models import Count
 from django.db.models.query_utils import (
     DeferredAttribute, deferred_class_factory,
@@ -263,6 +265,24 @@ class DeferRegressionTest(TestCase):
         deferred_cls = deferred_class_factory(Item, ())
         self.assertFalse(deferred_cls._deferred)
 
+    def test_deferred_class_factory_apps_reuse(self):
+        """
+        #25563 - model._meta.apps should be used for caching and
+        retrieval of the created proxy class.
+        """
+        isolated_apps = Apps(['defer_regress'])
+
+        class BaseModel(models.Model):
+            field = models.BooleanField()
+
+            class Meta:
+                apps = isolated_apps
+                app_label = 'defer_regress'
+
+        deferred_model = deferred_class_factory(BaseModel, ['field'])
+        self.assertIs(deferred_model._meta.apps, isolated_apps)
+        self.assertIs(deferred_class_factory(BaseModel, ['field']), deferred_model)
+
 
 class DeferAnnotateSelectRelatedTest(TestCase):
     def test_defer_annotate_select_related(self):