diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2495aaac206c1cc1765ef41d7150e00316faa456..c4a23f4beb6db4239f945394698c4eaa9a8a81b8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -99,6 +99,20 @@ installcheck:linux:amd64:
     - linux
     - amd64
 
+doc:
+  stage: test
+  script:
+    - SPHINXFLAGS="-W" make doc
+  dependencies: []
+  artifacts:
+    expire_in: 1 hour
+    paths:
+      - ./doc/*
+  tags:
+    - docker
+
+
+
 deckard:linux:amd64:
   stage: test
   script:
@@ -184,6 +198,7 @@ respdiff:iter:udp:linux:amd64:
     expire_in: '1 week'
     paths:
       - results/*.txt
+      - results/respdiff.db/data.mdb.xz
       - ./*.info
   tags:
     - docker
@@ -208,6 +223,7 @@ respdiff:iter:tcp:linux:amd64:
     expire_in: '1 week'
     paths:
       - results/*.txt
+      - results/respdiff.db/data.mdb.xz
       - ./*.info
   tags:
     - docker
@@ -232,6 +248,7 @@ respdiff:iter:tls:linux:amd64:
     expire_in: '1 week'
     paths:
       - results/*.txt
+      - results/respdiff.db/data.mdb.xz
       - ./*.info
   tags:
     - docker
diff --git a/ci/Dockerfile b/ci/Dockerfile
index 7a72190aaa4177570c00154fa9394b986cf0fe99..86dba2022314ae37bcb9a983e18155cf98136fc2 100644
--- a/ci/Dockerfile
+++ b/ci/Dockerfile
@@ -10,6 +10,8 @@ RUN apt-get upgrade -y -qqq
 
 # Knot and Knot Resolver dependecies
 RUN apt-get install -y -qqq make cmake pkg-config git build-essential bsdmainutils libtool autoconf make pkg-config liburcu-dev libgnutls28-dev libedit-dev liblmdb-dev libcap-ng-dev libsystemd-dev libidn11-dev protobuf-c-compiler libfstrm-dev pkg-config libuv1-dev libcmocka-dev libluajit-5.1-dev lua-sec lua-socket lua-http
+# documentation dependecies
+RUN apt-get install -y -qqq doxygen python3-sphinx python3-breathe python3-sphinx-rtd-theme
 
 # Python packags required for Deckard CI
 # Python: grab latest versions from PyPi
@@ -46,6 +48,7 @@ RUN apt-get install luarocks -y -qqq
 RUN luarocks install luacheck
 
 # respdiff for kresd CI
+RUN apt-get install lmdb-utils -y -qqq
 RUN pip3 install dnspython python-augeas
 RUN git clone --depth=1 https://gitlab.labs.nic.cz/knot/resolver-benchmarking.git /tmp/resolver-benchmarking
 RUN mv /tmp/resolver-benchmarking/response_differences/respdiff /var/opt/respdiff
diff --git a/ci/respdiff/kresd.config b/ci/respdiff/kresd.config
index e7827b00c240ffe93ceeebb7acd2d8793ddc3e3a..98d62f17f29f7a65a7688a719899e83b48f644bd 100644
--- a/ci/respdiff/kresd.config
+++ b/ci/respdiff/kresd.config
@@ -6,7 +6,7 @@ net.listen('127.0.0.1', 8853, { tls = true })
 net.listen('::1', 8853, { tls = true })
 
 -- Auto-maintain root TA
-trust_anchors.file = '.local/etc/kresd/root.keys'
+trust_anchors.file = '.local/etc/knot-resolver/root.keys'
 
 -- Large cache size, so we don't need to flush often
 -- This can be larger than available RAM, least frequently accessed
diff --git a/ci/respdiff/run-respdiff-tests.sh b/ci/respdiff/run-respdiff-tests.sh
index b85d412560794d9e2f7265d8693c9e74958aab6d..f99fdd732409321ad4b6d31180b89a51ccf3842e 100755
--- a/ci/respdiff/run-respdiff-tests.sh
+++ b/ci/respdiff/run-respdiff-tests.sh
@@ -12,3 +12,7 @@ CONFIG="$(pwd)/ci/respdiff/respdiff-${1}.conf"
 time /var/opt/respdiff/orchestrator.py respdiff.db -c "${CONFIG}"
 time /var/opt/respdiff/msgdiff.py respdiff.db -c "${CONFIG}"
 /var/opt/respdiff/diffsum.py respdiff.db -c "${CONFIG}" > results/respdiff.txt
+: minimize LMDB size so it can be effectively archived
+mkdir results/respdiff.db
+mdb_copy -c respdiff.db results/respdiff.db
+xz -9 results/respdiff.db/data.mdb
diff --git a/client/kresc.c b/client/kresc.c
index 5f38d4f83ac9b588e7c096bc4e2b2f8ba106123f..42dba3454aa8c031de9efa1b461d894860a998f8 100644
--- a/client/kresc.c
+++ b/client/kresc.c
@@ -90,7 +90,7 @@ const char *get_type_name(const char *value)
 
 static void complete_function(EditLine * el)
 {
-	//Add left parenthesis to function name. 
+	//Add left parenthesis to function name.
 	el_insertstr(el, "(");
 }
 
@@ -392,7 +392,7 @@ static int interact()
 		//Create necessary folders.
 		char *dirs[3] =
 		    { afmt("%s/.local", home), afmt("%s/.local/share", home),
-			afmt("%s/.local/share/kresd/", home)
+			afmt("%s/.local/share/knot-resolver/", home)
 		};
 		bool ok = true;
 		for (int i = 0; i < 3; i++) {
@@ -403,12 +403,12 @@ static int interact()
 		}
 		if (ok) {
 			hist_file =
-			    afmt("%s/.local/share/kresd/" HISTORY_FILE, home);
+			    afmt("%s/.local/share/knot-resolver/" HISTORY_FILE, home);
 		}
 	} else {
-		if (!mkdir(afmt("%s/kresd/", data_home), 0755)
+		if (!mkdir(afmt("%s/knot-resolver/", data_home), 0755)
 		    || errno == EEXIST) {
-			hist_file = afmt("%s/kresd/" HISTORY_FILE, data_home);
+			hist_file = afmt("%s/knot-resolver/" HISTORY_FILE, data_home);
 		}
 	}
 
diff --git a/config.mk b/config.mk
index ac3bac985914212f34e421b0edb3c293b0ddad4c..77b15a79bd2e5c3b040a064c6cfac6ba236ea00f 100644
--- a/config.mk
+++ b/config.mk
@@ -18,7 +18,7 @@ PKGCONFIGDIR ?= $(LIBDIR)/pkgconfig
 MANDIR ?= $(PREFIX)/share/man
 INCLUDEDIR ?= $(PREFIX)/include
 MODULEDIR ?= $(LIBDIR)/kdns_modules
-ETCDIR ?= $(PREFIX)/etc/kresd
+ETCDIR ?= $(PREFIX)/etc/knot-resolver
 ROOTHINTS ?= $(ETCDIR)/root.hints
 COVERAGE_STAGE ?= gcov
 COVERAGE_STATSDIR ?= $(CURDIR)/coverage.stats
@@ -40,11 +40,6 @@ BUILD_CFLAGS += -I$(abspath .) -I$(abspath lib/generic) -I$(abspath contrib)
 BUILD_CFLAGS += -DPACKAGE_VERSION="\"$(VERSION)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\""
 BUILD_CFLAGS += -fvisibility=hidden
 
-# Otherwise Fedora is making kresd symbols inaccessible for modules
-# TODO: clang needs different flag name, etc.
-BUILD_CFLAGS += -rdynamic
-BUILD_LDFLAGS += -export-dynamic
-
 ifeq (,$(findstring -O,$(CFLAGS)))
 	BUILD_CFLAGS += -O2
 endif
diff --git a/daemon/README.rst b/daemon/README.rst
index 5e4d63ec4f2d0923a60c36817655cb49a73299fd..902e8ac3c113933572f8b9ef09e5d1a88968c36b 100644
--- a/daemon/README.rst
+++ b/daemon/README.rst
@@ -9,144 +9,6 @@ The server is in the `daemon` directory, it works out of the box without any con
    $ kresd -h # Get help
    $ kresd -a ::1
 
-Enabling DNSSEC
-===============
-
-The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates and :rfc:`7646` negative trust anchors.
-To enable it, you need to provide trusted root keys. Bootstrapping of the keys is automated, and kresd fetches root trust anchors set `over a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_ from IANA. From there, it can perform :rfc:`5011` automatic updates for you.
-
-.. note:: Automatic bootstrap requires luasocket_ and luasec_ installed.
-
-.. code-block:: none
-
-   $ kresd -k root-new.keys # File for root keys
-   [ ta ] keyfile 'root-new.keys': doesn't exist, bootstrapping
-   [ ta ] Root trust anchors bootstrapped over https with pinned certificate.
-          You SHOULD verify them manually against original source:
-          https://www.iana.org/dnssec/files
-   [ ta ] Current root trust anchors are:
-   . 0 IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
-   . 0 IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
-   [ ta ] next refresh for . in 24 hours
-
-Alternatively, you can set it in configuration file with ``trust_anchors.file = 'root.keys'``. If the file doesn't exist, it will be automatically populated with root keys validated using root anchors retrieved over HTTPS.
-
-This is equivalent to `using unbound-anchor <https://www.unbound.net/documentation/howto_anchor.html>`_:
-
-.. code-block:: bash
-
-   $ unbound-anchor -a "root.keys" || echo "warning: check the key at this point"
-   $ echo "auto-trust-anchor-file: \"root.keys\"" >> unbound.conf
-   $ unbound -c unbound.conf
-
-.. warning:: Bootstrapping of the root trust anchors is automatic, you are however **encouraged to check** the key over **secure channel**, as specified in `DNSSEC Trust Anchor Publication for the Root Zone <https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs>`_. This is a critical step where the whole infrastructure may be compromised, you will be warned in the server log.
-
-Configuration is described in :ref:`dnssec-config`.
-
-Manually providing root anchors
--------------------------------
-
-The root anchors bootstrap may fail for various reasons, in this case you need to provide IANA or alternative root anchors. The format of the keyfile is the same as for Unbound or BIND and contains DS/DNSKEY records.
-
-1. Check the current TA published on `IANA website <https://data.iana.org/root-anchors/root-anchors.xml>`_
-2. Fetch current keys (DNSKEY), verify digests
-3. Deploy them
-
-.. code-block:: bash
-
-   $ kdig DNSKEY . @k.root-servers.net +noall +answer | grep "DNSKEY[[:space:]]257" > root.keys
-   $ ldns-key2ds -n root.keys # Only print to stdout
-   ... verify that digest matches TA published by IANA ...
-   $ kresd -k root.keys
-
-You've just enabled DNSSEC!
-
-.. note:: Bootstrapping and automatic update need write access to keyfile direcory. If you want to manage root anchors manually you should use ``trust_anchors.add_file('root.keys', true)``.
-
-CLI interface
-=============
-
-The daemon features a CLI interface, type ``help()`` to see the list of available commands.
-
-.. code-block:: bash
-
-   $ kresd /var/cache/knot-resolver
-   [system] started in interactive mode, type 'help()'
-   > cache.count()
-   53
-
-.. role:: lua(code)
-   :language: lua
-
-Verbose output
---------------
-
-If the verbose logging is compiled in, i.e. not turned off by ``-DNOVERBOSELOG``, you can turn on verbose tracing of server operation with the ``-v`` option.
-You can also toggle it on runtime with ``verbose(true|false)`` command.
-
-.. code-block:: bash
-
-   $ kresd -v
-
-To run the daemon by hand, such as under ``nohup``, use ``-f 1`` to start a single fork. For example:
-
-.. code-block:: bash
-
-   $ nohup ./daemon/kresd -a 127.0.0.1 -f 1 -v &
-
-
-Scaling out
-===========
-
-The server can clone itself into multiple processes upon startup, this enables you to scale it on multiple cores.
-Multiple processes can serve different addresses, but still share the same working directory and cache.
-You can add, start and stop processes during runtime based on the load.
-
-.. code-block:: bash
-
-   $ kresd -f 4 rundir > kresd.log &
-   $ kresd -f 2 rundir > kresd_2.log & # Extra instances
-   $ pstree $$ -g
-   bash(3533)─┬─kresd(19212)─┬─kresd(19212)
-              │              ├─kresd(19212)
-              │              └─kresd(19212)
-              ├─kresd(19399)───kresd(19399)
-              └─pstree(19411)
-   $ kill 19399 # Kill group 2, former will continue to run
-   bash(3533)─┬─kresd(19212)─┬─kresd(19212)
-              │              ├─kresd(19212)
-              │              └─kresd(19212)
-              └─pstree(19460)
-
-.. _daemon-reuseport:
-
-.. note:: On recent Linux supporting ``SO_REUSEPORT`` (since 3.9, backported to RHEL 2.6.32) it is also able to bind to the same endpoint and distribute the load between the forked processes. If your OS doesn't support it, use only one daemon process.
-
-Notice the absence of an interactive CLI. You can attach to the the consoles for each process, they are in ``rundir/tty/PID``.
-
-.. code-block:: bash
-
-	$ nc -U rundir/tty/3008 # or socat - UNIX-CONNECT:rundir/tty/3008
-	> cache.count()
-	53
-
-The *direct output* of the CLI command is captured and sent over the socket, while also printed to the daemon standard outputs (for accountability). This gives you an immediate response on the outcome of your command.
-Error or debug logs aren't captured, but you can find them in the daemon standard outputs.
-
-This is also a way to enumerate and test running instances, the list of files in ``tty`` corresponds to the list
-of running processes, and you can test the process for liveliness by connecting to the UNIX socket.
-
-.. _daemon-supervised:
-
-Running supervised
-==================
-
-Knot Resolver can run under a supervisor to allow for graceful restarts, watchdog process and socket activation. This way the supervisor binds to sockets and lends them to the resolver daemon. If the resolver terminates or is killed, the sockets remain open and no queries are dropped.
-
-The watchdog process must notify kresd about active file descriptors, and kresd will automatically determine the socket type and bound address, thus it will appear as any other address. You should have a look at `real process managers`_.
-
-The daemon also supports `systemd socket activation`_, it is automatically detected and requires no configuration on users's side.
-
 Configuration
 =============
 
@@ -434,7 +296,7 @@ Environment
 
       > user('baduser')
       invalid user name
-      > user('kresd', 'netgrp')
+      > user('knot-resolver', 'netgrp')
       true
       > user('root')
       Operation not permitted
@@ -605,9 +467,9 @@ For when listening on ``localhost`` just doesn't cut it.
 
    .. code-block:: lua
 
-      > net.tls("/etc/kresd/server-cert.pem", "/etc/kresd/server-key.pem")
+      > net.tls("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem")
       > net.tls()
-      ("/etc/kresd/server-cert.pem", "/etc/kresd/server-key.pem")
+      ("/etc/knot-resolver/server-cert.pem", "/etc/knot-resolver/server-key.pem")
       > net.listen("::", 853)
       > net.listen("::", 443, {tls = true})
 
@@ -1141,6 +1003,144 @@ specified worker count and process rank.
 
 	print(worker.stats().concurrent)
 
+Running supervised
+==================
+
+Knot Resolver can run under a supervisor to allow for graceful restarts, watchdog process and socket activation. This way the supervisor binds to sockets and lends them to the resolver daemon. If the resolver terminates or is killed, the sockets remain open and no queries are dropped.
+
+The watchdog process must notify kresd about active file descriptors, and kresd will automatically determine the socket type and bound address, thus it will appear as any other address. You should have a look at `real process managers`_.
+
+The daemon also supports `systemd socket activation`_, it is automatically detected and requires no configuration on users's side.
+
+Enabling DNSSEC
+===============
+
+The resolver supports DNSSEC including :rfc:`5011` automated DNSSEC TA updates and :rfc:`7646` negative trust anchors.
+To enable it, you need to provide trusted root keys. Bootstrapping of the keys is automated, and kresd fetches root trust anchors set `over a secure channel <http://jpmens.net/2015/01/21/opendnssec-rfc-5011-bind-and-unbound/>`_ from IANA. From there, it can perform :rfc:`5011` automatic updates for you.
+
+.. note:: Automatic bootstrap requires luasocket_ and luasec_ installed.
+
+.. code-block:: none
+
+   $ kresd -k root-new.keys # File for root keys
+   [ ta ] keyfile 'root-new.keys': doesn't exist, bootstrapping
+   [ ta ] Root trust anchors bootstrapped over https with pinned certificate.
+          You SHOULD verify them manually against original source:
+          https://www.iana.org/dnssec/files
+   [ ta ] Current root trust anchors are:
+   . 0 IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5
+   . 0 IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D
+   [ ta ] next refresh for . in 24 hours
+
+Alternatively, you can set it in configuration file with ``trust_anchors.file = 'root.keys'``. If the file doesn't exist, it will be automatically populated with root keys validated using root anchors retrieved over HTTPS.
+
+This is equivalent to `using unbound-anchor <https://www.unbound.net/documentation/howto_anchor.html>`_:
+
+.. code-block:: bash
+
+   $ unbound-anchor -a "root.keys" || echo "warning: check the key at this point"
+   $ echo "auto-trust-anchor-file: \"root.keys\"" >> unbound.conf
+   $ unbound -c unbound.conf
+
+.. warning:: Bootstrapping of the root trust anchors is automatic, you are however **encouraged to check** the key over **secure channel**, as specified in `DNSSEC Trust Anchor Publication for the Root Zone <https://data.iana.org/root-anchors/draft-icann-dnssec-trust-anchor.html#sigs>`_. This is a critical step where the whole infrastructure may be compromised, you will be warned in the server log.
+
+Configuration is described in :ref:`dnssec-config`.
+
+Manually providing root anchors
+-------------------------------
+
+The root anchors bootstrap may fail for various reasons, in this case you need to provide IANA or alternative root anchors. The format of the keyfile is the same as for Unbound or BIND and contains DS/DNSKEY records.
+
+1. Check the current TA published on `IANA website <https://data.iana.org/root-anchors/root-anchors.xml>`_
+2. Fetch current keys (DNSKEY), verify digests
+3. Deploy them
+
+.. code-block:: bash
+
+   $ kdig DNSKEY . @k.root-servers.net +noall +answer | grep "DNSKEY[[:space:]]257" > root.keys
+   $ ldns-key2ds -n root.keys # Only print to stdout
+   ... verify that digest matches TA published by IANA ...
+   $ kresd -k root.keys
+
+You've just enabled DNSSEC!
+
+.. note:: Bootstrapping and automatic update need write access to keyfile direcory. If you want to manage root anchors manually you should use ``trust_anchors.add_file('root.keys', true)``.
+
+CLI interface
+=============
+
+The daemon features a CLI interface, type ``help()`` to see the list of available commands.
+
+.. code-block:: bash
+
+   $ kresd /var/cache/knot-resolver
+   [system] started in interactive mode, type 'help()'
+   > cache.count()
+   53
+
+.. role:: lua(code)
+   :language: lua
+
+Verbose output
+--------------
+
+If the verbose logging is compiled in, i.e. not turned off by ``-DNOVERBOSELOG``, you can turn on verbose tracing of server operation with the ``-v`` option.
+You can also toggle it on runtime with ``verbose(true|false)`` command.
+
+.. code-block:: bash
+
+   $ kresd -v
+
+To run the daemon by hand, such as under ``nohup``, use ``-f 1`` to start a single fork. For example:
+
+.. code-block:: bash
+
+   $ nohup ./daemon/kresd -a 127.0.0.1 -f 1 -v &
+
+
+Scaling out
+===========
+
+The server can clone itself into multiple processes upon startup, this enables you to scale it on multiple cores.
+Multiple processes can serve different addresses, but still share the same working directory and cache.
+You can add, start and stop processes during runtime based on the load.
+
+.. code-block:: bash
+
+   $ kresd -f 4 rundir > kresd.log &
+   $ kresd -f 2 rundir > kresd_2.log & # Extra instances
+   $ pstree $$ -g
+   bash(3533)─┬─kresd(19212)─┬─kresd(19212)
+              │              ├─kresd(19212)
+              │              └─kresd(19212)
+              ├─kresd(19399)───kresd(19399)
+              └─pstree(19411)
+   $ kill 19399 # Kill group 2, former will continue to run
+   bash(3533)─┬─kresd(19212)─┬─kresd(19212)
+              │              ├─kresd(19212)
+              │              └─kresd(19212)
+              └─pstree(19460)
+
+.. _daemon-reuseport:
+
+.. note:: On recent Linux supporting ``SO_REUSEPORT`` (since 3.9, backported to RHEL 2.6.32) it is also able to bind to the same endpoint and distribute the load between the forked processes. If your OS doesn't support it, use only one daemon process.
+
+Notice the absence of an interactive CLI. You can attach to the the consoles for each process, they are in ``rundir/tty/PID``.
+
+.. code-block:: bash
+
+	$ nc -U rundir/tty/3008 # or socat - UNIX-CONNECT:rundir/tty/3008
+	> cache.count()
+	53
+
+The *direct output* of the CLI command is captured and sent over the socket, while also printed to the daemon standard outputs (for accountability). This gives you an immediate response on the outcome of your command.
+Error or debug logs aren't captured, but you can find them in the daemon standard outputs.
+
+This is also a way to enumerate and test running instances, the list of files in ``tty`` corresponds to the list
+of running processes, and you can test the process for liveliness by connecting to the UNIX socket.
+
+.. _daemon-supervised:
+
 Using CLI tools
 ===============
 
diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh
index 3599e9b3aa4f3f7ff52d626fb2f8c4ed60b1fc56..ff673a517cf3b7b9590ab8f22e7f5b6bbc8e5b70 100755
--- a/daemon/lua/kres-gen.sh
+++ b/daemon/lua/kres-gen.sh
@@ -6,7 +6,7 @@
 # (Avoid typos, accidental mismatches, etc.)
 #
 # To regenerate the C definitions for lua:
-# - you need to have debugging symbols for knot-dns and kresd;
+# - you need to have debugging symbols for knot-dns and knot-resolver;
 #   you get those by compiling with -g; for knot-dns it might be enough
 #   to just install it with debugging symbols included (in your distro way)
 # - remove file ./kres-gen.lua and run make as usual
diff --git a/daemon/main.c b/daemon/main.c
index 560aec2a7f0a1c571dde1de8c1d06c16dc6a66fe..046a19a279ba22aeea043d9aa50031a87d814ea3 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -475,7 +475,10 @@ static void args_init(struct args *args)
 	args->quiet = false;
 }
 
-int parse_args(int argc, char **argv, struct args *args)
+/** Process arguments into struct args.
+ * @return >=0 if main() should be exited immediately.
+ */
+static int parse_args(int argc, char **argv, struct args *args)
 {
 	/* Long options. */
 	int c = 0, li = 0;
@@ -554,11 +557,11 @@ int parse_args(int argc, char **argv, struct args *args)
 			help(argc, argv);
 			return EXIT_FAILURE;
 		}
-	}	
+	}
 	if (optind < argc) {
 		args->rundir = argv[optind];
 	}
-	return EXIT_SUCCESS;
+	return -1;
 }
 
 static int bind_fds(struct network *net, fd_array_t *fd_set, bool tls) {
@@ -595,7 +598,7 @@ int main(int argc, char **argv)
 	int ret = 0;
 	struct args args;
 	args_init(&args);
-	if ((ret = parse_args(argc, argv, &args)) != EXIT_SUCCESS) {
+	if ((ret = parse_args(argc, argv, &args)) >= 0) {
 		return ret;
 	}
 
@@ -651,7 +654,7 @@ int main(int argc, char **argv)
 	 * sockets etc. before forking, but at the same time can't touch it before
 	 * forking otherwise it crashes, so it's a chicken and egg problem.
 	 * Disabling until https://github.com/libuv/libuv/pull/846 is done. */
-	 if (forks > 1 && fd_set.len == 0 && tls_fd_set.len == 0) {
+	 if (args.forks > 1 && args.fd_set.len == 0 && args.tls_fd_set.len == 0) {
 	 	kr_log_error("[system] forking >1 workers supported only on Linux 3.9+ or with supervisor\n");
 	 	return EXIT_FAILURE;
 	 }
diff --git a/doc/build.rst b/doc/build.rst
index 6ac99ba759adbf279b0370ac692c000012c117d4..6da43b3a3c290bd645efda26b1b9a95782cd3140 100644
--- a/doc/build.rst
+++ b/doc/build.rst
@@ -108,7 +108,7 @@ Most of the dependencies can be resolved from packages, here's an overview for s
 
    brew install pkg-config libuv luajit cmocka
 
-Building from sources 
+Building from sources
 ---------------------
 
 Initialize git submodules first.
@@ -195,7 +195,7 @@ All paths are prefixed with ``PREFIX`` variable by default if not specified othe
 
    "library", "``LIBDIR``", "``$(PREFIX)/lib``", "pkg-config is auto-generated [#]_"
    "daemon",  "``SBINDIR``", "``$(PREFIX)/sbin``", ""
-   "configuration", "``ETCDIR``", "``$(PREFIX)/etc/kresd``", "Configuration file, templates."
+   "configuration", "``ETCDIR``", "``$(PREFIX)/etc/knot-resolver``", "Configuration file, templates."
    "modules", "``MODULEDIR``", "``$(LIBDIR)/kdns_modules``", "Runtime directory for loading dynamic modules [#]_."
    "trust anchor file", "``KEYFILE_DEFAULT``", "*(none)*", "Path to read-only trust anchor file, which is used as fallback when no other file is specified. [#]_"
    "work directory", "", "the current directory", "Run directory for daemon. (Only relevant during run time, not e.g. during installation.)"
@@ -216,7 +216,7 @@ By default the resolver library is built as a dynamic library with versioned ABI
    $ make BUILDMODE=dynamic # Default, create dynamic library
    $ make BUILDMODE=static  # Create static library
 
-When the library is linked statically, it usually produces a smaller binary. However linking it to various C modules might violate ODR and increase the size. 
+When the library is linked statically, it usually produces a smaller binary. However linking it to various C modules might violate ODR and increase the size.
 
 Resolving dependencies
 ~~~~~~~~~~~~~~~~~~~~~~
diff --git a/doc/doc.mk b/doc/doc.mk
index 26a52bf6325e4bbd344eff98441a59b281eecdd2..58c299e3411a327a6ddad8627baa9e787af7dec3 100644
--- a/doc/doc.mk
+++ b/doc/doc.mk
@@ -2,7 +2,7 @@ ifeq ($(HAS_doxygen)|$(HAS_sphinx-build), yes|yes)
 doc-doxygen:
 	@cd doc && $(doxygen_BIN)
 doc-html: doc-doxygen
-	@cd doc && $(sphinx-build_BIN) -b html . html
+	@cd doc && $(sphinx-build_BIN) $(SPHINXFLAGS) -b html . html
 else
 doc-html:
 	$(error doxygen and sphinx must be installed)
diff --git a/doc/index.rst b/doc/index.rst
index d5ac52459bfdcabe33c11602976f8140297c3e1b..b38d5de5d8b4941ecf7cbb20730ef9481a626763 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -9,10 +9,10 @@ Modular architecture of the library keeps the core tiny and efficient, and provi
 .. toctree::
    :maxdepth: 2
 
-   build
-   lib
    daemon
    modules
+   build
+   lib
    modules_api
 
 
diff --git a/etc/config.cluster b/etc/config.cluster
index f434be8f5446db5ee12af55c689efd8eb194b88b..bc1cf60a5bb64258f398bf2fb281ac7f46c3eacc 100644
--- a/etc/config.cluster
+++ b/etc/config.cluster
@@ -8,7 +8,7 @@
 net = { '127.0.0.1', '::1', '192.168.1.1' }
 
 -- Drop root privileges
-user('kresd', 'kresd')
+user('knot-resolver', 'knot-resolver')
 
 -- Auto-maintain root TA
 trust_anchors.file = 'root.keys'
@@ -16,7 +16,7 @@ trust_anchors.file = 'root.keys'
 -- Large cache size, so we don't need to flush ever
 -- This can be larger than available RAM, least frequently accessed
 -- records will be paged out as long as there's enough disk space to back it
-cache.size = 100 * GB 
+cache.size = 100 * GB
 
 -- Load Useful modules
 modules = {
diff --git a/etc/config.isp b/etc/config.isp
index 344de7e15ecdfc52fbf53821dc815b88be86bd07..f050bd883f0f410c55135092d6ccf632e4a8eac7 100644
--- a/etc/config.isp
+++ b/etc/config.isp
@@ -5,7 +5,7 @@
 net = { '127.0.0.1', '::1', '192.168.1.1' }
 
 -- Drop root privileges
-user('kresd', 'kresd')
+user('knot-resolver', 'knot-resolver')
 
 -- Auto-maintain root TA
 trust_anchors.file = 'root.keys'
@@ -13,7 +13,7 @@ trust_anchors.file = 'root.keys'
 -- Large cache size, so we don't need to flush often
 -- This can be larger than available RAM, least frequently accessed
 -- records will be paged out
-cache.size = 4 * GB 
+cache.size = 4 * GB
 
 -- Load Useful modules
 modules = {
diff --git a/etc/config.personal b/etc/config.personal
index a46af4d847f83d21b2bacf34f89ae98f4001ad37..490798e22120da32742379923bc21317893a63fc 100644
--- a/etc/config.personal
+++ b/etc/config.personal
@@ -7,7 +7,7 @@
 -- net = { '127.0.0.1', '::1' }
 
 -- Drop root privileges
-user('kresd', 'kresd')
+user('knot-resolver', 'knot-resolver')
 
 -- Auto-maintain root TA
 trust_anchors.file = 'root.keys'
diff --git a/etc/config.splitview b/etc/config.splitview
index 2d5f66d6ec5f2f5fc52c11db74b03057d4b2b31e..1706a498a61a1d9841917b0af6051029f2b15ae4 100644
--- a/etc/config.splitview
+++ b/etc/config.splitview
@@ -5,7 +5,7 @@
 net = { '127.0.0.1', '::1', '192.168.1.1' }
 
 -- Drop root privileges
-user('kresd', 'kresd')
+user('knot-resolver', 'knot-resolver')
 
 -- Auto-maintain root TA
 trust_anchors.file = 'root.keys'
@@ -28,7 +28,7 @@ modules = {
 -- Large cache size, so we don't need to flush often
 -- This can be larger than available RAM, least frequently accessed
 -- records will be paged out
-cache.size = 4 * GB 
+cache.size = 4 * GB
 
 -- Forward everything below `company.cz` to `192.168.1.3`
 policy.add(policy.suffix(policy.FORWARD('192.168.1.3'), {todname('company.cz')}))
diff --git a/lib/utils.c b/lib/utils.c
index 97df669e16c701b384a7f02ec876f1141121e178..6b1c2eac5743aa35c1265f2a917e2827a99f3866 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -143,7 +143,10 @@ char* kr_strcatdup(unsigned n, ...)
 	for (unsigned i = 0; i < n; ++i) {
 		char *item = va_arg(vl, char *);
 		const size_t new_len = total_len + strlen_safe(item);
-		if (unlikely(new_len < total_len)) return NULL;
+		if (unlikely(new_len < total_len)) {
+			va_end(vl);
+			return NULL;
+		}
 		total_len = new_len;
 	}
 	va_end(vl);
diff --git a/modules/etcd/README.rst b/modules/etcd/README.rst
index d25b369a4c3bceddd78cd211715ebb82efdd3cf0..5de55c216a13ca9895a00b93dfeead1e786f62e6 100644
--- a/modules/etcd/README.rst
+++ b/modules/etcd/README.rst
@@ -4,15 +4,15 @@ Etcd module
 -----------
 
 The module connects to Etcd peers and watches for configuration change.
-By default, the module looks for the subtree under ``/kresd`` directory,
+By default, the module looks for the subtree under ``/knot-resolver`` directory,
 but you can change this `in the configuration <https://github.com/mah0x211/lua-etcd#cli-err--etcdnew-optiontable->`_.
 
 The subtree structure corresponds to the configuration variables in the declarative style.
 
 .. code-block:: bash
 
-	$ etcdctl set /kresd/net/127.0.0.1 53
-	$ etcdctl set /kresd/cache/size 10000000
+	$ etcdctl set /knot-resolvevr/net/127.0.0.1 53
+	$ etcdctl set /knot-resolver/cache/size 10000000
 
 Configures all listening nodes to following configuration:
 
@@ -28,7 +28,7 @@ Example configuration
 
 	modules = {
 		etcd = {
-			prefix = '/kresd',
+			prefix = '/knot-resolver',
 			peer = 'http://127.0.0.1:7001'
 		}
 	}
diff --git a/modules/etcd/etcd.lua b/modules/etcd/etcd.lua
index 08348529dac9e394d4badff2936a9dda418063c0..4d4bbfba0788f9ace0a90da1c56fe90cbd016129 100644
--- a/modules/etcd/etcd.lua
+++ b/modules/etcd/etcd.lua
@@ -27,7 +27,7 @@ end
 
 function etcd.init()
 	etcd.Etcd = require('etcd.luasocket')
-	etcd.defaults = { prefix = '/kresd' }
+	etcd.defaults = { prefix = '/knot-resolver' }
 end
 
 function etcd.deinit()
diff --git a/modules/policy/README.rst b/modules/policy/README.rst
index 499d15f15623ae948ec8bfc48edd9a5a32e0e8db..722819dbedc62ea2b918189bcf184cb8fcdee3be 100644
--- a/modules/policy/README.rst
+++ b/modules/policy/README.rst
@@ -4,11 +4,15 @@ Query policies
 --------------
 
 This module can block, rewrite, or alter inbound queries based on user-defined policies.
-By default, if no rule applies to a query, rules for special-use domain names are applied, as required by :rfc:`6761`.
 
-You can however extend it e.g. to deflect `Slow drip DNS attacks <https://secure64.com/water-torture-slow-drip-dns-ddos-attack>`_ or gray-list resolution of misbehaving zones.
+Each policy *rule* has two parts: a *filter* and an *action*. A *filter* selects which queries will be affected by the policy, and *action* which modifies queries matching the associated filter. Typically a rule is defined as follows: ``filter(action(action parameters), filter parameters)``. For example, a filter can be ``suffix`` which matches queries whose suffix part is in specified set, and one of possible actions is ``DENY``, which denies resolution. These are combined together into ``policy.suffix(policy.DENY, {todname('badguy.example.')})``. The rule is effective when it is added into rule table using ``policy.add()``, please see `Policy examples`_.
 
-There are several policy filters available in the ``policy.`` table:
+By default, if no rule applies to a query, built-in rules for `special-use <https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml>`_ and `locally-served <http://www.iana.org/assignments/locally-served-dns-zone>`_ domain names are applied. These built-in rules can be overriden using action ``PASS``, see `Policy examples`_ below.
+
+
+Filters
+^^^^^^^
+A *filter* selects which queries will be affected by specified *action*. There are several policy filters available in the ``policy.`` table:
 
 * ``all(action)``
   - always applies the action
@@ -22,10 +26,13 @@ There are several policy filters available in the ``policy.`` table:
   - implements a subset of RPZ_ in zonefile format.  See below for details: :any:`policy.rpz`.
 * custom filter function
 
-There are several actions available in the ``policy.`` table:
+Actions
+^^^^^^^
+An *action* is function which modifies DNS query. There are several actions available in the ``policy.`` table:
 
 * ``PASS`` - let the query pass through; it's useful to make exceptions before wider rules
 * ``DENY`` - reply NXDOMAIN authoritatively
+* ``DENY_MSG(msg)`` - reply NXDOMAIN authoritatively and add explanatory message to additional section
 * ``DROP`` - terminate query resolution and return SERVFAIL to the requestor
 * ``TC`` - set TC=1 if the request came through UDP, forcing client to retry with TCP
 * ``FORWARD(ip)`` - resolve a query via forwarding to an IP while validating and caching locally;
@@ -40,6 +47,8 @@ There are several actions available in the ``policy.`` table:
 
 Most actions stop the policy matching on the query, but "chain actions" allow to keep trying to match other rules, until a non-chain action is triggered.
 
+Also, it is possible to write your own action (i.e. Lua function). It is possible to implement complex heuristics, e.g. to deflect `Slow drip DNS attacks <https://secure64.com/water-torture-slow-drip-dns-ddos-attack>`_ or gray-list resolution of misbehaving zones.
+
 .. warning:: The policy module currently only looks at whole DNS requests.  The rules won't be re-applied e.g. when following CNAMEs.
 
 .. note:: The module (and ``kres``) expects domain names in wire format, not textual representation. So each label in name is prefixed with its length, e.g. "example.com" equals to ``"\7example\3com"``. You can use convenience function ``todname('example.com')`` for automatic conversion.
@@ -76,14 +85,13 @@ TLS Examples
 		{'2001:DB8::d0c', hostname='res.example.', ca_file='/etc/knot-resolver/tlsca.crt'}
 	})
 
+.. _policy_examples:
 
-Other examples
-^^^^^^^^^^^^^^
+Policy examples
+^^^^^^^^^^^^^^^
 
 .. code-block:: lua
 
-	-- Load default policies
-	modules = { 'policy' }
 	-- Whitelist 'www[0-9].badboy.cz'
 	policy.add(policy.pattern(policy.PASS, '\4www[0-9]\6badboy\2cz'))
 	-- Block all names below badboy.cz
diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua
index 95010d4f07ed35576279ae607537ece685c5ff7a..21209b88af8ebd3003bf1c9a2425fc7f3fbf40c9 100644
--- a/modules/policy/policy.lua
+++ b/modules/policy/policy.lua
@@ -57,8 +57,15 @@ local function addr2sock(target, default_port)
 	return sock
 end
 
+-- policy functions are defined below
+local policy = {}
+
+function policy.PASS(state, _)
+	return state
+end
+
 -- Mirror request elsewhere, and continue solving
-local function mirror(target)
+function policy.MIRROR(target)
 	local addr, port = addr_split_port(target, 53)
 	local sink, err = socket_client(addr, port)
 	if not sink then panic('MIRROR target %s is not a valid: %s', target, err) end
@@ -84,7 +91,7 @@ local function set_nslist(qry, list)
 end
 
 -- Forward request, and solve as stub query
-local function stub(target)
+function policy.STUB(target)
 	local list = {}
 	if type(target) == 'table' then
 		for _, v in pairs(target) do
@@ -105,7 +112,7 @@ local function stub(target)
 end
 
 -- Forward request and all subrequests to upstream; validate answers
-local function forward(target)
+function policy.FORWARD(target)
 	local list = {}
 	if type(target) == 'table' then
 		for _, v in pairs(target) do
@@ -188,7 +195,7 @@ local function tls_forward_target_check_syntax(idx, list_entry)
 end
 
 -- Forward request and all subrequests to upstream over TLS; validate answers
-local function tls_forward(target)
+function policy.TLS_FORWARD(target)
 	local sockaddr_c_list = {}
 	local sockaddr_config = {}  -- items: { string_addr=<addr string>, auth_type=<auth type> }
 	local ca_files = {}
@@ -255,7 +262,7 @@ local function tls_forward(target)
 end
 
 -- Rewrite records in packet
-local function reroute(tbl, names)
+function policy.REROUTE(tbl, names)
 	-- Import renumbering rules
 	local ren = require('renumber')
 	local prefixes = {}
@@ -267,7 +274,7 @@ local function reroute(tbl, names)
 end
 
 -- Set and clear some query flags
-local function flags(opts_set, opts_clear)
+function policy.FLAGS(opts_set, opts_clear)
 	return function(_, req)
 		local qry = req:current()
 		ffi.C.kr_qflags_set  (qry.flags, kres.mk_qflags(opts_set   or {}))
@@ -280,8 +287,8 @@ local function mkauth_soa(answer, dname, mname)
 	if mname == nil then
 		mname = dname
 	end
-	return answer:put(dname, 900, answer:qclass(), kres.type.SOA,
-		mname .. '\6nobody\7invalid\0\0\0\0\0\0\0\14\16\0\0\3\132\0\9\58\128\0\0\3\132')
+	return answer:put(dname, 10800, answer:qclass(), kres.type.SOA,
+		mname .. '\6nobody\7invalid\0\0\0\0\1\0\0\14\16\0\0\4\176\0\9\58\128\0\0\42\48')
 end
 
 local dname_localhost = todname('localhost.')
@@ -367,15 +374,6 @@ local function localhost_reversed(_, req)
 	return kres.DONE
 end
 
-local policy = {
-	-- Policies
-	PASS = 1, DENY = 2, DROP = 3, TC = 4, QTRACE = 5,
-	FORWARD = forward, TLS_FORWARD = tls_forward,
-	STUB = stub, REROUTE = reroute, MIRROR = mirror, FLAGS = flags,
-	-- Special values
-	ANY = 0,
-}
-
 -- All requests
 function policy.all(action)
 	return function(_, _) return action end
@@ -450,8 +448,9 @@ local function rpz_parse(action, path)
 	return rules
 end
 
+-- RPZ policy set
 -- Create RPZ from zone file
-local function rpz_zonefile(action, path)
+function policy.rpz(action, path)
 	local rules = rpz_parse(action, path)
 	collectgarbage()
 	return function(_, query)
@@ -465,9 +464,48 @@ local function rpz_zonefile(action, path)
 	end
 end
 
--- RPZ policy set
-function policy.rpz(action, path)
-	return rpz_zonefile(action, path)
+function policy.DENY_MSG(msg)
+	if msg and (type(msg) ~= 'string' or #msg >= 255) then
+		error('DENY_MSG: optional msg must be string shorter than 256 characters')
+        end
+
+	return function (_, req)
+		-- Write authority information
+		local answer = req.answer
+		ffi.C.kr_pkt_make_auth_header(answer)
+		answer:rcode(kres.rcode.NXDOMAIN)
+		answer:begin(kres.section.AUTHORITY)
+		mkauth_soa(answer, answer:qname())
+		if msg then
+			answer:begin(kres.section.ADDITIONAL)
+			answer:put('\11explanation\7invalid', 10800, answer:qclass(), kres.type.TXT,
+				   string.char(#msg) .. msg)
+
+		end
+		return kres.DONE
+	end
+end
+policy.DENY = policy.DENY_MSG() -- compatibility with < 2.0
+
+function policy.DROP(_, _)
+	return kres.FAIL
+end
+
+function policy.TC(state, req)
+	local answer = req.answer
+	if answer.max_size ~= 65535 then
+		answer:tc(1) -- ^ Only UDP queries
+		return kres.DONE
+	else
+		return state
+	end
+end
+
+function policy.QTRACE(_, req)
+	local qry = req:current()
+	req.options.TRACE = true
+	qry.flags.TRACE = true
+	return -- this allows to continue iterating over policy list
 end
 
 -- Evaluate packet in given rules to determine policy action
@@ -478,7 +516,7 @@ function policy.evaluate(rules, req, query, state)
 			local action = rule.cb(req, query)
 			if action ~= nil then
 				rule.count = rule.count + 1
-				local next_state = policy.enforce(state, req, action)
+				local next_state = action(state, req)
 				if next_state then    -- Not a chain rule,
 					return next_state -- stop on first match
 				end
@@ -488,35 +526,6 @@ function policy.evaluate(rules, req, query, state)
 	return
 end
 
--- Enforce policy action
-function policy.enforce(state, req, action)
-	if action == policy.DENY then
-		-- Write authority information
-		local answer = req.answer
-		ffi.C.kr_pkt_make_auth_header(answer)
-		answer:rcode(kres.rcode.NXDOMAIN)
-		answer:begin(kres.section.AUTHORITY)
-		mkauth_soa(answer, '\7blocked\0')
-		return kres.DONE
-	elseif action == policy.DROP then
-		return kres.FAIL
-	elseif action == policy.TC then
-		local answer = req.answer
-		if answer.max_size ~= 65535 then
-			answer:tc(1) -- ^ Only UDP queries
-			return kres.DONE
-		end
-	elseif action == policy.QTRACE then
-		local qry = req:current()
-		req.options.TRACE = true
-		qry.flags.TRACE = true
-		return -- this allows to continue iterating over policy list
-	elseif type(action) == 'function' then
-		return action(state, req)
-	end
-	return state
-end
-
 -- Top-down policy list walk until we hit a match
 -- the caller is responsible for reordering policy list
 -- from most specific to least specific.
@@ -607,7 +616,7 @@ local private_zones = {
 	'100.51.198.in-addr.arpa.',
 	'113.0.203.in-addr.arpa.',
 	'255.255.255.255.in-addr.arpa.',
-	-- RFC7796
+	-- RFC7793
 	'64.100.in-addr.arpa.',
 	'65.100.in-addr.arpa.',
 	'66.100.in-addr.arpa.',
@@ -690,14 +699,22 @@ policy.rules = {}
 policy.postrules = {}
 policy.special_names = {
 	{
-		cb=policy.suffix_common(policy.DENY, private_zones, todname('arpa.')),
+		cb=policy.suffix_common(policy.DENY_MSG(
+			'Blocking is mandated by standards, see references on '
+			.. 'https://www.iana.org/assignments/'
+			.. 'locally-served-dns-zones/locally-served-dns-zones.xhtml'),
+			private_zones, todname('arpa.')),
 		count=0
 	},
 	{
-		cb=policy.suffix(policy.DENY, {
-			todname('test.'),
-			todname('invalid.'),
-			todname('onion.'), -- RFC7686, 2.4
+		cb=policy.suffix(policy.DENY_MSG(
+			'Blocking is mandated by standards, see references on '
+			.. 'https://www.iana.org/assignments/'
+			.. 'special-use-domain-names/special-use-domain-names.xhtml'),
+			{
+				todname('test.'),
+				todname('onion.'),
+				todname('invalid.'),
 			}),
 		count=0
 	},
diff --git a/modules/stats/README.rst b/modules/stats/README.rst
index f6755ac3236f7417c70e23335d1b5fd44086200a..8f5280641f6ba02ae91f1be3eb9d0034b90099ad 100644
--- a/modules/stats/README.rst
+++ b/modules/stats/README.rst
@@ -7,7 +7,7 @@ This modules gathers various counters from the query resolution and server inter
 and offers them as a key-value storage. Any module may update the metrics or simply hook
 in new ones.
 
-.. code-block:: lua
+.. code-block:: none
 
 	-- Enumerate metrics
 	> stats.list()
diff --git a/modules/stats/stats.c b/modules/stats/stats.c
index cff04b24b00c08c16d494f85fecbed370460b3ca..525175422034b9e10c21875eeeaa887c4bd62c15 100644
--- a/modules/stats/stats.c
+++ b/modules/stats/stats.c
@@ -313,7 +313,7 @@ static char* stats_list(void *env, struct kr_module *module, const char *args)
 	size_t args_len = args ? strlen(args) : 0;
 	for (unsigned i = 0; i < metric_const_end; ++i) {
 		struct const_metric_elm *elm = &const_metrics[i];
-		if (strncmp(elm->key, args, args_len) == 0) {
+		if (args && strncmp(elm->key, args, args_len) == 0) {
 			json_append_member(root, elm->key, json_mknumber(elm->val));
 		}
 	}
diff --git a/modules/view/view.lua b/modules/view/view.lua
index f2837fc595661755c40c76319c8b903fabbd7b58..dad097aff25962d0d3f4333fb660fb4da26de19b 100644
--- a/modules/view/view.lua
+++ b/modules/view/view.lua
@@ -1,5 +1,4 @@
 local kres = require('kres')
-local policy = require('policy')
 local ffi = require('ffi')
 local C = ffi.C
 
@@ -91,7 +90,12 @@ view.layer = {
 		local match_cb = evaluate(view, req)
 		if match_cb ~= nil then
 			local action = match_cb(req, req:current())
-			return policy.enforce(state, req, action) or state
+			if action then
+				local next_state = action(state, req)
+				if next_state then    -- Not a chain rule,
+					return next_state -- stop on first match
+				end
+			end
 		end
 		return state
 	end
diff --git a/platform.mk b/platform.mk
index f2657ed15326889adae88e6f4a74d1cfbc2e57cd..d42c66e4ce6429c442f58ac57052be1dad3705e1 100644
--- a/platform.mk
+++ b/platform.mk
@@ -36,6 +36,7 @@ else
 		PLATFORM := Darwin
 		LIBEXT := .dylib
 		MODTYPE := dynamiclib
+		LDFLAGS += -Wl,-export_dynamic
                 # OS X specific hardening since -pie doesn't work
 		ifneq ($(HARDENING),no)
 			BINFLAGS += -Wl,-pie
@@ -45,14 +46,18 @@ else
 		SOVER = $(if $(1), -compatibility_version $(2) -current_version $(1),)
 	else
 		PLATFORM := POSIX
-		LDFLAGS += -pthread -lm -Wl,-E
+		LDFLAGS += -pthread -lm -Wl,--export-dynamic
                 # ELF hardening options
 		ifneq ($(HARDENING),no)
 			BINFLAGS += -pie
 			LDFLAGS += -Wl,-z,relro,-z,now
 		endif
 		ifeq ($(UNAME),Linux)
-		LDFLAGS += -ldl
+			LDFLAGS += -ldl
+		endif
+		ifeq ($(firstword $(shell $(CC) --version)),gcc)
+			# Otherwise Fedora is making kresd symbols inaccessible for modules?
+			CFLAGS += -rdynamic
 		endif
 	endif
 endif
diff --git a/scripts/Dockerfile b/scripts/Dockerfile
index 2ab7c7e66ce624c3f2513a6ece6fb19047ce78cf..f0399f0853a1e4499cc4f9304472232afad8a8f4 100644
--- a/scripts/Dockerfile
+++ b/scripts/Dockerfile
@@ -1,23 +1,25 @@
 FROM alpine:edge
-MAINTAINER Marek Vavrusa <marek.vavrusa@nic.cz>
+MAINTAINER Knot Resolver team <knot-resolver-users@lists.nic.cz>
 
 # Environment
 ENV BUILD_PKGS build-base automake autoconf libtool pkgconfig git luajit-dev libuv-dev gnutls-dev jansson-dev userspace-rcu-dev curl vim bsd-compat-headers
-ENV RUN_PKGS luajit libuv gnutls jansson bash
+ENV RUN_PKGS luajit libuv gnutls jansson bash libstdc++ lua5.1-cqueues lua5.1-http lua5.1-sec lua5.1-socket
 ENV BUILD_IGNORE gmp nettle jansson gnutls lua libuv cmocka
 ENV PKG_CONFIG_PATH /usr/local/lib/pkgconfig
 ENV CFLAGS -O2 -ftree-vectorize -fstack-protector -g
 ENV LDFLAGS -Wl,--as-needed
 
-# Expose port
-EXPOSE 53
+# export DNS over UDP & TCP, DNS-over-TLS, web interface
+EXPOSE 53/UDP 53/TCP 853/TCP 8053/TCP
 
 # Select entrypoint
 WORKDIR /data
-CMD ["/usr/local/sbin/kresd"]
+COPY "config.docker" "/data"
+CMD ["/usr/local/sbin/kresd", "-c", "/data/config.docker"]
 
 # Install dependencies and sources
 RUN \
+apk add -t lua5.1-compat5.3 lua5.1-compat53 && \
 apk --update add ${RUN_PKGS} && \
 apk add --virtual build-dep ${BUILD_PKGS} && \
 git clone --depth 1 --recurse-submodules=modules/policy/lua-aho-corasick \
diff --git a/scripts/config.docker b/scripts/config.docker
new file mode 100644
index 0000000000000000000000000000000000000000..58d8d13b606828bcafaa11e6d6f16fda822badd0
--- /dev/null
+++ b/scripts/config.docker
@@ -0,0 +1,41 @@
+-- Refer to manual: https://knot-resolver.readthedocs.io/en/latest/daemon.html#configuration
+
+-- Listen on all interfaces (localhost would not work in Docker)
+net.listen('0.0.0.0')
+net.listen('0.0.0.0', 853, {tls=true})
+
+-- Auto-maintain root TA
+trust_anchors.file = '/data/root.keys'
+
+-- Load Useful modules
+modules = {
+	'policy',   -- Block queries to local zones/bad sites
+	'stats',    -- Track internal statistics
+	-- Load HTTP module with defaults
+        http = {
+                host = '0.0.0.0',
+                port = 8053,
+        }
+}
+
+-- Smaller cache size
+cache.size = 10 * MB
+
+function print_help()
+	print('\nUsage\n'
+	   .. '=====\n'
+	   .. 'Run this container using command:\n'
+	   .. '$ docker run -Pti cznic/knot-resolver\n'
+	   .. '\n'
+	   .. 'Docker will map ports 53, 853, and 8053 to some other numbers, see\n'
+	   .. '$ docker ps\n'
+	   .. '(column PORTS)\n'
+	   .. '80   -> DNS protocol over UDP and TCP\n'
+	   .. '853  -> DNS-over-TLS protocol\n'
+	   .. '8053 -> web interface\n'
+	   .. '\n'
+	   .. 'For verbose logging enter following command to prompt below:\n'
+	   .. 'verbose(true)\n')
+end
+print_help()
+event.after(11000, print_help)
diff --git a/scripts/kresd.apparmor b/scripts/kresd.apparmor
index 2b5b5f7ff86829531fbc89d88c4f41016e96948f..ad6f911a06202c72ddd7e31f2fd684cc110faff3 100644
--- a/scripts/kresd.apparmor
+++ b/scripts/kresd.apparmor
@@ -7,7 +7,7 @@
   capability net_bind_service,
   capability setgid,
   capability setuid,
-  # seems to be needed during start to read /var/lib/kresd
+  # seems to be needed during start to read /var/lib/knot-resolver
   # while we still run as root.
   capability dac_override,
 
@@ -15,9 +15,9 @@
   network udp,
 
   /proc/sys/net/core/somaxconn r,
-  /etc/kresd/* r, 
-  /var/lib/kresd/ r,
-  /var/lib/kresd/** rwlk,
+  /etc/knot-resolver/* r,
+  /var/lib/knot-resolver/ r,
+  /var/lib/knot-resolver/** rwlk,
 
   # modules
   /usr/lib{,64}/kdns_modules/*.lua r,