diff --git a/roles/ensure-sphinx/README.rst b/roles/ensure-sphinx/README.rst
index a0922587a..fff853275 100644
--- a/roles/ensure-sphinx/README.rst
+++ b/roles/ensure-sphinx/README.rst
@@ -16,6 +16,11 @@ All pip installs are done with a provided constraints file, if given.
 
    List of python packages to install for building docs.
 
+.. zuul:rolevar:: sphinx_python
+   :default: python2
+
+   Version of python to use, either ``python2`` or ``python3``.
+
 .. zuul:rolevar:: zuul_work_virtualenv
    :default: ~/.venv
 
diff --git a/roles/ensure-sphinx/defaults/main.yaml b/roles/ensure-sphinx/defaults/main.yaml
index ddd3976cd..661a8dd51 100644
--- a/roles/ensure-sphinx/defaults/main.yaml
+++ b/roles/ensure-sphinx/defaults/main.yaml
@@ -1,4 +1,5 @@
 zuul_work_dir: "{{ zuul.project.src_dir }}"
 zuul_work_virtualenv: "{{ ansible_user_dir }}/.venv"
+sphinx_python: python2
 doc_building_packages:
   - sphinx
diff --git a/roles/ensure-sphinx/tasks/main.yaml b/roles/ensure-sphinx/tasks/main.yaml
index 819207931..422c20e80 100644
--- a/roles/ensure-sphinx/tasks/main.yaml
+++ b/roles/ensure-sphinx/tasks/main.yaml
@@ -9,36 +9,37 @@
   include_role:
     name: find-constraints
 
-- name: Install virtualenv and doc requirements files if found
+# We're not using with_first_found because the files are remote, not local.
+# We want to use doc/requirements.txt if it exists or fallback to
+# test-requirements.txt.
+- name: Get requirements files
   shell:
     executable: /bin/bash
     chdir: "{{ zuul_work_dir }}"
-    # NOTE(mordred) There is a bug in ansible-lint that mistakenly detects
-    # setting the VENV variable below as an error if it occurs on the fist
-    # line. Work around that by putting a comment as the first line until we
-    # can get a fix upstream.
     cmd: |
-      # Create virtualenv is it does not already exist
-      VENV={{ zuul_work_virtualenv }}
-      if [ ! -d $VENV ] ; then
-          virtualenv $VENV
-      fi
-      source $VENV/bin/activate
-      # skipping requirements.txt as it gets picked up by installing the
-      # python package itself
       for f in doc/requirements.txt test-requirements.txt ; do
           if [ -f $f ] ; then
-              pip install $CONSTRAINTS -r $f
+              echo $f
               break
           fi
       done
-  environment:
-    CONSTRAINTS: "{{ upper_constraints|default('') }}"
+  failed_when: "'requirements.txt' not in requirements_file.stdout"
+  register: requirements_file
+
+# TODO(dmsimard) Don't assume virtualenv is installed
+- name: Initialize virtual environment
+  pip:
+    requirements: "{{ requirements_file.stdout }}"
+    chdir: "{{ zuul_work_dir }}"
+    virtualenv: "{{ zuul_work_virtualenv }}"
+    virtualenv_python: "{{ sphinx_python }}"
+    extra_args: "{{ upper_constraints | default(omit) }}"
 
 - name: Install doc building packages
   pip:
     name: "{{ item }}"
     chdir: "{{ zuul_work_dir }}"
     virtualenv: "{{ zuul_work_virtualenv }}"
-    extra_args: "{{ upper_constraints|default(omit) }}"
+    virtualenv_python: "{{ sphinx_python }}"
+    extra_args: "{{ upper_constraints | default(omit) }}"
   with_items: "{{ doc_building_packages }}"
diff --git a/roles/install-if-python/tasks/main.yaml b/roles/install-if-python/tasks/main.yaml
index 27d45eef5..4175b5c5f 100644
--- a/roles/install-if-python/tasks/main.yaml
+++ b/roles/install-if-python/tasks/main.yaml
@@ -28,7 +28,7 @@
 - name: Install requirements if they exist
   pip:
     chdir: "{{ zuul_work_dir }}"
-    virtualenv: "{{ ansible_user_dir }}/.venv"
+    virtualenv: "{{ zuul_work_virtualenv }}"
     requirements: requirements.txt
     extra_args: "{{ upper_constraints|default(omit) }}"
   register: requirements_install
@@ -45,7 +45,7 @@
 # the ChangeLog to have been generated.
 - name: Make sdist to generate ChangeLog
   command:
-    cmd: "{{ ansible_user_dir }}/.venv/bin/python setup.py sdist"
+    cmd: "{{ zuul_work_virtualenv }}/bin/python setup.py sdist"
     chdir: "{{ zuul_work_dir }}"
   when:
     - install_package
@@ -61,7 +61,7 @@
 - name: Install the project if it is a Python project
   pip:
     chdir: "{{ zuul_work_dir }}"
-    virtualenv: "{{ ansible_user_dir }}/.venv"
+    virtualenv: "{{ zuul_work_virtualenv }}"
     name: .
     extra_args: --no-deps
   when:
diff --git a/roles/sphinx/tasks/main.yaml b/roles/sphinx/tasks/main.yaml
index bdf773bf5..b32f9b078 100644
--- a/roles/sphinx/tasks/main.yaml
+++ b/roles/sphinx/tasks/main.yaml
@@ -10,11 +10,14 @@
     sphinx_warning_is_error: "{{ check_result.warning_is_error }}"
 
 - name: Run sphinx
-  command:
-    cmd: >
-      {{ zuul_work_virtualenv }}/bin/sphinx-build
-        -b {{ item }}
-        {% if sphinx_warning_is_error %} -W {% endif %}
+  shell:
+    executable: /bin/bash
+    cmd: |
+      # Source the activate file so that sphinx subcommands have the correct
+      # paths set.
+      source {{ zuul_work_virtualenv }}/bin/activate
+      sphinx-build -b {{ item }} \
+        {% if sphinx_warning_is_error %} -W {% endif %} \
         {{ sphinx_source_dir }} {{ sphinx_build_dir }}/{{ item }}
     chdir: "{{ zuul_work_dir }}"
   with_items: "{{ sphinx_builders }}"
diff --git a/zuul.yaml b/zuul.yaml
index b82007bbe..9b07b5ea9 100644
--- a/zuul.yaml
+++ b/zuul.yaml
@@ -174,6 +174,11 @@
          Optional path to a pip constraints file for installing python
          libraries.
 
+      .. zuul:jobvar:: sphinx_python
+          :default: python2
+
+         Version of python to use, either ``python2`` or ``python3``.
+
       .. zuul:jobvar:: zuul_work_dir
          :default: {{ zuul.project.src_dir }}