diff --git a/infra/common/options.nix b/infra/common/options.nix
index f2ff8147..c8ec4aac 100644
--- a/infra/common/options.nix
+++ b/infra/common/options.nix
@@ -125,8 +125,17 @@ in
 
     hostPublicKey = mkOption {
       description = ''
-        The host public key of the machine. It is used to filter Age secrets and
-        only keep the relevant ones, and to feed to NixOps4.
+        The ed25519 host public key of the machine. It is used to filter Age
+        secrets and only keep the relevant ones, and to feed to NixOps4.
+      '';
+    };
+
+    unsafeHostPrivateKey = mkOption {
+      default = null;
+      description = ''
+        The ed25519 host private key of the machine. It is used when
+        provisioning to have a predictable public key. Warning: only ever use
+        this for testing machines, as it is a security hole for so many reasons.
       '';
     };
   };
diff --git a/infra/flake-part.nix b/infra/flake-part.nix
index 81dba2ac..fac98ee6 100644
--- a/infra/flake-part.nix
+++ b/infra/flake-part.nix
@@ -86,6 +86,8 @@ let
         sockets
         cores
         memory
+        hostPublicKey
+        unsafeHostPrivateKey
         ;
     })
   );
diff --git a/infra/proxmox-provision.sh b/infra/proxmox-provision.sh
index 22207af4..be433849 100755
--- a/infra/proxmox-provision.sh
+++ b/infra/proxmox-provision.sh
@@ -168,7 +168,7 @@ grab_vm_option () {
     --impure --raw --expr "
       builtins.toJSON (builtins.getFlake (builtins.toString ./.)).vmOptions.$1
     " | jq -r ."$2"
-  }
+}
 
 ################################################################################
 ## Build ISO
@@ -177,9 +177,20 @@ build_iso () {
   acquire_lock build
   printf 'Building ISO for VM %s...\n' "$2"
 
-  ## FIXME: Support injecting host keys for test VMs (but not for production
-  ## VMs as that would be unsafe).
-
+  host_public_key=$(grab_vm_option "$2" hostPublicKey)
+  host_private_key=$(grab_vm_option "$2" unsafeHostPrivateKey)
+  if [ "$host_public_key" != null ] && [ "$host_private_key" != null ]; then
+    echo "$host_public_key" > "$tmpdir"/"$2"_host_key.pub
+    echo "$host_private_key" > "$tmpdir"/"$2"_host_key
+    nix_host_keys="
+      hostKeys.ed25519 = {
+        public = $tmpdir/$2_host_key.pub;
+        private = $tmpdir/$2_host_key;
+      };
+    "
+  else
+    nix_host_keys=
+  fi
 
   nix build \
     --impure --expr "
@@ -187,6 +198,7 @@ build_iso () {
       flake.lib.makeInstallerIso {
         nixosConfiguration = flake.nixosConfigurations.$2;
         nixpkgs = flake.inputs.nixpkgs;
+        $nix_host_keys
       }
     " \
     --log-format raw --quiet \
@@ -305,14 +317,18 @@ start_vm () {
 
 printf 'Provisioning VMs%s...\n' "$vm_names"
 
-provision_vm () {
+provision_vm () (
+  ## NOTE: Mind the fact that we now run in a sub-shell, allowing the following
+  ## functions to define global variables without clashing with concurrent VMs
+  ## provisioning.
+
   build_iso "$@"
   upload_iso "$@"
   create_vm "$@"
   install_vm "$@"
   start_vm "$@"
   remove_iso "$@"
-}
+)
 
 for vm_name in $vm_names; do
   vm_id=$(grab_vm_option "$vm_name" vmId)