diff --git a/tools/jenkins/build_docker_and_run_tests.sh b/tools/jenkins/build_docker_and_run_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..fa6bd44e180cd8d9a4181c1e35b32a5f4b060f55
--- /dev/null
+++ b/tools/jenkins/build_docker_and_run_tests.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# This script is invoked by run_tests.py to accommodate "test under docker"
+# scenario. You should never need to call this script on your own.
+
+set -ex
+
+cd `dirname $0`/../..
+git_root=`pwd`
+cd -
+
+mkdir -p /tmp/ccache
+
+# Create a local branch so the child Docker script won't complain
+git branch -f jenkins-docker
+
+# Use image name based on Dockerfile checksum
+DOCKER_IMAGE_NAME=grpc_jenkins_slave${docker_suffix}_`sha1sum tools/jenkins/grpc_jenkins_slave/Dockerfile | cut -f1 -d\ `
+
+# Make sure docker image has been built. Should be instantaneous if so.
+docker build -t $DOCKER_IMAGE_NAME tools/jenkins/grpc_jenkins_slave$docker_suffix
+
+# Make sure the CID file is gone.
+rm -f docker.cid
+
+# Run tests inside docker
+docker run \
+  -e "RUN_TESTS_COMMAND=$RUN_TESTS_COMMAND" \
+  -e "config=$config" \
+  -e "arch=$arch" \
+  -e CCACHE_DIR=/tmp/ccache \
+  -i $TTY_FLAG \
+  -v "$git_root:/var/local/jenkins/grpc" \
+  -v /tmp/ccache:/tmp/ccache \
+  -w /var/local/git/grpc \
+  --cidfile=docker.cid \
+  $DOCKER_IMAGE_NAME \
+  bash -l /var/local/jenkins/grpc/tools/jenkins/docker_run_tests.sh || DOCKER_FAILED="true"
+
+DOCKER_CID=`cat docker.cid`
+
+if [ "$XML_REPORT" != "" ]
+then
+  docker cp "$DOCKER_CID:/var/local/git/grpc/$XML_REPORT" $git_root
+fi
+
+# remove the container, possibly killing it first
+docker rm -f $DOCKER_CID || true
+
+if [ "$DOCKER_FAILED" != "" ] && [ "$XML_REPORT" == "" ]
+then
+  exit 1
+fi
diff --git a/tools/jenkins/docker_run_jenkins.sh b/tools/jenkins/docker_run_tests.sh
similarity index 88%
rename from tools/jenkins/docker_run_jenkins.sh
rename to tools/jenkins/docker_run_tests.sh
index 0a5516c30df671f2845dadb8297cc1245f6e0b8c..781bff26b93bd913696d902e9cd012d9d28470d3 100755
--- a/tools/jenkins/docker_run_jenkins.sh
+++ b/tools/jenkins/docker_run_tests.sh
@@ -28,18 +28,17 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
-# This script is invoked by run_jekins.sh when piggy-backing into docker.
+# This script is invoked by build_docker_and_run_tests.py inside a docker
+# container. You should never need to call this script on your own.
 set -e
 
 export CONFIG=$config
 export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.5
-export CPPFLAGS=-I/tmp/prebuilt/include
 
 mkdir -p /var/local/git
 git clone --recursive /var/local/jenkins/grpc /var/local/git/grpc
 
-cd /var/local/git/grpc
 nvm use 0.12
 rvm use ruby-2.1
 
-setarch $arch tools/run_tests/run_tests.py -t -c $config -l $language -x report.xml
+$RUN_TESTS_COMMAND
diff --git a/tools/jenkins/run_jenkins.sh b/tools/jenkins/run_jenkins.sh
index 9ff588e8fa46b10e49f4288fabc76eb2da098c8b..a55f1de9960484789cf6cb3086101b0399b6e554 100755
--- a/tools/jenkins/run_jenkins.sh
+++ b/tools/jenkins/run_jenkins.sh
@@ -56,46 +56,8 @@ if [ "$platform" == "linux" ]
 then
   echo "building $language on Linux"
 
-  cd `dirname $0`/../..
-  git_root=`pwd`
-  cd -
+  ./tools/run_tests/run_tests.py --use_docker -t -l $language -c $config -x report.xml || true
 
-  mkdir -p /tmp/ccache
-
-  # Use image name based on Dockerfile checksum
-  DOCKER_IMAGE_NAME=grpc_jenkins_slave$docker_suffix_`sha1sum tools/jenkins/grpc_jenkins_slave/Dockerfile | cut -f1 -d\ `
-
-  # Make sure docker image has been built. Should be instantaneous if so.
-  docker build -t $DOCKER_IMAGE_NAME tools/jenkins/grpc_jenkins_slave$docker_suffix
-
-  # Create a local branch so the child Docker script won't complain
-  git branch jenkins-docker
-
-  # Make sure the CID file is gone.
-  rm -f docker.cid
-
-  # Run tests inside docker
-  docker run \
-    -e "config=$config" \
-    -e "language=$language" \
-    -e "arch=$arch" \
-    -e CCACHE_DIR=/tmp/ccache \
-    -i \
-    -v "$git_root:/var/local/jenkins/grpc" \
-    -v /tmp/ccache:/tmp/ccache \
-    --cidfile=docker.cid \
-    $DOCKER_IMAGE_NAME \
-    bash -l /var/local/jenkins/grpc/tools/jenkins/docker_run_jenkins.sh || DOCKER_FAILED="true"
-
-  DOCKER_CID=`cat docker.cid`
-  # forcefully kill the instance if it's still running, otherwise
-  # continue 
-  # (failure to kill something that's already dead => things are dead)
-  docker kill $DOCKER_CID || true
-  docker cp $DOCKER_CID:/var/local/git/grpc/report.xml $git_root
-  # TODO(ctiller): why?
-  sleep 4
-  docker rm $DOCKER_CID || true
 elif [ "$platform" == "interop" ]
 then
   python tools/run_tests/run_interops.py --language=$language
diff --git a/tools/run_tests/run_tests.py b/tools/run_tests/run_tests.py
index 8f6511b1f88c9eb995688be19d8ecb95bbb84e51..f7e9805393d217f80c6b67f064a47cd7ad8e13e0 100755
--- a/tools/run_tests/run_tests.py
+++ b/tools/run_tests/run_tests.py
@@ -397,12 +397,6 @@ _WINDOWS_CONFIG = {
     'opt': 'Release',
     }
 
-# parse command line
-argp = argparse.ArgumentParser(description='Run grpc tests.')
-argp.add_argument('-c', '--config',
-                  choices=['all'] + sorted(_CONFIGS.keys()),
-                  nargs='+',
-                  default=_DEFAULT)
 
 def runs_per_test_type(arg_str):
     """Auxilary function to parse the "runs_per_test" flag.
@@ -423,6 +417,13 @@ def runs_per_test_type(arg_str):
     except:
         msg = "'{}' isn't a positive integer or 'inf'".format(arg_str)
         raise argparse.ArgumentTypeError(msg)
+
+# parse command line
+argp = argparse.ArgumentParser(description='Run grpc tests.')
+argp.add_argument('-c', '--config',
+                  choices=['all'] + sorted(_CONFIGS.keys()),
+                  nargs='+',
+                  default=_DEFAULT)
 argp.add_argument('-n', '--runs_per_test', default=1, type=runs_per_test_type,
         help='A positive integer or "inf". If "inf", all tests will run in an '
              'infinite loop. Especially useful in combination with "-f"')
@@ -449,11 +450,48 @@ argp.add_argument('-S', '--stop_on_failure',
                   default=False,
                   action='store_const',
                   const=True)
+argp.add_argument('--use_docker',
+                  default=False,
+                  action='store_const',
+                  const=True,
+                  help="Run all the tests under docker. That provides " +
+                  "additional isolation and prevents the need to installs " +
+                  "language specific prerequisites. Only available on Linux.")
 argp.add_argument('-a', '--antagonists', default=0, type=int)
 argp.add_argument('-x', '--xml_report', default=None, type=str,
         help='Generates a JUnit-compatible XML report')
 args = argp.parse_args()
 
+if args.use_docker:
+  if not args.travis:
+    print 'Seen --use_docker flag, will run tests under docker.'
+    print
+    print 'IMPORTANT: The changes you are testing need to be locally committed'
+    print 'because only the committed changes in the current branch will be'
+    print 'copied to the docker environment.'
+    time.sleep(5)
+
+  child_argv = [ arg for arg in sys.argv if not arg == '--use_docker' ]
+  run_tests_cmd = 'tools/run_tests/run_tests.py %s' % " ".join(child_argv[1:])
+
+  # TODO(jtattermusch): revisit if we need special handling for arch here
+  # set arch command prefix in case we are working with different arch.
+  arch_env = os.getenv('arch')
+  if arch_env:
+    run_test_cmd = 'arch %s %s' % (arch_env, run_test_cmd)
+
+  env = os.environ.copy()
+  env['RUN_TESTS_COMMAND'] = run_tests_cmd
+  if args.xml_report:
+    env['XML_REPORT'] = args.xml_report
+  if not args.travis:
+    env['TTY_FLAG'] = '-t'  # enables Ctrl-C when not on Jenkins.
+
+  subprocess.check_call(['tools/jenkins/build_docker_and_run_tests.sh'],
+                        shell=True,
+                        env=env)
+  sys.exit(0)
+
 # grab config
 run_configs = set(_CONFIGS[cfg]
                   for cfg in itertools.chain.from_iterable(