diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3fe7c7630832fb9e47fb8a214723f72827f9b9e2..8c393e1ad5d65dac119855647901edd1aab24377 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -49,30 +49,103 @@ jobs:
 
   tests:
     docker:
-      - image: "circleci/python:2.7"
+      # Run in a highly Nix-capable environment.
+      - image: "nixorg/nix:circleci"
 
     environment:
-      PIP_REQUIREMENTS: "-r requirements.txt -r requirements-tests.txt"
-      ZKAPAUTHORIZER_HYPOTHESIS_PROFILE: "ci"
+      CODECOV_TOKEN: "cc6e4697-4337-4506-88af-92b8f8ca6b22"
+      # Specify a revision of NixOS/nixpkgs to run against.  This essentially
+      # pins the majority of the software involved in the build.  This
+      # revision is selected arbitrarily.  It's somewhat current as of the
+      # time of this comment.  We can bump it to a newer version when that
+      # makes sense.  Meanwhile, the platform won't shift around beneath us
+      # unexpectedly.
+      NIXPKGS_REV: "5d5cd70516001e79516d2ade8bcf31df208a4ef3"
 
     steps:
+      - run:
+          # Get NIX_PATH set for the rest of the job so that the revision of
+          # nixpkgs we selected will be used everywhere Nix pulls in software.
+          # There is no way to set an environment variable containing the
+          # value of another environment variable on CircleCI except to use
+          # the `BASE_ENV` feature as we do here.
+          name: "Setup NIX_PATH Environment Variable"
+          command: |
+            echo "export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/$NIXPKGS_REV.tar.gz" >> $BASH_ENV
+
       - "checkout"
 
-      - <<: *PREPARE_VIRTUALENV
+      - "run":
+          # CircleCI won't let us interpolate NIXPKGS_REV into a cache key.
+          # Only CircleCI's own environment variables or variables set via the
+          # web interface in a "context" can be interpolated into cache keys.
+          # However, we can interpolate the checksum of a file...  Since we
+          # don't care about the exact revision, we just care that a new
+          # revision gives us a new string, we can write the revision to a
+          # file and then put the checksum of that file into the cache key.
+          # This way, we don't have to maintain the nixpkgs revision in two
+          # places and risk having them desynchronize.
+          name: "Prepare For Cache Key"
+          command: |
+            echo "${NIXPKGS_REV}" > nixpkgs.rev
+
+      - restore_cache:
+          # Get all of Nix's state relating to the particular revision of
+          # nixpkgs we're using.  It will always be the same.  CircleCI
+          # artifacts and nixpkgs store objects are probably mostly hosted in
+          # the same place (S3) so there's not a lot of difference for
+          # anything that's pre-built.  For anything we end up building
+          # ourselves, though, this saves us all of the build time (less the
+          # download time).
+          #
+          # Read about caching dependencies: https://circleci.com/docs/2.0/caching/
+          name: "Restore Nix Store Paths"
+          keys:
+            # Construct cache keys that allow sharing as long as nixpkgs
+            # revision is unchanged.
+            #
+            # If nixpkgs changes then potentially a lot of cached packages for
+            # the base system will be invalidated so we may as well drop them
+            # and make a new cache with the new packages.
+            - zkapauthorizer-nix-store-v1-{{ checksum "nixpkgs.rev" }}
+            - zkapauthorizer-nix-store-v1-
 
       - run:
           name: "Run Test Suite"
           command: |
-            . venv/bin/activate
-            pip install .
-            coverage run -m twisted.trial _zkapauthorizer
+            # Building the package has, as a side effect, running the test
+            # suite.  If the test suite fails, so does the build.
+            #
+            # Pass in a couple args here to control how the test suite is run
+            # - configure Hypothesis so it can behave appropriately in a CI
+            # environment (where resources are scarce, competetion with other
+            # tenants is high, etc) and collect coverage information.
+            #
+            # Further, we want the "doc" output built as well because that's
+            # where the coverage data ends up.
+            nix-build --argstr hypothesisProfile ci --arg collectCoverage true --attr doc
 
       - run:
-          name: "Report Coverage"
+          name: "Cache codecov"
           command: |
-            . venv/bin/activate
-            CODECOV_TOKEN="cc6e4697-4337-4506-88af-92b8f8ca6b22" codecov
+            # Build codecov and any dependencies here, before we save the
+            # cache, so that they make it in to the cache too.  Turns out
+            # there is a python-cryptography dependency here that is expensive
+            # to build that doesn't get built earlier.  This saves us a couple
+            # minutes.
+            nix-build --expr '(import <nixpkgs> { }).python.withPackages (ps: [ ps.codecov ])'
+
+      - save_cache:
+          name: "Cache Nix Store Paths"
+          key: zkapauthorizer-nix-store-v1-{{ checksum "nixpkgs.rev" }}
+          paths:
+            - "/nix"
 
+      - run:
+          name: "Report Coverage"
+          command: |
+            nix-shell -p 'python.withPackages (ps: [ ps.codecov ])' --keep CODECOV_TOKEN --run \
+              'codecov --file ./result-doc/share/doc/*/.coverage'
 
 workflows:
   version: 2
diff --git a/autobahn.nix b/autobahn.nix
index 3cc1df2138e783f7bb212b50ba09435773233b88..df026096799d6b65bf34ad7700b6a726e9252f68 100644
--- a/autobahn.nix
+++ b/autobahn.nix
@@ -1,6 +1,7 @@
 { lib, buildPythonPackage, fetchFromGitHub, isPy3k,
   six, txaio, twisted, zope_interface, cffi, trollius, futures, cryptography,
-  mock, pytest
+  mock, pytest,
+  supportAsyncio ? false
 }:
 buildPythonPackage rec {
   pname = "autobahn";
@@ -14,7 +15,9 @@ buildPythonPackage rec {
   };
 
   propagatedBuildInputs = [ six txaio twisted zope_interface cffi cryptography ] ++
-    (lib.optionals (!isPy3k) [ trollius futures ]);
+    (lib.optionals (!isPy3k) [ futures ]) ++
+    (lib.optionals (supportAsyncio && !isPy3k) [ trollius ])
+    ;
 
   checkInputs = [ mock pytest ];
   checkPhase = ''
diff --git a/default.nix b/default.nix
index f1c5e37ea433eb5add61f81b6c9ed3c4b359ce25..04af35d91986bd58cdc708b9bddc31c6aa1d50d4 100644
--- a/default.nix
+++ b/default.nix
@@ -1,2 +1,2 @@
-{ pkgs ? import ./nixpkgs.nix { } }:
-pkgs.python27Packages.callPackage ./zkapauthorizer.nix { }
+{ pkgs ? import ./nixpkgs.nix { }, hypothesisProfile ? null, collectCoverage ? false }:
+pkgs.python27Packages.callPackage ./zkapauthorizer.nix { inherit hypothesisProfile collectCoverage; }
diff --git a/zkapauthorizer.nix b/zkapauthorizer.nix
index 0b311a4868fcec93729afe25b028ab4dcd336126..ae7453630fde5af7ae11afb728cdd9563f109f8c 100644
--- a/zkapauthorizer.nix
+++ b/zkapauthorizer.nix
@@ -1,6 +1,8 @@
 { buildPythonPackage, sphinx, circleci-cli
 , attrs, zope_interface, twisted, tahoe-lafs, privacypass
 , fixtures, testtools, hypothesis, pyflakes, treq, coverage
+, hypothesisProfile ? null
+, collectCoverage ? false
 }:
 buildPythonPackage rec {
   version = "0.0";
@@ -8,7 +10,7 @@ buildPythonPackage rec {
   name = "${pname}-${version}";
   src = ./.;
 
-  outputs = [ "out" "doc" ];
+  outputs = [ "out" ] ++ (if collectCoverage then [ "doc" ] else [ ]);
 
   depsBuildBuild = [
     sphinx
@@ -35,13 +37,18 @@ buildPythonPackage rec {
   checkPhase = ''
     runHook preCheck
     "${pyflakes}/bin/pyflakes" src/_zkapauthorizer
-    python -m coverage run --branch --source _zkapauthorizer,twisted.plugins.zkapauthorizer --module twisted.trial _zkapauthorizer
+    python -m ${if collectCoverage
+      then "coverage run --branch --source _zkapauthorizer,twisted.plugins.zkapauthorizer --module"
+      else ""
+    } twisted.trial _zkapauthorizer
     runHook postCheck
   '';
 
-  postCheck = ''
+  postCheck = if collectCoverage
+    then ''
     python -m coverage html
     mkdir -p "$doc/share/doc/${name}"
     cp -vr .coverage htmlcov "$doc/share/doc/${name}"
-  '';
+    ''
+    else "";
 }