diff --git a/.gitignore b/.gitignore
index f5fc2ae550b6b092eb911c410a320a365a926372..cb16d33b84cf83fbe7afbaf48715c69302b8f3d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,6 @@
 .eggs/
 __pycache__
 *.egg-info/
-version.txt
\ No newline at end of file
+version.txt
+.tox
+docs/apidoc
diff --git a/docs/Makefile b/docs/Makefile
index 5a1770d9e96add6c8f85680d709dafe608b3df65..8c2f78b75780aeaf62e55d65986b2f2b28db2c52 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -3,17 +3,23 @@ SPHINXBUILD = python3 -c 'import sphinx, sys; sys.exit(sphinx.main(sys.argv))'
 SOURCEDIR = .
 BUILDDIR = _build
 HTMLDIR = $(BUILDDIR)/html
+SWHPKGDIR = `python3 -c 'import swh; print(swh.__path__[0])'`
 
 INSTALL_HOST = pergamon.internal.softwareheritage.org
 INSTALL_DIR = /srv/softwareheritage/docs/webroot/devel
 INSTALL_GROUP = swhdev
 INSTALL_PERMS = g+rwX
 
-html: fix-indices-stamp sphinx/html
+SPHINXAPIDOC = sphinx-apidoc
+APIDOC_DIR = apidoc
+APIDOC_OPTS = --ext-viewcode
+APIDOC_EXCLUDES = */tests */tests/* */*/tests/* */*/*/tests/*
+APIDOC_EXCLUDES += */migrations */migrations/* */*/migrations/* */*/*/migrations/*
+APIDOC_SWH_EXCLUDES = $(patsubst %,$(SWHPKGDIR)/%,$(APIDOC_EXCLUDES))
 
-fix-indices-stamp: sphinx/html
-	bin/copy-and-fix-subprojects-indices
-	touch $@
+apidoc_dep = apidoc-stamp
+
+html: sphinx/html
 
 sphinx/html: links-stamp apidoc-stamp images-stamp rec-build-stamp
 
@@ -21,8 +27,9 @@ links-stamp:
 	bin/ln-sphinx-subprojects
 	touch $@
 
+apidoc: $(apidoc_dep)
 apidoc-stamp:
-	$(MAKE) -C ../../ docs-apidoc
+	$(SPHINXAPIDOC) $(APIDOC_OPTS) -o $(APIDOC_DIR) $(SWHPKGDIR) $(APIDOC_SWH_EXCLUDES)
 	touch $@
 
 images-stamp:
@@ -33,13 +40,14 @@ images-stamp:
 # non-sphinx managed documentation artifacts (e.g., schema diagrams) are also
 # built.
 rec-build-stamp: $(wildcard ../../swh-*/docs/*.rst)
-	$(MAKE) -C ../../ docs
+	$(MAKE) -C ../../ docs-assets
 	touch $@
 
 clean: sphinx/clean
 	bin/ln-sphinx-subprojects --remove
 	$(MAKE) -C images clean
 	rm -f *-stamp
+	rm -f $(APIDOC_DIR)/*
 
 distclean: clean
 	make -C ../../ docs-clean
diff --git a/requirements-swh.txt b/requirements-swh.txt
index 2d1bbce85d814754c66a7c4926e55673df3da4fd..6d154199c3b607c9a32b735cd010491c2f19e78c 100644
--- a/requirements-swh.txt
+++ b/requirements-swh.txt
@@ -1 +1,22 @@
 # Add here internal Software Heritage dependencies, one per line.
+swh-core
+swh-model
+swh-objstorage[testing]
+swh-scheduler
+swh-storage[schemadata]
+swh-loader-core
+swh-lister
+swh-journal
+swh-vault
+swh-loader-dir
+swh-loader-tar
+swh-loader-pypi
+swh-loader-debian
+swh-loader-mercurial
+swh-loader-svn
+swh-loader-git
+swh-archiver
+swh-web
+swh-deposit
+swh-indexer
+swh-mirror-forge
diff --git a/swh/docs/django_settings.py b/swh/docs/django_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..07a9a3cfa3af51535bd111025a8d8ec13dfd66a0
--- /dev/null
+++ b/swh/docs/django_settings.py
@@ -0,0 +1,4 @@
+#from swh.web.settings.development import *  # noqa
+from swh.deposit.settings.development import *  # noqa
+
+SECRET_KEY = 'change me'
diff --git a/swh/docs/sphinx/conf.py b/swh/docs/sphinx/conf.py
index 41e8c49044acf61a77002b0d2e0dc528efbada0b..23ed0b870c66fe60e7f0a80070b405ae3f651510 100755
--- a/swh/docs/sphinx/conf.py
+++ b/swh/docs/sphinx/conf.py
@@ -20,6 +20,7 @@ extensions = ['sphinx.ext.autodoc',
               'sphinxcontrib.httpdomain',
               'sphinx.ext.extlinks',
               'sphinxcontrib.images',
+              'sphinx.ext.viewcode',
               ]
 
 # Add any paths that contain templates here, relative to this directory.
@@ -40,8 +41,9 @@ master_doc = 'index'
 
 # A string of reStructuredText that will be included at the beginning of every
 # source file that is read.
+# A bit hackish but should work both for each swh package and the whole swh-doc
 rst_prolog = '''
-.. include:: /swh_substitutions
+.. include:: /../../swh-docs/docs/swh_substitutions
 '''
 
 # The version info for the project you're documenting, acts as replacement for
@@ -130,14 +132,9 @@ extlinks = {}
 # hack to set the adequate django settings when building global swh doc
 # to avoid build errors
 def source_read_handler(app, docname, source):
-    if 'swh-deposit' in docname:
-        os.environ.setdefault('DJANGO_SETTINGS_MODULE',
-                              'swh.deposit.settings.development')
-        django.setup()
-    elif 'swh-web' in docname:
-        os.environ.setdefault('DJANGO_SETTINGS_MODULE',
-                              'swh.web.settings.development')
-        django.setup()
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE',
+                          'swh.docs.django_settings')
+    django.setup()
 
 
 def setup(app):
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000000000000000000000000000000000000..c58ec851ae9dbc85003f89949c0cec2a12987fe3
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,17 @@
+[tox]
+envlist=flake8
+
+[testenv:sphinx]
+deps =
+  django < 2
+  .[testing]
+  pifpaf
+commands =
+  {envpython} -m pifpaf run postgresql -- make -C docs html
+
+[testenv:flake8]
+skip_install = true
+deps =
+  flake8
+commands =
+  {envpython} -m flake8