diff --git a/infra/README.md b/infra/README.md
index 0afde290..c43362f5 100644
--- a/infra/README.md
+++ b/infra/README.md
@@ -1,6 +1,7 @@
 # Infra
 
-This directory contains the definition of the VMs that host our infrastructure.
+This directory contains the definition of [the VMs](machines.md) that host our
+infrastructure.
 
 ## Provisioning VMs with an initial configuration
 
@@ -91,16 +92,3 @@ nixops4 apply
 ## Removing an existing VM
 
 See `infra/proxmox-remove.sh --help`.
-
-## Machines
-
-These machines are hosted on the Procolix Proxmox instance,
-to which non-Procolix members of the project do not have access.
-They host our stable infrastructure.
-
-  Machine   Proxmox       Description
-  --------- ------------- ------------------------
-  vm02116   Procolix      Forgejo
-  vm02187   Procolix      Wiki
-  fedi200   Fediversity   Testing machine for Hans
-  fedi201   Fediversity   FediPanel
diff --git a/infra/machines.md b/infra/machines.md
new file mode 100644
index 00000000..e2c49c0c
--- /dev/null
+++ b/infra/machines.md
@@ -0,0 +1,15 @@
+<!-- This file is auto-generated by `machines.md.sh` from the machines'
+     configuration. -->
+
+# Machines
+
+Currently, this repository keeps track of the following VMs:
+
+Machine | Proxmox | Description
+--------|---------|-------------
+[`fedi200`](./fedi200) | fediversity | Testing machine for Hans
+[`fedi201`](./fedi201) | fediversity | FediPanel
+[`vm02116`](./vm02116) | procolix | Forgejo
+[`vm02187`](./vm02187) | procolix | Wiki
+
+This table excludes all machines with names starting with `test`.
diff --git a/infra/machines.md.sh b/infra/machines.md.sh
new file mode 100644
index 00000000..4a2a5ca1
--- /dev/null
+++ b/infra/machines.md.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env sh
+set -euC
+
+cd "$(dirname "$0")"
+
+{
+cat <<\EOF
+<!-- This file is auto-generated by `machines.md.sh` from the machines'
+     configuration. -->
+
+# Machines
+
+Currently, this repository keeps track of the following VMs:
+
+Machine | Proxmox | Description
+--------|---------|-------------
+EOF
+
+vmOptions=$(
+  cd ..
+  nix eval \
+    --impure --raw --expr "
+      builtins.toJSON (builtins.getFlake (builtins.toString ./.)).vmOptions
+    " \
+    --log-format raw --quiet
+)
+
+## NOTE: `jq`'s `keys` is alphabetically sorted, just what we want here.
+for machine in $(echo "$vmOptions" | jq -r 'keys[]'); do
+  if [ "${machine#test}" = "$machine" ]; then
+    proxmox=$(echo "$vmOptions" | jq -r ".$machine.proxmox")
+    description=$(echo "$vmOptions" | jq -r ".$machine.description" | head -n 1)
+
+    printf '[`%s`](./%s) | %s | %s\n' "$machine" "$machine" "$proxmox" "$description"
+  fi
+done
+
+cat <<\EOF
+
+This table excludes all machines with names starting with `test`.
+EOF
+} >| machines.md
diff --git a/infra/machines/vm02116/default.nix b/infra/machines/vm02116/default.nix
index cfee5639..be461038 100644
--- a/infra/machines/vm02116/default.nix
+++ b/infra/machines/vm02116/default.nix
@@ -1,5 +1,6 @@
 {
   fediversityVm = {
+    vmId = 2116;
     proxmox = "procolix";
     description = "Forgejo";
 
diff --git a/infra/machines/vm02187/default.nix b/infra/machines/vm02187/default.nix
index 82a1adef..b389efb2 100644
--- a/infra/machines/vm02187/default.nix
+++ b/infra/machines/vm02187/default.nix
@@ -1,5 +1,6 @@
 {
   fediversityVm = {
+    vmId = 2187;
     proxmox = "procolix";
     description = "Wiki";