diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3256c18de0ec272c5182b93fb0d8b423cdaa2fc2..45eaa02f4beed2da63fc2ba7347892fdd5541ccc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -17,48 +17,87 @@ version: 2
 jobs:
   test:
     docker:
-      - image: "nixos/nix:2.2.1"
+      - image: "nixorg/nix:circleci"
     steps:
-      - run:
-          name: "Install Git"
-          command: |
-            # Required for the checkout step
-            nix-env -i git openssh
-
       - "checkout"
 
       - run:
           name: "Run Tests"
           command: |
+            export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/$(cat nixpkgs.rev).tar.gz
             nix-build nixos/unit-tests.nix && cat result
 
-  build:
+  system-tests-driver:
+    # Cannot actually run the system tests on CircleCI but we can build
+    # everything that makes them up.  This by itself can catch a lot of
+    # problems.
     docker:
-      - image: "nixos/nix:2.2.1"
+      - image: "nixorg/nix:circleci"
     steps:
-      - run:
-          name: "Install CA Certificates"
-          command: |
-            # Required for cache and artifact interactions.  Though we use a
-            # nix image, it's actually an alpine base...  The CircleCI cache
-            # management (which we stopped using) and artifact uploader don't
-            # know how to use the nix ca bundle we could install.
-            apk update
-            apk add ca-certificates
+      - "checkout"
+
+      - 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.
+            - privatestorageio-nix-store-v2-{{ checksum "nixpkgs.rev" }}
+            - privatestorageio-nix-store-v2-
 
       - run:
-          name: "Install Git"
+          name: "Build System Test Driver"
           command: |
-            # Required for the checkout step
-            nix-env -i git openssh
+            export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/$(cat nixpkgs.rev).tar.gz
+            nix-build --max-jobs 1 --cores 1 nixos/system-tests.nix -A driver
 
+          # Give it a good long while.  PaymentServer and its dependencies, in
+          # particular, can take a while to build.
+          no_output_timeout: "20m"
+
+      - save_cache:
+          name: "Cache Nix Store Paths"
+          key: privatestorageio-nix-store-v2-{{ checksum "nixpkgs.rev" }}
+          paths:
+            - "/nix"
+
+  build:
+    docker:
+      - image: "nixorg/nix:circleci"
+    steps:
       - "checkout"
 
+      - restore_cache:
+          # See comments for nix store caching in `build` job.
+          name: "Restore Nix Store Paths"
+          keys:
+            - privatestorageio-docs-nix-store-v1-{{ checksum "nixpkgs.rev" }}
+            - privatestorageio-docs-nix-store-v1-
+
       - run:
           name: "Nix Build"
           command: |
+            export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/$(cat nixpkgs.rev).tar.gz
             nix-build docs.nix
 
+      - save_cache:
+          name: "Cache Nix Store Paths"
+          key: privatestorageio-docs-nix-store-v1-{{ checksum "nixpkgs.rev" }}
+          paths:
+            - "/nix"
+
       - store_artifacts:
           path: "result/docs"
           destination: "docs"
diff --git a/README.rst b/README.rst
index 675c9a7fbf9f17f63f025ca843d8e985735b366b..7bd9f0fcabc91d6d95351893d5e0da3da653ca77 100644
--- a/README.rst
+++ b/README.rst
@@ -7,6 +7,10 @@ Building
 --------
 
 The build system uses `Nix`_ which must be installed before anything can be built.
+Builds are tested against a particular nixpkgs revision.
+Start by exporting ``NIX_PATH`` to ensure you use the same revision::
+
+  $ export NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/$(cat nixpkgs.rev).tar.gz
 
 Documentation
 ~~~~~~~~~~~~~
@@ -34,4 +38,9 @@ The system tests are run using this command::
 
 The system tests boot QEMU VMs which prevents them from running on CI at this time.
 
+Deployment
+----------
+
+See ``morph/README.rst``.
+
 .. _Nix: https://nixos.org/nix
diff --git a/docs.nix b/docs.nix
index 2db695d9f5756e35824b1dd1fb15938d532bcb96..813a6cb432942fccd96b96ee07313ff84cf885c6 100644
--- a/docs.nix
+++ b/docs.nix
@@ -1,4 +1,4 @@
-{ pkgs ? import ./nixpkgs.nix { } }:
+{ pkgs ? import <nixpkgs> { } }:
 let
   # NixOS 19.03 packaged graphviz has trouble rendering our architecture
   # overview.  Latest from upstream does alright, though.  Use that.
diff --git a/morph/grid.config.json b/morph/grid.config.json
index 71d0fcd9aac658364700aa2cfbb3533d184a5f8d..178f44d39e5dd88ef709f92713d579c2cd32caff 100644
--- a/morph/grid.config.json
+++ b/morph/grid.config.json
@@ -1,2 +1,3 @@
 { "publicStoragePort": 8898
+, "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key"
 }
diff --git a/morph/make-grid.nix b/morph/make-grid.nix
index d740adbe237cbae54192843a6b11969a4672c6d8..32e0b98ed3ffd5741522f9b0e3ef67037fa6a180 100644
--- a/morph/make-grid.nix
+++ b/morph/make-grid.nix
@@ -5,7 +5,7 @@
 # server in the morph network.
 { name, nodes }:
 let
-  pkgs = import ../nixpkgs.nix { };
+  pkgs = import <nixpkgs> { };
   # Load our JSON configuration for later use.
   cfg = pkgs.lib.trivial.importJSON ./grid.config.json;
 in
diff --git a/morph/storage000.nix b/morph/storage000.nix
index 540229b5376d309c144cf95db62fd059e293421c..cd2b6c1fbb057852ee924fe7efe41973d3b9dfc6 100644
--- a/morph/storage000.nix
+++ b/morph/storage000.nix
@@ -5,7 +5,23 @@ in
 # Define the function that defines the node.  Accept the public storage server
 # port argument so we can configure Tahoe-LAFS with it.  Accept but ignore any
 # other arguments.
-{ publicStoragePort, ... }: {
+{ publicStoragePort, ristrettoSigningKeyPath, ... }: rec {
+
+  deployment = {
+    secrets = {
+      "ristretto-signing-key" = {
+        source = ristrettoSigningKeyPath;
+        destination = "/var/secrets/ristretto.signing-key";
+        owner.user = "root";
+        owner.group = "root";
+        permissions = "0400";
+        # Service name here matches the name defined by our tahoe-lafs nixos
+        # module.  It would be nice to not have to hard-code it here.  Can we
+        # extract it from the tahoe-lafs nixos module somehow?
+        action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
+      };
+    };
+  };
 
   # Any extra NixOS modules to load on this server.
   imports = [
@@ -34,6 +50,8 @@ in
     inherit (cfg) publicIPv4;
     # And the port to operate on is specified via parameter.
     inherit publicStoragePort;
+    # Give it the Ristretto signing key, too, to support authorization.
+    ristrettoSigningKeyPath = deployment.secrets.ristretto-signing-key.destination;
   };
 
   # This value determines the NixOS release with which your system is to be
diff --git a/morph/testing-grid.nix b/morph/testing-grid.nix
index c58cefa4de516178c6619a26228cfd53473443a0..5591827e3e02423abeeb31123c383bd42dfdf1c5 100644
--- a/morph/testing-grid.nix
+++ b/morph/testing-grid.nix
@@ -4,12 +4,8 @@
 import ./make-grid.nix {
   name = "Testing";
   nodes = cfg: {
-    "testing000" = import ./testing000.nix {
-      publicIPv4 = "3.123.26.90";
-      # Pass along some of the Tahoe-LAFS configuration.  If we have much more
-      # configuration than this we may want to keep it bundled up in one value
-      # instead of pulling individual values out to pass along.
-      inherit (cfg) publicStoragePort;
-    };
+    "testing000" = import ./testing000.nix (cfg // {
+      publicIPv4 = "35.157.216.200";
+    });
   };
 }
diff --git a/morph/testing000.nix b/morph/testing000.nix
index 3a5bd414ce7a2dbe8ed9ae751a34203affbaadd3..e5f9c3f32bf4c75fea438a309a92c372f44f8ff8 100644
--- a/morph/testing000.nix
+++ b/morph/testing000.nix
@@ -1,5 +1,22 @@
-{ publicIPv4, publicStoragePort }:
-{ imports = [
+{ publicIPv4, publicStoragePort, ristrettoSigningKeyPath }: rec {
+
+  deployment = {
+    secrets = {
+      "ristretto-signing-key" = {
+        source = ristrettoSigningKeyPath;
+        destination = "/var/secrets/ristretto.signing-key";
+        owner.user = "root";
+        owner.group = "root";
+        permissions = "0400";
+        # Service name here matches the name defined by our tahoe-lafs nixos
+        # module.  It would be nice to not have to hard-code it here.  Can we
+        # extract it from the tahoe-lafs nixos module somehow?
+        action = ["sudo" "systemctl" "restart" "tahoe.storage.service"];
+      };
+    };
+  };
+
+  imports = [
     ./testing000-hardware.nix
     ../nixos/modules/private-storage.nix
   ];
@@ -8,5 +25,6 @@
   { enable = true;
     inherit publicIPv4;
     inherit publicStoragePort;
+    ristrettoSigningKeyPath = deployment.secrets.ristretto-signing-key.destination;
   };
 }
diff --git a/nixos/modules/overlays.nix b/nixos/modules/overlays.nix
index 779384ab6914ea4858970f7190780a7543702913..a644e630e4fd01da411cbc4200ea3501a1965550 100644
--- a/nixos/modules/overlays.nix
+++ b/nixos/modules/overlays.nix
@@ -34,6 +34,10 @@ self: super: {
   # the value from the previously overlay, not from the fixed point.  This is
   # important because this override never converges.
   python27 = super.python27.override (old: {
-    packageOverrides = super.lib.composeExtensions old.packageOverrides pythonTwistedOverride;
+    packageOverrides =
+      if old ? packageOverrides then
+        super.lib.composeExtensions old.packageOverrides pythonTwistedOverride
+      else
+        pythonTwistedOverride;
   });
 }
diff --git a/nixos/modules/tests/private-storage.nix b/nixos/modules/tests/private-storage.nix
index 5df571f2ad10ba27a0607df11a8405ca767c9f35..e4d541989804992036b324eecd817c91920f62e9 100644
--- a/nixos/modules/tests/private-storage.nix
+++ b/nixos/modules/tests/private-storage.nix
@@ -1,5 +1,5 @@
 let
-  pkgs = (import ../../../nixpkgs.nix { });
+  pkgs = import <nixpkgs> { };
   pspkgs = import ../pspkgs.nix { inherit pkgs; };
 
   # Separate helper programs so we can write as little perl inside a string
diff --git a/nixos/modules/tests/run-client.py b/nixos/modules/tests/run-client.py
index bcbca050b5380f3b8f3aa9691a4f6dd62b198523..e28293980b75f3fa4002e34ac45abe20b9fae02f 100755
--- a/nixos/modules/tests/run-client.py
+++ b/nixos/modules/tests/run-client.py
@@ -14,10 +14,6 @@ from configparser import ConfigParser
 def main():
     (introducerFURL, issuerURL) = argv[1:]
 
-    # PYTHONHOME set for Python 3 for this script breaks Python 2 used by
-    # Tahoe. :/ This is kind of a NixOS Python packaging bug.
-    del environ["PYTHONHOME"]
-
     run(["tahoe", "--version"])
     run([
         "tahoe", "create-client",
diff --git a/nixos/modules/tests/run-introducer.py b/nixos/modules/tests/run-introducer.py
index 7a69bdb8240b5dc6b347b960762dd4e2ef0cdde1..33c3ec10369477e39c1461b3e59149e015f03ce9 100755
--- a/nixos/modules/tests/run-introducer.py
+++ b/nixos/modules/tests/run-introducer.py
@@ -17,10 +17,6 @@ log = print
 def main():
     pemFile, introducerPort, introducerFURL = argv[1:]
 
-    # PYTHONHOME set for Python 3 for this script breaks Python 2 used by
-    # Tahoe. :/ This is kind of a NixOS Python packaging bug.
-    del environ["PYTHONHOME"]
-
     run(["tahoe", "--version"])
     run([
         "tahoe", "create-introducer",
diff --git a/nixos/pkgs/zkapauthorizer-repo.nix b/nixos/pkgs/zkapauthorizer-repo.nix
index 059c333c65e1f971be9b7ecfc266913b031077ef..e85e99405a6e7003c4ba968053a5662a4f73f689 100644
--- a/nixos/pkgs/zkapauthorizer-repo.nix
+++ b/nixos/pkgs/zkapauthorizer-repo.nix
@@ -2,8 +2,8 @@ let
   pkgs = import <nixpkgs> {};
 in
   pkgs.fetchFromGitHub {
-    owner = "privatestorageio";
-    repo = "zkapauthorizer";
-    rev = "0ae5bb532b9dfd515c65852bdbe86bd85d70f0e8";
-    sha256 = "06vsy7lbn4j9rwgzb5qcjj6255x27q1a2z84xphr0675rdi27f4f";
+    owner = "PrivateStorageio";
+    repo = "ZKAPAuthorizer";
+    rev = "996f0acdb46dc0f2ef14a06ae0012771b43c087c";
+    sha256 = "09kkzq6pd61xwaq6dlfl25rqbi7ssdzkvknhsx59cyanxpk66rcd";
   }
\ No newline at end of file
diff --git a/nixos/pkgs/zkapissuer-repo.nix b/nixos/pkgs/zkapissuer-repo.nix
index 481d30960ace0c12443b0b618d2a6d66cc541823..b406280fd06066b5bbcb76d80b30deed28f2eadb 100644
--- a/nixos/pkgs/zkapissuer-repo.nix
+++ b/nixos/pkgs/zkapissuer-repo.nix
@@ -1,5 +1,5 @@
 let
-  pkgs = import ../../nixpkgs.nix {};
+  pkgs = import <nixpkgs> { };
 in
   pkgs.fetchFromGitHub {
     owner = "PrivateStorageio";
diff --git a/nixos/unit-tests.nix b/nixos/unit-tests.nix
index f5419aa7fe491c4b0dcf511e028b95e7d64a4cd7..75016a17d128fabe11f4ecaad65dba3471ed863d 100644
--- a/nixos/unit-tests.nix
+++ b/nixos/unit-tests.nix
@@ -1,6 +1,6 @@
 # The overall unit test suite for PrivateStorageio NixOS configuration.
 let
-  pkgs = import ../nixpkgs.nix { };
+  pkgs = import <nixpkgs> { };
 
   # Total the numbers in a list.
   sum = builtins.foldl' (a: b: a + b) 0;
diff --git a/nixpkgs.nix b/nixpkgs.nix
deleted file mode 100644
index ff369425e75e52f449c8899a31fc2b2feefc9ffa..0000000000000000000000000000000000000000
--- a/nixpkgs.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-# Pin the deployment package-set to a specific version of nixpkgs.  This is
-# NixOS 19.09 as of Oct 2 2019.  There's nothing special about it.  It's just
-# recent at the time of development.  It can be upgraded when there is value
-# in doing so.  Meanwhile, our platform doesn't shift around beneath us in
-# surprising ways as time passes.
-import (builtins.fetchTarball {
-  url = "https://github.com/NixOS/nixpkgs-channels/archive/5d5cd70516001e79516d2ade8bcf31df208a4ef3.tar.gz";
-  sha256 = "042i081cfwdvcfp3q79219akypb53chf730wg0vwxlp21pzgns33";
-})
diff --git a/nixpkgs.rev b/nixpkgs.rev
new file mode 100644
index 0000000000000000000000000000000000000000..41802ea669e9d5bb48fff0fe62e5da506b741bf8
--- /dev/null
+++ b/nixpkgs.rev
@@ -0,0 +1 @@
+8bf142e001b6876b021c8ee90c2c7cec385fe8e9
\ No newline at end of file