diff --git a/.gitignore b/.gitignore
index c4e0cb2be054db0d9ebd1db26dbbb9f6aa17aeb6..08f968f943b34f932160cf96bfd2222642d1a1d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,7 +62,6 @@
 /doc/upgrade.rst
 /doc/hs-lint.html
 /doc/*.in
-/doc/*.png
 
 # doc/examples
 /doc/examples/bash_completion
diff --git a/Makefile.am b/Makefile.am
index 41968d23fa715316b533b4b9550d1bd69c9f3b87..84e0743ba1b4abd2936bcac2e42b025d8a09abdc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -150,7 +150,6 @@ APIDOC_PY_DIR = $(APIDOC_DIR)/py
 APIDOC_HS_DIR = $(APIDOC_DIR)/hs
 
 MAINTAINERCLEANFILES = \
-	$(docpng) \
 	$(maninput) \
 	doc/install-quick.rst \
 	doc/news.rst \
@@ -582,7 +581,7 @@ $(RUN_IN_TEMPDIR): | stamp-directories
 # has to exist in order for the sphinx module to be loaded
 # successfully, but we certainly don't want the docs to be rebuilt if
 # it changes
-doc/html/index.html: $(docrst) $(docpng) doc/conf.py configure.ac \
+doc/html/index.html: $(docrst) $(docdot) doc/conf.py configure.ac \
 	$(RUN_IN_TEMPDIR) lib/build/sphinx_ext.py \
 	lib/build/shell_example_lexer.py lib/opcodes.py lib/ht.py \
 	| $(BUILT_PYTHON_SOURCES)
@@ -593,6 +592,7 @@ doc/html/index.html: $(docrst) $(docpng) doc/conf.py configure.ac \
 	    -d . \
 	    -D version="$(VERSION_MAJOR).$(VERSION_MINOR)" \
 	    -D release="$(PACKAGE_VERSION)" \
+	    -D graphviz_dot="$(DOT)" \
 	    $(abs_top_srcdir)/doc $(CURDIR)/doc/html
 	rm -f doc/html/.buildinfo doc/html/objects.inv
 	touch $@
@@ -616,8 +616,6 @@ docdot = \
 	doc/design-2.1-lock-acquire.dot \
 	doc/design-2.1-lock-release.dot
 
-docpng = $(patsubst %.dot,%.png,$(docdot))
-
 # Things to build but not to install (add it to EXTRA_DIST if it should be
 # distributed)
 noinst_DATA = \
@@ -818,7 +816,6 @@ EXTRA_DIST = \
 	tools/kvm-ifup.in \
 	tools/vcluster-setup.in \
 	$(docdot) \
-	$(docpng) \
 	$(docrst) \
 	doc/conf.py \
 	doc/html \
@@ -1192,10 +1189,6 @@ doc/examples/bash_completion doc/examples/bash_completion-debug: \
 	PYTHONPATH=. $(RUN_IN_TEMPDIR) \
 	  $(CURDIR)/$(BUILD_BASH_COMPLETION) $(BC_ARGS) > $@
 
-doc/%.png: doc/%.dot
-	@test -n "$(DOT)" || { echo 'dot' not found during configure; exit 1; }
-	$(DOT) -Tpng -o $@ $<
-
 man/%.gen: man/%.rst lib/query.py lib/build/sphinx_ext.py \
 	lib/build/shell_example_lexer.py \
 	| $(RUN_IN_TEMPDIR) $(BUILT_PYTHON_SOURCES)
diff --git a/doc/design-2.0.rst b/doc/design-2.0.rst
index 75d1859e2fcba6a1dd207d7f2ffc0f25cc17e291..8a7ebcba968683b4c856bbc9110709956d6069a1 100644
--- a/doc/design-2.0.rst
+++ b/doc/design-2.0.rst
@@ -102,7 +102,7 @@ these operations are scheduled internally will change radically.
 
 The new design will change the cluster architecture to:
 
-.. image:: arch-2.0.png
+.. graphviz:: arch-2.0.dot
 
 This differs from the 1.2 architecture by the addition of the master
 daemon, which will be the only entity to talk to the node daemons.
diff --git a/doc/design-2.1-lock-acquire.dot b/doc/design-2.1-lock-acquire.dot
index b90808ba1705b3001917af7464ffbab1a35aaa65..2e4cd3d076e4d2a7584a5312cb01d646a110c95f 100644
--- a/doc/design-2.1-lock-acquire.dot
+++ b/doc/design-2.1-lock-acquire.dot
@@ -11,10 +11,10 @@ digraph "design-2.1-lock-acquire" {
   remove_from_queue[label="Remove from queue"]
 
   /* Conditions */
-  alone[label="Empty queue and can acquire?", shape=diamond]
-  have_timeout[label="Do I have timeout?", shape=diamond]
+  alone[label="Empty queue\nand can acquire?", shape=diamond]
+  have_timeout[label="Do I have\ntimeout?", shape=diamond]
   top_of_queue_and_can_acquire[
-    label="On top of queue and can acquire lock?",
+    label="On top of queue and\ncan acquire lock?",
     shape=diamond,
     ]
 
diff --git a/doc/design-2.1-lock-release.dot b/doc/design-2.1-lock-release.dot
index 17ca192a2f493422353a5f50306433bbcdedf467..9bfef248b8056d1a3f19e7eeb260ea83877b6ee8 100644
--- a/doc/design-2.1-lock-release.dot
+++ b/doc/design-2.1-lock-release.dot
@@ -10,7 +10,7 @@ digraph "design-2.1-lock-release" {
   success[label="Success"]
 
   /* Conditions */
-  have_pending[label="Any pending acquires?", shape=diamond]
+  have_pending[label="Any pending\nacquires?", shape=diamond]
   was_active_queue[
     label="Was active condition\nfor shared acquires?",
     shape=diamond,
diff --git a/doc/design-2.1.rst b/doc/design-2.1.rst
index d30be8677e1449b550690124d6e320523eb50cb8..8a120119712426954656d18d6d5a420cf284a190 100644
--- a/doc/design-2.1.rst
+++ b/doc/design-2.1.rst
@@ -229,7 +229,7 @@ Optimization: There's no need to touch the queue if there are no pending
 acquires and no current holders. The caller can have the lock
 immediately.
 
-.. image:: design-2.1-lock-acquire.png
+.. graphviz:: design-2.1-lock-acquire.dot
 
 
 Release
@@ -244,7 +244,7 @@ inactive condition will be made active. This ensures fairness with
 exclusive locks by forcing consecutive shared acquires to wait in the
 queue.
 
-.. image:: design-2.1-lock-release.png
+.. graphviz:: design-2.1-lock-release.dot
 
 
 Delete