Repository: mkdocs/mkdocs

Issue:
Create site for usage in a Docker image that can be deployed to any domain/URL
We use mkdocs to create a static web site that is deployed via Docker images.
We build the static HTML files with mkdocs, then put them in a Docker image that includes an Apache web server.
And then we (our customer actually) deploy the Docker image 8 times, each to a different domain, even with different sub URLs (like e.g. https://domain1/path/one and https://domain2/longer/different/path/two)

With mkdocs 1.2, mkdocs complains that site_url is now mandatory.

How can we solve our use case with mkdocs 1.2?

Is setting site_url to an empty string the recommended way? I think I've read in some tickets ("somewhere"), that setting site_url to an empty string has some negative side effects.

I personally don't think that making site_url mandatory is the right way. In today's containerized world, you often don't know the final domain/URL that your container will be deployed to. And rebuilding a static site for each container deployment is not something that can be done by the image creators.


Discussion:

Resolving patch:
diff --git a/mkdocs/commands/new.py b/mkdocs/commands/new.py
--- a/mkdocs/commands/new.py
+++ b/mkdocs/commands/new.py
@@ -1,9 +1,7 @@
 import logging
 import os
 
-config_text = """site_name: My Docs
-site_url: https://example.com/
-"""
+config_text = 'site_name: My Docs\n'
 index_text = """# Welcome to MkDocs
 
 For full documentation visit [mkdocs.org](https://www.mkdocs.org).
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -292,15 +292,6 @@ def __init__(self, default='', required=False, is_dir=False):
         self.is_dir = is_dir
         super().__init__(default, required)
 
-    def pre_validation(self, config, key_name):
-        # TODO: replace this with an error in a future release (1.3?)
-        user_defined_keys = sum([list(x.keys()) for x in config.user_configs], [])
-        if key_name == 'site_url' and key_name not in user_defined_keys:
-            self.warnings.append(
-                'This option is now required. Set to a valid URL or '
-                'an empty string to avoid an error in a future release.'
-            )
-
     def run_validation(self, value):
         if value == '':
             return value
@@ -318,16 +309,6 @@ def run_validation(self, value):
         raise ValidationError(
             "The URL isn't valid, it should include the http:// (scheme)")
 
-    def post_validation(self, config, key_name):
-        if key_name == 'site_url':
-            if config[key_name] in ['', None] and config['use_directory_urls']:
-                config['use_directory_urls'] = False
-                self.warnings.append(
-                    "The 'use_directory_urls' option has been disabled because "
-                    "'site_url' contains an empty value. Either define a valid "
-                    "URL for 'site_url' or set 'use_directory_urls' to False."
-                )
-
 
 class RepoURL(URL):
     """
diff --git a/mkdocs/config/defaults.py b/mkdocs/config/defaults.py
--- a/mkdocs/config/defaults.py
+++ b/mkdocs/config/defaults.py
@@ -24,7 +24,7 @@ def get_schema():
         ('pages', config_options.Nav()),
 
         # The full URL to where the documentation will be hosted
-        ('site_url', config_options.URL(is_dir=True, required=True)),
+        ('site_url', config_options.URL(is_dir=True)),
 
         # A description for the documentation project that will be added to the
         # HTML meta tags.


Test patch:
diff --git a/mkdocs/tests/base.py b/mkdocs/tests/base.py
--- a/mkdocs/tests/base.py
+++ b/mkdocs/tests/base.py
@@ -5,7 +5,6 @@
 from tempfile import TemporaryDirectory
 
 from mkdocs import config
-from mkdocs.config.defaults import get_schema
 from mkdocs import utils
 
 
@@ -28,14 +27,12 @@ def load_config(**cfg):
     cfg = cfg or {}
     if 'site_name' not in cfg:
         cfg['site_name'] = 'Example'
-    if 'site_url' not in cfg:
-        cfg['site_url'] = 'https://example.com'
     if 'config_file_path' not in cfg:
         cfg['config_file_path'] = os.path.join(path_base, 'mkdocs.yml')
     if 'docs_dir' not in cfg:
         # Point to an actual dir to avoid a 'does not exist' error on validation.
         cfg['docs_dir'] = os.path.join(path_base, 'docs')
-    conf = config.Config(schema=get_schema(), config_file_path=cfg['config_file_path'])
+    conf = config.Config(schema=config.defaults.get_schema(), config_file_path=cfg['config_file_path'])
     conf.load_dict(cfg)
 
     errors_warnings = conf.validate()
diff --git a/mkdocs/tests/config/base_tests.py b/mkdocs/tests/config/base_tests.py
--- a/mkdocs/tests/config/base_tests.py
+++ b/mkdocs/tests/config/base_tests.py
@@ -19,11 +19,10 @@ def test_unrecognised_keys(self):
 
         failed, warnings = c.validate()
 
-        self.assertIn(
+        self.assertEqual(warnings, [
             ('not_a_valid_config_option',
-                'Unrecognised configuration name: not_a_valid_config_option'),
-            warnings
-        )
+                'Unrecognised configuration name: not_a_valid_config_option')
+        ])
 
     def test_missing_required(self):
 
@@ -35,7 +34,7 @@ def test_missing_required(self):
         self.assertEqual(errors[0][0], 'site_name')
         self.assertEqual(str(errors[0][1]), 'Required configuration not provided.')
 
-        self.assertEqual(len(warnings), 1)
+        self.assertEqual(len(warnings), 0)
 
     def test_load_from_file(self):
         """
diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py
--- a/mkdocs/tests/config/config_tests.py
+++ b/mkdocs/tests/config/config_tests.py
@@ -23,7 +23,7 @@ def load_missing_config():
 
     def test_missing_site_name(self):
         c = config.Config(schema=defaults.get_schema())
-        c.load_dict({'site_url': 'https://example.com'})
+        c.load_dict({})
         errors, warnings = c.validate()
         self.assertEqual(len(errors), 1)
         self.assertEqual(errors[0][0], 'site_name')
@@ -295,123 +295,3 @@ def testConfigInstancesUnique(self):
         conf.load_dict({'site_name': 'foo'})
         conf.validate()
         self.assertIsNone(conf['mdx_configs'].get('toc'))
-
-    def test_site_url_and_use_directory_urls_undefined(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], '')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 2)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_undefined_and_use_directory_urls_false(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'use_directory_urls': False,
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], '')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 1)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_undefined_and_use_directory_urls_true(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'use_directory_urls': True,
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], '')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 2)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_empty_and_use_directory_urls_undefined(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'site_url': '',
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], '')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 1)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_empty_and_use_directory_urls_false(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'site_url': '',
-            'use_directory_urls': False,
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], '')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 0)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_empty_and_use_directory_urls_true(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'site_url': '',
-            'use_directory_urls': True,
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], '')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 1)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_defined_and_use_directory_urls_undefined(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'site_url': 'http://example.com/',
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], 'http://example.com/')
-        self.assertTrue(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 0)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_defined_and_use_directory_urls_false(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'site_url': 'http://example.com/',
-            'use_directory_urls': False,
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], 'http://example.com/')
-        self.assertFalse(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 0)
-        self.assertEqual(len(errors), 0)
-
-    def test_site_url_defined_and_use_directory_urls_true(self):
-        conf = config.Config(schema=defaults.get_schema())
-        conf.load_dict({
-            'site_name': 'Example',
-            'site_url': 'http://example.com/',
-            'use_directory_urls': True,
-            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
-        })
-        errors, warnings = conf.validate()
-        self.assertEqual(conf['site_url'], 'http://example.com/')
-        self.assertTrue(conf['use_directory_urls'])
-        self.assertEqual(len(warnings), 0)
-        self.assertEqual(len(errors), 0)
diff --git a/mkdocs/tests/integration/minimal/mkdocs.yml b/mkdocs/tests/integration/minimal/mkdocs.yml
--- a/mkdocs/tests/integration/minimal/mkdocs.yml
+++ b/mkdocs/tests/integration/minimal/mkdocs.yml
@@ -1,5 +1,4 @@
 site_name: MyTest
-site_url: 'https://example.com'
 
 nav:
     - 'testing.md'
diff --git a/mkdocs/tests/integration/subpages/mkdocs.yml b/mkdocs/tests/integration/subpages/mkdocs.yml
--- a/mkdocs/tests/integration/subpages/mkdocs.yml
+++ b/mkdocs/tests/integration/subpages/mkdocs.yml
@@ -1,2 +1 @@
 site_name: My Docs
-site_url: 'https://example.com'
diff --git a/mkdocs/tests/integration/unicode/mkdocs.yml b/mkdocs/tests/integration/unicode/mkdocs.yml
--- a/mkdocs/tests/integration/unicode/mkdocs.yml
+++ b/mkdocs/tests/integration/unicode/mkdocs.yml
@@ -1,2 +1 @@
 site_name: My Docs
-site_url: 'https://example.com'
diff --git a/mkdocs/tests/structure/page_tests.py b/mkdocs/tests/structure/page_tests.py
--- a/mkdocs/tests/structure/page_tests.py
+++ b/mkdocs/tests/structure/page_tests.py
@@ -20,8 +20,8 @@ def test_homepage(self):
         pg = Page('Foo', fl, cfg)
         self.assertEqual(fl.page, pg)
         self.assertEqual(pg.url, '')
-        self.assertEqual(pg.abs_url, '/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -44,8 +44,8 @@ def test_nested_index_page(self):
         pg = Page('Foo', fl, cfg)
         pg.parent = 'foo'
         self.assertEqual(pg.url, 'sub1/')
-        self.assertEqual(pg.abs_url, '/sub1/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/sub1/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -68,8 +68,8 @@ def test_nested_index_page_no_parent(self):
         pg = Page('Foo', fl, cfg)
         pg.parent = None  # non-homepage at nav root level; see #1919.
         self.assertEqual(pg.url, 'sub1/')
-        self.assertEqual(pg.abs_url, '/sub1/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/sub1/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -92,8 +92,8 @@ def test_nested_index_page_no_parent_no_directory_urls(self):
         pg = Page('Foo', fl, cfg)
         pg.parent = None  # non-homepage at nav root level; see #1919.
         self.assertEqual(pg.url, 'sub1/index.html')
-        self.assertEqual(pg.abs_url, '/sub1/index.html')
-        self.assertEqual(pg.canonical_url, 'https://example.com/sub1/index.html')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -116,8 +116,8 @@ def test_nested_nonindex_page(self):
         pg = Page('Foo', fl, cfg)
         pg.parent = 'foo'
         self.assertEqual(pg.url, 'sub1/non-index/')
-        self.assertEqual(pg.abs_url, '/sub1/non-index/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/sub1/non-index/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -140,8 +140,8 @@ def test_page_defaults(self):
         pg = Page('Foo', fl, cfg)
         self.assertRegex(pg.update_date, r'\d{4}-\d{2}-\d{2}')
         self.assertEqual(pg.url, 'testing/')
-        self.assertEqual(pg.abs_url, '/testing/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/testing/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -163,8 +163,8 @@ def test_page_no_directory_url(self):
         fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
         pg = Page('Foo', fl, cfg)
         self.assertEqual(pg.url, 'testing.html')
-        self.assertEqual(pg.abs_url, '/testing.html')
-        self.assertEqual(pg.canonical_url, 'https://example.com/testing.html')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -181,13 +181,13 @@ def test_page_no_directory_url(self):
         self.assertEqual(pg.title, 'Foo')
         self.assertEqual(pg.toc, [])
 
-    def test_page_site_url_empty(self):
-        cfg = load_config(site_url='', use_directory_urls=False)
+    def test_page_canonical_url(self):
+        cfg = load_config(site_url='http://example.com')
         fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
         pg = Page('Foo', fl, cfg)
-        self.assertEqual(pg.url, 'testing.html')
-        self.assertEqual(pg.abs_url, None)
-        self.assertEqual(pg.canonical_url, None)
+        self.assertEqual(pg.url, 'testing/')
+        self.assertEqual(pg.abs_url, '/testing/')
+        self.assertEqual(pg.canonical_url, 'http://example.com/testing/')
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -204,29 +204,6 @@ def test_page_site_url_empty(self):
         self.assertEqual(pg.title, 'Foo')
         self.assertEqual(pg.toc, [])
 
-    def test_homepage_site_url_empty(self):
-        cfg = load_config(site_url='', use_directory_urls=False)
-        fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
-        pg = Page('Foo', fl, cfg)
-        self.assertEqual(pg.url, 'index.html')
-        self.assertEqual(pg.abs_url, None)
-        self.assertEqual(pg.canonical_url, None)
-        self.assertEqual(pg.edit_url, None)
-        self.assertEqual(pg.file, fl)
-        self.assertEqual(pg.content, None)
-        self.assertTrue(pg.is_homepage)
-        self.assertTrue(pg.is_index)
-        self.assertTrue(pg.is_page)
-        self.assertFalse(pg.is_section)
-        self.assertTrue(pg.is_top_level)
-        self.assertEqual(pg.markdown, None)
-        self.assertEqual(pg.meta, {})
-        self.assertEqual(pg.next_page, None)
-        self.assertEqual(pg.parent, None)
-        self.assertEqual(pg.previous_page, None)
-        self.assertEqual(pg.title, 'Foo')
-        self.assertEqual(pg.toc, [])
-
     def test_page_canonical_url_nested(self):
         cfg = load_config(site_url='http://example.com/foo/')
         fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
@@ -279,8 +256,8 @@ def test_predefined_page_title(self):
         pg = Page('Page Title', fl, cfg)
         pg.read_source(cfg)
         self.assertEqual(pg.url, 'testing/')
-        self.assertEqual(pg.abs_url, '/testing/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/testing/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -303,8 +280,8 @@ def test_page_title_from_markdown(self):
         pg = Page(None, fl, cfg)
         pg.read_source(cfg)
         self.assertEqual(pg.url, 'testing/')
-        self.assertEqual(pg.abs_url, '/testing/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/testing/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -327,8 +304,8 @@ def test_page_title_from_meta(self):
         pg = Page(None, fl, cfg)
         pg.read_source(cfg)
         self.assertEqual(pg.url, 'metadata/')
-        self.assertEqual(pg.abs_url, '/metadata/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/metadata/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -351,8 +328,8 @@ def test_page_title_from_filename(self):
         pg = Page(None, fl, cfg)
         pg.read_source(cfg)
         self.assertEqual(pg.url, 'page-title/')
-        self.assertEqual(pg.abs_url, '/page-title/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/page-title/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -375,8 +352,8 @@ def test_page_title_from_capitalized_filename(self):
         pg = Page(None, fl, cfg)
         pg.read_source(cfg)
         self.assertEqual(pg.url, 'pageTitle/')
-        self.assertEqual(pg.abs_url, '/pageTitle/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/pageTitle/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)
@@ -399,8 +376,8 @@ def test_page_title_from_homepage_filename(self):
         pg = Page(None, fl, cfg)
         pg.read_source(cfg)
         self.assertEqual(pg.url, '')
-        self.assertEqual(pg.abs_url, '/')
-        self.assertEqual(pg.canonical_url, 'https://example.com/')
+        self.assertEqual(pg.abs_url, None)
+        self.assertEqual(pg.canonical_url, None)
         self.assertEqual(pg.edit_url, None)
         self.assertEqual(pg.file, fl)
         self.assertEqual(pg.content, None)