diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 00000000..d0baee6f
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,27 @@
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Build documentation in the doc/ directory with Sphinx
+sphinx:
+  configuration: doc/source/conf.py
+
+# Build documentation with MkDocs
+#mkdocs:
+#  configuration: mkdocs.yml
+
+# Optionally build your docs in additional formats such as PDF and ePub
+formats:
+  - pdf
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+  version: 3.7
+  install:
+    - requirements: doc/requirements.txt
+    - requirements: requirements.txt
+    - requirements: requirements-direct.txt
+    - requirements: requirements-frozen.txt
diff --git a/.zuul.yaml b/.zuul.yaml
index 48b299c1..526b5e19 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -11,12 +11,16 @@
 # limitations under the License.
 
 - project:
+    templates:
+      - docs-on-readthedocs
+    vars:
+      rtd_webhook_id: '38575'
+      rtd_project_name: 'airship-promenade'
     check:
       jobs:
         - openstack-tox-pep8
         - airship-promenade-lint-ws
         - airship-promenade-docker-build-gate
-        - airship-promenade-doc-build
         - airship-promenade-chart-build-gate
         - airship-promenade-chart-build-latest-htk
         - airship-promenade-unit-py35
@@ -27,7 +31,6 @@
         - openstack-tox-pep8
         - airship-promenade-lint-ws
         - airship-promenade-docker-build-gate
-        - airship-promenade-doc-build
         - airship-promenade-chart-build-gate
         - airship-promenade-unit-py35
         - airship-promenade-genesis-gate
@@ -88,19 +91,6 @@
     files:
       - ^.*\.py$
 
-- job:
-    name: airship-promenade-doc-build
-    description: |
-      Locally build the documentation to check for errors
-    run: tools/zuul/playbooks/doc-build.yaml
-    timeout: 300
-    nodeset: airship-promenade-single-node
-    irrelevant-files:
-      - ^charts/.*$
-      - ^etc/.*$
-      - ^tests/.*$
-      - ^tools/.*$
-
 - job:
     name: airship-promenade-linter
     run: tools/zuul/playbooks/zuul-linter.yaml
diff --git a/Dockerfile b/Dockerfile
index c04c7c22..0a39beba 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,12 +15,12 @@
 ARG FROM=python:3.6
 FROM ${FROM}
 
-LABEL org.opencontainers.image.authors='airship-discuss@lists.airshipit.org, irc://#airshipit@freenode'
-LABEL org.opencontainers.image.url='https://airshipit.org'
-LABEL org.opencontainers.image.documentation='https://airship-promenade.readthedocs.org'
-LABEL org.opencontainers.image.source='https://git.openstack.org/openstack/airship-promenade'
-LABEL org.opencontainers.image.vendor='The Airship Authors'
-LABEL org.opencontainers.image.licenses='Apache-2.0'
+LABEL org.opencontainers.image.authors='airship-discuss@lists.airshipit.org, irc://#airshipit@freenode' \
+      org.opencontainers.image.url='https://airshipit.org' \
+      org.opencontainers.image.documentation='https://airship-promenade.readthedocs.org' \
+      org.opencontainers.image.source='https://git.openstack.org/openstack/airship-promenade' \
+      org.opencontainers.image.vendor='The Airship Authors' \
+      org.opencontainers.image.licenses='Apache-2.0'
 
 VOLUME /etc/promenade
 VOLUME /target
diff --git a/Makefile b/Makefile
index 19a8c855..684c1248 100644
--- a/Makefile
+++ b/Makefile
@@ -34,90 +34,69 @@ HELM_PIDFILE ?= $(abspath ./.helm-pid)
 
 CHARTS := $(patsubst charts/%/.,%,$(wildcard charts/*/.))
 
-.PHONY: all
 all: charts lint
 
-.PHONY: tests
 tests: external-deps gate-lint
 	tox
 
-.PHONY: tests-security
 tests-security:
 	tox -e bandit
 
-.PHONY: docs
-docs:
+docs: clean
 	tox -e docs
 
-.PHONY: tests-unit
 tests-unit: external-deps
 	tox -e py35
 
-.PHONY: external-deps
 external-deps:
 	./tools/install-external-deps.sh
 
-.PHONY: tests-pep8
 tests-pep8:
 	tox -e pep8
 
 chartbanner:
 	@echo Building charts: $(CHARTS)
 
-.PHONY: charts
 charts: $(CHARTS)
 	@echo Done building charts.
 
-.PHONY: helm-init
 helm-init: $(addprefix helm-init-,$(CHARTS))
 
-.PHONY: helm-init-%
 helm-init-%: helm-serve
 	@echo Initializing chart $*
 	cd charts;if [ -s $*/requirements.yaml ]; then echo "Initializing $*";$(HELM) dep up $*; fi
 
-.PHONY: lint
 lint: helm-lint gate-lint
 
-.PHONY: gate-lint
 gate-lint: gate-lint-deps
 	tox -e gate-lint
 
-.PHONY: gate-lint-deps
 gate-lint-deps:
 	sudo apt-get install -y --no-install-recommends shellcheck
 
-.PHONY: helm-lint
 helm-lint: $(addprefix helm-lint-,$(CHARTS))
 
-.PHONY: helm-lint-%
 helm-lint-%: helm-install helm-init-%
 	@echo Linting chart $*
 	cd charts;$(HELM) lint $*
 
-.PHONY: images
 images: check-docker build_promenade
 
-.PHONY: check-docker
 check-docker:
 	@if [ -z $$(which docker) ]; then \
 		echo "Missing \`docker\` client which is required for development"; \
 		exit 2; \
 	fi
 
-.PHONY: dry-run
 dry-run: $(addprefix dry-run-,$(CHARTS))
 
-.PHONY: dry-run-%
 dry-run-%: helm-lint-%
 	echo Running Dry-Run on chart $*
 	cd charts;$(HELM) template --set pod.resources.enabled=true $*
 
-.PHONY: $(CHARTS)
 $(CHARTS): $(addprefix dry-run-,$(CHARTS)) chartbanner
 	$(HELM) package -d charts charts/$@
 
-.PHONY: build_promenade
 build_promenade:
 ifeq ($(USE_PROXY), true)
 	docker build --network host -t $(IMAGE) --label $(LABEL) \
@@ -145,17 +124,20 @@ ifeq ($(PUSH_IMAGE), true)
 endif
 
 
-.PHONY: helm-serve
 helm-serve: helm-install
 	./tools/helm_tk.sh $(HELM) $(HELM_PIDFILE)
 
-.PHONY: clean
 clean:
+	rm -rf doc/build
 	rm -f charts/*.tgz
 	rm -f charts/*/requirements.lock
 	rm -rf charts/*/charts
 
 # Install helm binary
-.PHONY: helm-install
 helm-install:
 	tools/helm_install.sh $(HELM)
+
+.PHONY: $(CHARTS) all build_promenade charts check-docker clean docs \
+  dry-run dry-run-% external-deps gate-lint gate-lint-deps helm-init \
+  helm-init-% helm-install helm-lint helm-lint-% helm-serve images \
+  lint tests tests-pep8 tests-security tests-unit
diff --git a/doc/source/exceptions.rst b/doc/source/exceptions.rst
index 0826ba7f..5f306f28 100644
--- a/doc/source/exceptions.rst
+++ b/doc/source/exceptions.rst
@@ -18,32 +18,34 @@
 Promenade Exceptions
 ====================
 
-.. autoexception:: promenade.exceptions.ApiError
+.. currentmodule:: promenade.exceptions
+
+.. autoexception:: ApiError
    :members:
    :show-inheritance:
    :undoc-members:
 
-.. autoexception:: promenade.exceptions.InvalidFormatError
+.. autoexception:: InvalidFormatError
    :members:
    :show-inheritance:
    :undoc-members:
 
-.. autoexception:: promenade.exceptions.ValidationException
+.. autoexception:: ValidationException
    :members:
    :show-inheritance:
    :undoc-members:
 
-.. autoexception:: promenade.exceptions.KubernetesConfigException
+.. autoexception:: KubernetesConfigException
    :members:
    :show-inheritance:
    :undoc-members:
 
-.. autoexception:: promenade.exceptions.KubernetesApiError
+.. autoexception:: KubernetesApiError
    :members:
    :show-inheritance:
    :undoc-members:
 
-.. autoexception:: promenade.exceptions.NodeNotFoundException
+.. autoexception:: NodeNotFoundException
    :members:
    :show-inheritance:
    :undoc-members:
diff --git a/tools/zuul/playbooks/doc-build.yaml b/tools/zuul/playbooks/doc-build.yaml
deleted file mode 100644
index b7b2aa16..00000000
--- a/tools/zuul/playbooks/doc-build.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-- hosts: primary
-  tasks:
-    - name: Build documents locally
-      make:
-        chdir: "{{ zuul.project.src_dir }}"
-        target: docs
-      register: result
-      failed_when: result.failed
diff --git a/tox.ini b/tox.ini
index 00eea9d0..79473e02 100644
--- a/tox.ini
+++ b/tox.ini
@@ -36,7 +36,7 @@ deps =
     -r{toxinidir}/doc/requirements.txt
 commands =
     rm -rf doc/build
-    sphinx-build -W -b html doc/source doc/build
+    sphinx-build -W -b html doc/source doc/build/html
 
 [testenv:fmt]
 deps =