diff --git a/.ci/build_doc.sh b/.ci/build_doc.sh
index e15a2d163c9edf257b430ffd0fd916304d669949..622c1749fec495a0e671a9dddcec2507b7f671d3 100644
--- a/.ci/build_doc.sh
+++ b/.ci/build_doc.sh
@@ -2,7 +2,8 @@
 
 set -e
 
-pip install -r requirements-dev.txt
-python setup.py doc
-mkdir -p public
-mv doc/html/* public/
+source /test.env/bin/activate
+
+pip install ".[doc]"
+
+sphinx build doc public
diff --git a/.ci/build_pypi_dist.sh b/.ci/build_pypi_dist.sh
index 14cee6119baa4e84c55cff1850da7204bdb43391..07557488bc1cd2fbe0179b0466c7ff9c4a0f6917 100644
--- a/.ci/build_pypi_dist.sh
+++ b/.ci/build_pypi_dist.sh
@@ -2,9 +2,11 @@
 
 set -e
 
-pip install wheel setuptools twine
-python setup.py sdist
-python setup.py bdist_wheel
+source /test.venv/bin/activate
+
+pip install --upgrade pip wheel setuptools twine build
+
+python -m build
 twine check dist/*
 
 # do a test install from both source and wheel
diff --git a/.ci/setup_ssh.sh b/.ci/setup_ssh.sh
index 9c07270b15dc173ee0001d926036d70952e97990..fa62521056a1ad9c626f6c9e59d2f1c4a1a99629 100644
--- a/.ci/setup_ssh.sh
+++ b/.ci/setup_ssh.sh
@@ -46,10 +46,6 @@ if [[ -f /.dockerenv ]]; then
  echo "$SSH_PRIVATE_KEY_GIT"          > $HOME/.ssh/id_git;
  echo "$SSH_PRIVATE_KEY_FSL_DOWNLOAD" > $HOME/.ssh/id_fsl_download;
 
- if [[ "$CI_PROJECT_PATH" == "$UPSTREAM_PROJECT" ]]; then
-   echo "$SSH_PRIVATE_KEY_DOC_DEPLOY"  > $HOME/.ssh/id_doc_deploy;
- fi;
-
  chmod go-rwx $HOME/.ssh/id_*;
 
  ssh-add $HOME/.ssh/id_git;
@@ -59,21 +55,10 @@ if [[ -f /.dockerenv ]]; then
    ssh-add $HOME/.ssh/id_doc_deploy;
  fi
 
- ssh-keyscan ${UPSTREAM_URL##*@} >> $HOME/.ssh/known_hosts;
- ssh-keyscan ${DOC_HOST##*@}     >> $HOME/.ssh/known_hosts;
- ssh-keyscan ${FSL_HOST##*@}     >> $HOME/.ssh/known_hosts;
+ ssh-keyscan ${FSL_HOST##*@} >> $HOME/.ssh/known_hosts;
 
  touch $HOME/.ssh/config;
 
- echo "Host ${UPSTREAM_URL##*@}"                    >> $HOME/.ssh/config;
- echo "    User ${UPSTREAM_URL%@*}"                 >> $HOME/.ssh/config;
- echo "    IdentityFile $HOME/.ssh/id_git"          >> $HOME/.ssh/config;
-
- echo "Host docdeploy"                              >> $HOME/.ssh/config;
- echo "    HostName ${DOC_HOST##*@}"                >> $HOME/.ssh/config;
- echo "    User ${DOC_HOST%@*}"                     >> $HOME/.ssh/config;
- echo "    IdentityFile $HOME/.ssh/id_doc_deploy"   >> $HOME/.ssh/config;
-
  echo "Host fsldownload"                            >> $HOME/.ssh/config;
  echo "    HostName ${FSL_HOST##*@}"                >> $HOME/.ssh/config;
  echo "    User ${FSL_HOST%@*}"                     >> $HOME/.ssh/config;
diff --git a/.ci/test_template.sh b/.ci/test_template.sh
index f0a8c7103e835411884441f2da6464c17af167c3..2b95f300ae14b1dd04f40358bd31f3ca23dae730 100644
--- a/.ci/test_template.sh
+++ b/.ci/test_template.sh
@@ -2,34 +2,14 @@
 
 set -e
 
-# Temporary: this should be done
-# in docker image definition
-apt install -y locales
-locale-gen en_US.UTF-8
-locale-gen en_GB.UTF-8
-update-locale
-
-# If running on a fork repository, we merge in the
-# upstream/master branch. This is done so that merge
-# requests from fork to the parent repository will
-# have unit tests run on the merged code, something
-# which gitlab CE does not currently do for us.
-if [[ "$CI_PROJECT_PATH" != "$UPSTREAM_PROJECT" ]]; then
-  git fetch upstream;
-  git merge --no-commit --no-ff -s recursive -X ours upstream/master;
-fi;
-
 source /test.venv/bin/activate
 
-pip install --retries 10 -r requirements.txt
-pip install --retries 10 -r requirements-extra.txt
-pip install --retries 10 -r requirements-dev.txt
+pip install ".[extra,test,style]"
 
 # style stage
-if [ "$TEST_STYLE"x != "x" ]; then pip install --retries 10 pylint flake8; fi;
 if [ "$TEST_STYLE"x != "x" ]; then flake8                           fsl || true; fi;
 if [ "$TEST_STYLE"x != "x" ]; then pylint --output-format=colorized fsl || true; fi;
-if [ "$TEST_STYLE"x != "x" ]; then exit 0; fi
+if [ "$TEST_STYLE"x != "x" ]; then exit 0;                                       fi;
 
 # We need the FSL atlases for the atlas
 # tests, and need $FSLDIR to be defined
@@ -37,7 +17,8 @@ export FSLDIR=/fsl/
 mkdir -p $FSLDIR/data/
 rsync -rv "fsldownload:$FSL_ATLAS_DIR" "$FSLDIR/data/atlases/"
 
-# Finally, run the damned tests.
+# Run the tests. Suppress coverage
+# reporting until after we're finished.
 TEST_OPTS="--cov-report= --cov-append"
 
 # We run some tests under xvfb-run