diff --git a/docs/source/index.rst b/docs/source/index.rst
index e712e0e44c7d5e4b83f94c8798b2d089cf12edda..8cc638a205fcdf6723cf0d48ca4e258dadb00fce 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -12,6 +12,7 @@ Welcome to PrivateStorageio's documentation!
 
    README
    architecture-overview
+   morph
 
 
 
diff --git a/docs/source/morph.rst b/docs/source/morph.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5bcffb5fb5a6928a69300dcfb3ac4cb7126ba09a
--- /dev/null
+++ b/docs/source/morph.rst
@@ -0,0 +1,2 @@
+.. include::
+   ../../morph/README.rst
diff --git a/morph/README.rst b/morph/README.rst
new file mode 100644
index 0000000000000000000000000000000000000000..96a4d5bf62dc9c29fbec0bcf16c08528dbb59a64
--- /dev/null
+++ b/morph/README.rst
@@ -0,0 +1,52 @@
+Morph
+=====
+
+This directory contains Nix-based configuration for the grid.
+This takes the form of Nix expressions in ``.nix`` files
+and some JSON-based configuration in ``.config.json`` files.
+
+This configuration is fed to `morph`_ to make changes to the deployment.
+
+bootstrap-configuration.nix
+---------------------------
+
+This is meant as a minimal system configuration to use as part of crossgrading a Debian install to NixOS.
+It has a lot of comments explaining different parts of Nix and NixOS.
+You may want to browse it before looking at other ``.nix`` files here.
+
+grid.config.json
+----------------
+
+This contains configuration for Tahoe-LAFS.
+
+grid.nix
+--------
+
+This is the `morph`_ entrypoint for the grid.
+This defines all of the servers that are part of the grid.
+
+The actual configuration is split into separate files that are imported from this one.
+You can do things like build the network::
+
+  morph build grid.nix
+
+<hostname>-hardware.nix
+-----------------------
+
+These are the generated hardware-related configuration files for servers in the grid.
+These files are referenced from the corresponding ``<hostname>.nix`` files.
+
+<hostname>-config.nix
+---------------------
+
+Each such file contains a minimal Nix expression supplying critical system configuration details.
+"Critical" roughly corresponds to anything which must be specified to have a bootable system.
+These files are referenced by the corresponding ``<hostname>.nix`` files.
+
+<hostname>.nix
+--------------
+
+Each such file contains the parts of the system configuration that aren't *so* related to hardware.
+They are referenced from ``grid.nix``.
+
+.. _`morph`: https://github.com/DBCDK/morph
diff --git a/morph/grid.config.json b/morph/grid.config.json
new file mode 100644
index 0000000000000000000000000000000000000000..71d0fcd9aac658364700aa2cfbb3533d184a5f8d
--- /dev/null
+++ b/morph/grid.config.json
@@ -0,0 +1,2 @@
+{ "publicStoragePort": 8898
+}
diff --git a/morph/grid.nix b/morph/grid.nix
new file mode 100644
index 0000000000000000000000000000000000000000..c6de769a64ba686c5056f5d3ccf2e033c509fe67
--- /dev/null
+++ b/morph/grid.nix
@@ -0,0 +1,24 @@
+# Load the helper function and call it with arguments tailored for the testing
+# grid.  It will make the morph configuration for us.  We share this function
+# with the testing grid and have one fewer possible point of divergence.
+import ./make-grid.nix {
+  name = "Production";
+  nodes = cfg: {
+    # Here are the hosts that are in this morph network.  This is sort of like
+    # a server manifest.  We try to keep as many of the specific details as
+    # possible out of *this* file so that this file only grows as server count
+    # grows.  If it grows too much, we can load servers by listing contents of
+    # a directory or reading from another JSON file or some such.  For now,
+    # I'm just manually maintaining these entries.
+    #
+    # The name on the left of the `=` is mostly irrelevant but it does provide
+    # a default hostname for the server if the configuration on the right side
+    # doesn't specify one.
+    #
+    # The names must be unique!
+
+    # Pass the whole grid configuration to the module and let it take what it
+    # wants.
+    "storage000" = import ./storage000.nix cfg;
+  };
+}
diff --git a/morph/make-grid.nix b/morph/make-grid.nix
new file mode 100644
index 0000000000000000000000000000000000000000..60d80aae1053119a0731af563290582409da9b9b
--- /dev/null
+++ b/morph/make-grid.nix
@@ -0,0 +1,27 @@
+# Define a function for making a morph configuration for a storage grid.  It
+# takes two arguments.  A string like "Production" giving the name of the grid
+# and a function that takes the grid configuration as an argument and returns
+# a set of nodes specifying the addresses and NixOS configurations for each
+# server in the morph network.
+{ name, nodes }:
+let
+  # Pin the deployment package-set to a specific version of nixpkgs.  This is
+  # NixOS 19.03 as of Aug 28 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.
+  pkgs = import (builtins.fetchTarball {
+    url = "https://github.com/NixOS/nixpkgs/archive/3c83ad6ac13b67101cc3e2e07781963a010c1624.tar.gz";
+    sha256 = "0cdq342wrkvkyccygpp1gvwp7hhqg68hljjwld4vjixm901ayy14";
+  }) {};
+  # Load our JSON configuration for later use.
+  cfg = pkgs.lib.trivial.importJSON ./grid.config.json;
+in
+{
+  network =  {
+    # Make all of the hosts in this network use the nixpkgs we pinned above.
+    inherit pkgs;
+    # This is just for human consumption as far as I can tell.
+    description = "PrivateStorage.io ${name} Grid";
+  };
+} // (nodes cfg)
diff --git a/morph/storage000-config.nix b/morph/storage000-config.nix
new file mode 100644
index 0000000000000000000000000000000000000000..b1d38ecb1f896b740f0a392df14da670d0156a48
--- /dev/null
+++ b/morph/storage000-config.nix
@@ -0,0 +1,8 @@
+{ "interface" = "eno1";
+  "publicIPv4" = "69.36.183.24";
+  "prefixLength" = 24;
+  "gateway" = "69.36.183.1";
+  "gatewayInterface" = "eno1";
+  "grubDeviceID" = "wwn-0x5000c500936410b9";
+  "rootPublicKey" = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN4GenAY/YLGuf1WoMXyyVa3S9i4JLQ0AG+pt7nvcLlQ exarkun@baryon";
+}
diff --git a/morph/storage000-hardware.nix b/morph/storage000-hardware.nix
new file mode 100644
index 0000000000000000000000000000000000000000..f0d8c290ddb50162bdb0fee7e0f0ca67cd3a4f5c
--- /dev/null
+++ b/morph/storage000-hardware.nix
@@ -0,0 +1,37 @@
+# Do not modify this file!  It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations.  Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, ... }:
+
+{
+  imports =
+    [ <nixpkgs/nixos/modules/installer/scan/not-detected.nix>
+    ];
+
+  boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "ehci_pci" "megaraid_sas" "usbhid" "sd_mod" ];
+  boot.initrd.kernelModules = [ ];
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    { device = "/dev/disk/by-uuid/ccabaa39-d888-467e-b8d9-75b5790a91aa";
+      fsType = "ext4";
+    };
+
+  fileSystems."/boot" =
+    { device = "/dev/disk/by-uuid/849c8696-a7e6-42d2-810d-15326d9f9ff6";
+      fsType = "ext4";
+    };
+
+  fileSystems."/storage" =
+    { device = "/dev/disk/by-uuid/2745cbf3-5a63-491d-ab92-6dfd4da1b504";
+      fsType = "ext4";
+    };
+
+  swapDevices =
+    [ { device = "/dev/disk/by-uuid/c6f09c9a-572a-4b0f-b792-412cb5c749d4"; }
+    ];
+
+  nix.maxJobs = lib.mkDefault 32;
+  powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
+}
diff --git a/morph/storage000.nix b/morph/storage000.nix
new file mode 100644
index 0000000000000000000000000000000000000000..540229b5376d309c144cf95db62fd059e293421c
--- /dev/null
+++ b/morph/storage000.nix
@@ -0,0 +1,44 @@
+let
+  # Get the configuration that's specific to this node.
+  cfg = import ./storage000-config.nix;
+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, ... }: {
+
+  # Any extra NixOS modules to load on this server.
+  imports = [
+    # Include the results of the hardware scan.
+    ./storage000-hardware.nix
+    # Configure it as a system operated by 100TB.
+    ../nixos/modules/100tb.nix
+    # Bring in our module for configuring the Tahoe-LAFS service and other
+    # Private Storage-specific things.
+    ../nixos/modules/private-storage.nix
+   ];
+
+  # Pass the configuration specific to this host to the 100TB module to be
+  # expanded into a complete system configuration.  See the 100tb module for
+  # handling of this value.
+  #
+  # The module name is quoted because `1` makes `100tb` look an awful lot like
+  # it should be a number.
+  "100tb".config = cfg;
+
+  # Turn on the Private Storage (Tahoe-LAFS) service.
+  services.private-storage = {
+    # Yep.  Turn it on.
+    enable = true;
+    # Get the public IPv4 address from the node configuration.
+    inherit (cfg) publicIPv4;
+    # And the port to operate on is specified via parameter.
+    inherit publicStoragePort;
+  };
+
+  # This value determines the NixOS release with which your system is to be
+  # compatible, in order to avoid breaking some software such as database
+  # servers. You should change this only after NixOS release notes say you
+  # should.
+  system.stateVersion = "19.03"; # Did you read the comment?
+}
diff --git a/morph/testing-grid.nix b/morph/testing-grid.nix
new file mode 100644
index 0000000000000000000000000000000000000000..c58cefa4de516178c6619a26228cfd53473443a0
--- /dev/null
+++ b/morph/testing-grid.nix
@@ -0,0 +1,15 @@
+# Load the helper function and call it with arguments tailored for the testing
+# grid.  It will make the morph configuration for us.  We share this function
+# with the production grid and have one fewer possible point of divergence.
+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;
+    };
+  };
+}
diff --git a/morph/testing000-hardware.nix b/morph/testing000-hardware.nix
new file mode 100644
index 0000000000000000000000000000000000000000..8eccc4b3e13d8f83838e1a07ab355956742e0e23
--- /dev/null
+++ b/morph/testing000-hardware.nix
@@ -0,0 +1,7 @@
+{
+  imports = [
+    <nixpkgs/nixos/modules/virtualisation/amazon-image.nix>
+  ];
+
+  config.ec2.hvm = true;
+}
diff --git a/morph/testing000.nix b/morph/testing000.nix
new file mode 100644
index 0000000000000000000000000000000000000000..3a5bd414ce7a2dbe8ed9ae751a34203affbaadd3
--- /dev/null
+++ b/morph/testing000.nix
@@ -0,0 +1,12 @@
+{ publicIPv4, publicStoragePort }:
+{ imports = [
+    ./testing000-hardware.nix
+    ../nixos/modules/private-storage.nix
+  ];
+
+  services.private-storage =
+  { enable = true;
+    inherit publicIPv4;
+    inherit publicStoragePort;
+  };
+}