diff --git a/Dockerfile b/Dockerfile
index 04afc9c28787c93da6a816235f3a5d74a58cd504..6e4127c4132d72c0595f15dcfd60dd9cd2e622a7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -32,6 +32,9 @@ RUN pip install /tmp/arkindex.tar.gz && rm /tmp/arkindex.tar.gz
 RUN mkdir -p /logs
 RUN chown -R ark:teklia /logs
 
+# Copy Version file
+COPY VERSION /etc/arkindex.version
+
 # Run with Daphne
 EXPOSE 80
 CMD [ \
diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py
index 0dc280b53f72b927ea05170c9edf39c55c4f06c5..438a72da5ee861c5b867d58f30832d06fb23f78d 100644
--- a/arkindex/project/settings.py
+++ b/arkindex/project/settings.py
@@ -45,6 +45,13 @@ ADMINS = [('', address) for address in env2list('ADMIN_EMAIL')]
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 ML_CLASSIFIERS_DIR = os.environ.get('ML_CLASSIFIERS_DIR', os.path.join(BASE_DIR, '../../ml-classifiers'))
 
+# Read Version either from Docker static file or local file
+_version = '/etc/arkindex.version' \
+    if os.path.exists('/etc/arkindex.version') \
+    else os.path.join(os.path.dirname(BASE_DIR), 'VERSION')
+with open(_version) as f:
+    VERSION = f.read().strip()
+
 # Local IIIF server
 LOCAL_IMAGESERVER_ID = int(os.environ.get('LOCAL_IMAGESERVER_ID', 1))
 
@@ -245,9 +252,12 @@ elif os.environ.get('CACHE_DIR'):
         }
     }
 else:
+    # On dev, use a dummy cache
+    # On prod, use at least a local memory cache
+    _cache = 'django.core.cache.backends.dummy.DummyCache' if DEBUG else 'django.core.cache.backends.locmem.LocMemCache'
     CACHES = {
         'default': {
-            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
+            'BACKEND': _cache
         }
     }
 
@@ -425,6 +435,12 @@ ARKINDEX_TASKS_IMAGE = os.environ.get('ARKINDEX_TASKS_IMAGE', 'arkindex-tasks')
 # User groups with special permissions
 INTERNAL_GROUP_ID = 2
 
+# CDN Assets URL to use for arkindex remote CSS/JS/Images assets
+CDN_ASSETS_URL = os.environ.get('CDN_ASSETS_URL')
+if CDN_ASSETS_URL is not None:
+    CDN_ASSETS_URL = CDN_ASSETS_URL.rstrip('/')
+    STATIC_URL = f"{CDN_ASSETS_URL}/{VERSION}/static/"
+
 # Optional unit tests runner with code coverage
 try:
     import django_nose # noqa
diff --git a/arkindex/project/urls.py b/arkindex/project/urls.py
index 37a0d594a3c6768d577b693e013c0616a805ac88..3edb949eca6b3b4e21d3bd5c207ee70b3591cff3 100644
--- a/arkindex/project/urls.py
+++ b/arkindex/project/urls.py
@@ -1,8 +1,8 @@
-from django.urls import path, include
+from django.urls import path, include, re_path
 from django.conf import settings
 from django.contrib import admin
 from arkindex.project.api_v1 import api
-from arkindex.project.views import FrontendView
+from arkindex.project.views import FrontendView, CdnHome
 
 urlpatterns = [
     path('api/v1/', include((api, 'api'), namespace='api')),
@@ -17,3 +17,8 @@ if 'debug_toolbar' in settings.INSTALLED_APPS:
     urlpatterns = [
         path('__debug__/', include(debug_toolbar.urls)),
     ] + urlpatterns
+
+# Add index.html using CDN assets
+# It's served as a full fallback on 404 to support loading specific frontend urls
+if settings.CDN_ASSETS_URL is not None:
+    urlpatterns.append(re_path(r'^.*$', CdnHome.as_view(), name="home"))
diff --git a/arkindex/project/views.py b/arkindex/project/views.py
index 00b8c7746ec4ea1f618fa578aaabeb26e3120223..3238334f3cc70e49e71435956219755fe5120949 100644
--- a/arkindex/project/views.py
+++ b/arkindex/project/views.py
@@ -1,4 +1,6 @@
-from django.views.generic import View
+from django.views.generic import View, TemplateView
+from django.views.decorators.cache import cache_page
+from django.conf import settings
 
 
 class FrontendView(View):
@@ -6,3 +8,21 @@ class FrontendView(View):
     View that show the frontend's router
     TODO: Get rid of this view once the frontend is fully split
     """
+
+
+class CdnHome(TemplateView):
+    """
+    Index template to use frontend assets from a CDN
+    """
+    template_name = 'index.html'
+
+    def dispatch(self, *args, **kwargs):
+        # Cache rendered page
+        # On dev, by default a dummy cache is enabled
+        return cache_page(60 * 10)(super().dispatch)(*args, **kwargs)
+
+    def get_context_data(self, *args, **kwargs):
+        ctx = super().get_context_data(*args, **kwargs)
+        ctx['cdn_assets_url'] = settings.CDN_ASSETS_URL
+        ctx['version'] = settings.VERSION
+        return ctx
diff --git a/arkindex/templates/index.html b/arkindex/templates/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..31e750fa0f8b2bdab3765e9732c87301c7e347f6
--- /dev/null
+++ b/arkindex/templates/index.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <meta name=viewport content="width=device-width,initial-scale=1">
+    <meta name="version" content="{{ version }}">
+    <meta name="assets_url" content="{{ cdn_assets_url }}/{{ version }}">
+    <meta name="api_base_url" content="/api/v1">
+    <title>ArkIndex {{ version }}</title>
+    <link href="{{ cdn_assets_url }}/{{ version }}/arkindex-vendors-main-{{ version }}.css" rel="stylesheet">
+    <link href="{{ cdn_assets_url }}/{{ version }}/arkindex-{{ version }}.css" rel="stylesheet">
+  </head>
+  <body>
+    <div id=app></div>
+    <script type="text/javascript" src="{{ cdn_assets_url }}/{{ version }}/arkindex-vendors-main-{{ version }}.js"></script>
+    <script type="text/javascript" src="{{ cdn_assets_url }}/{{ version }}/arkindex-{{ version }}.js"></script>
+</body>
+</html>