diff --git a/Makefile b/Makefile
index e2ce7cea5c881061af36f526a303a5bb6d7167fe..6d8394aa64e8f6b79b30d1f46f0747577d2cc0fa 100644
--- a/Makefile
+++ b/Makefile
@@ -200,33 +200,13 @@ bootstrap-jsons: $(BOOTSTRAP_GHC_VERSIONS:%=bootstrap-json-%)
 # documentation
 ##############################################################################
 
-# TODO: when we have sphinx-build2 ?
-SPHINXCMD:=sphinx-build
-# Flag -n ("nitpick") warns about broken references
-# Flag -W turns warnings into errors
-# Flag --keep-going continues after errors
-SPHINX_FLAGS:=-n -W --keep-going -E
-SPHINX_HTML_OUTDIR:=dist-newstyle/doc/users-guide
-USERGUIDE_STAMP:=$(SPHINX_HTML_OUTDIR)/index.html
-
-# do pip install every time so we have up to date requirements when we build
-users-guide: .python-sphinx-virtualenv/bin/activate $(USERGUIDE_STAMP)
-$(USERGUIDE_STAMP) : doc/*.rst
-	mkdir -p $(SPHINX_HTML_OUTDIR)
-	(. ./.python-sphinx-virtualenv/bin/activate && pip install -r doc/requirements.txt && $(SPHINXCMD) $(SPHINX_FLAGS) doc $(SPHINX_HTML_OUTDIR))
-
-.python-sphinx-virtualenv/bin/activate:
-	python3 -m venv .python-sphinx-virtualenv
-	(. ./.python-sphinx-virtualenv/bin/activate)
-
-# This goal is intended for manual invocation, always rebuilds.
-.PHONY: users-guide-requirements
-users-guide-requirements: doc/requirements.txt
+.PHONY: users-guide
+users-guide:
+	$(MAKE) -C doc users-guide
 
-.PHONY: doc/requirements.txt
-doc/requirements.txt: .python-sphinx-virtualenv/bin/activate
-	. .python-sphinx-virtualenv/bin/activate \
-	  && make -C doc build-and-check-requirements
+.PHONY: users-guide-requirements
+users-guide-requirements:
+	$(MAKE) -C doc users-guide-requirements
 
 ifeq ($(shell uname), Darwin)
 PROCS := $(shell sysctl -n hw.logicalcpu)
diff --git a/doc/Makefile b/doc/Makefile
index 5ef45877223c775ac85dcb94b4a9c4011b65f64c..58c2e4f4656103d4827b8d93fe05f9c36b32d744 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -12,6 +12,40 @@
 #
 #
 SKJOLD_GITHUB_API_TOKEN ?= ${GITHUB_TOKEN}
+# TODO: when we have sphinx-build2 ?
+SPHINXCMD:=sphinx-build
+# Flag -n ("nitpick") warns about broken references
+# Flag -W turns warnings into errors
+# Flag --keep-going continues after errors
+SPHINX_FLAGS:=-n -W --keep-going -E
+SPHINX_HTML_OUTDIR:=../dist-newstyle/doc/users-guide
+USERGUIDE_STAMP:=$(SPHINX_HTML_OUTDIR)/index.html
+PYTHON_VIRTUALENV_ACTIVATE:=../.python-sphinx-virtualenv/bin/activate
+
+# Python virtual environment
+##############################################################################
+
+# Create a python virtual environment in the root of the cabal repository.
+$(PYTHON_VIRTUALENV_ACTIVATE):
+	python3 -m venv ../.python-sphinx-virtualenv
+	(. $(PYTHON_VIRTUALENV_ACTIVATE))
+
+# Users guide
+##############################################################################
+
+# do pip install every time so we have up to date requirements when we build
+users-guide: $(PYTHON_VIRTUALENV_ACTIVATE) $(USERGUIDE_STAMP)
+$(USERGUIDE_STAMP) : *.rst
+	mkdir -p $(SPHINX_HTML_OUTDIR)
+	(. $(PYTHON_VIRTUALENV_ACTIVATE) && pip install -r requirements.txt && $(SPHINXCMD) $(SPHINX_FLAGS) . $(SPHINX_HTML_OUTDIR))
+
+# Requirements
+##############################################################################
+
+##
+# This goal is intended for manual invocation, always rebuilds.
+.PHONY: users-guide-requirements
+users-guide-requirements: requirements.txt
 
 .PHONY: build-and-check-requirements
 build-and-check-requirements: requirements.txt check-requirements
@@ -21,8 +55,8 @@ build-and-check-requirements: requirements.txt check-requirements
 # requirements.txt is generated from requirements.in
 # via pip-compile included in the pip-tools package.
 # See https://modelpredict.com/wht-requirements-txt-is-not-enough
-requirements.txt: requirements.in
-	. ../.python-sphinx-virtualenv/bin/activate \
+requirements.txt: requirements.in $(PYTHON_VIRTUALENV_ACTIVATE)
+	. $(PYTHON_VIRTUALENV_ACTIVATE) \
 	  && pip install --upgrade pip \
 	  && pip install pip-tools \
 	  && pip-compile requirements.in
@@ -37,7 +71,7 @@ check-requirements:
 	  echo "WARNING: Neither SKJOLD_GITHUB_API_TOKEN nor GITHUB_TOKEN is set." \
 	; echo "Vulnerability check via skjold might fail when using the GitHub GraphQL API." \
 	; fi
-	. ../.python-sphinx-virtualenv/bin/activate \
+	. $(PYTHON_VIRTUALENV_ACTIVATE) \
 	  && pip install skjold \
 	  && skjold audit
 # NB: For portability, we use '.' (sh etc.) instead of 'source' (bash).
diff --git a/doc/README.md b/doc/README.md
index 219b177c87f74faa4e2a2722c24d2671b8a806f2..c3b25787ce6525427fc21a462b0a08b2a06c4510 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -11,8 +11,7 @@ http://cabal.readthedocs.io/
 
 ### How to build it
 
-Building the documentation requires Python 3 and PIP. From the root of cabal
-repository run:
+Building the documentation requires Python 3 and PIP. Run the following command either from the root of the cabal repository or from the `docs/` subdirectory:
 
 ``` console
 make users-guide
@@ -33,7 +32,7 @@ generation step run
 > make users-guide-requirements
 ```
 
-in the root of the repository.
+either from the root of the cabal repository or from the `docs/` subdirectory.
 
 Note that generating `requirements.txt` is sensitive to the Python version.
 The version currently used is stamped at the top of `requirements.txt`.