diff --git a/src/runtime.c b/src/runtime.c
index 592e2a1d2b3a9515f287bfc53d08fd8271649f14..cd21c859da8c6fff2630b196cee10e0ac49f5389 100644
--- a/src/runtime.c
+++ b/src/runtime.c
@@ -328,6 +328,122 @@ pv_runtime_constructed (GObject *object)
   g_return_if_fail (self->tools_dir != NULL);
 }
 
+static gboolean
+pv_runtime_garbage_collect (PvRuntime *self,
+                            PvBwrapLock *mutable_parent_lock,
+                            GError **error)
+{
+  g_auto(GLnxDirFdIterator) iter = { FALSE };
+
+  g_return_val_if_fail (PV_IS_RUNTIME (self), FALSE);
+  g_return_val_if_fail (self->mutable_parent != NULL, FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+  /* We don't actually *use* this: it just acts as an assertion that
+   * we are holding the lock on the parent directory. */
+  g_return_val_if_fail (mutable_parent_lock != NULL, FALSE);
+
+  if (!glnx_dirfd_iterator_init_at (AT_FDCWD, self->mutable_parent,
+                                    TRUE, &iter, error))
+    return FALSE;
+
+  while (TRUE)
+    {
+      g_autoptr(GError) local_error = NULL;
+      g_autoptr(PvBwrapLock) temp_lock = NULL;
+      g_autofree gchar *keep = NULL;
+      g_autofree gchar *ref = NULL;
+      struct dirent *dent;
+      struct stat ignore;
+
+      if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&iter, &dent,
+                                                       NULL, error))
+        return FALSE;
+
+      if (dent == NULL)
+        break;
+
+      switch (dent->d_type)
+        {
+          case DT_DIR:
+            break;
+
+          case DT_BLK:
+          case DT_CHR:
+          case DT_FIFO:
+          case DT_LNK:
+          case DT_REG:
+          case DT_SOCK:
+          case DT_UNKNOWN:
+          default:
+            g_debug ("Ignoring %s/%s: not a directory",
+                     self->mutable_parent, dent->d_name);
+            continue;
+        }
+
+      if (!g_str_has_prefix (dent->d_name, "tmp-"))
+        {
+          g_debug ("Ignoring %s/%s: not tmp-*",
+                   self->mutable_parent, dent->d_name);
+          continue;
+        }
+
+      g_debug ("Found temporary runtime %s/%s, considering whether to "
+               "delete it...",
+               self->mutable_parent, dent->d_name);
+
+      keep = g_build_filename (dent->d_name, "keep", NULL);
+
+      if (glnx_fstatat (self->mutable_parent_fd, keep, &ignore,
+                        AT_SYMLINK_NOFOLLOW, &local_error))
+        {
+          g_debug ("Not deleting \"%s/%s\": ./keep exists",
+                   self->mutable_parent, dent->d_name);
+          continue;
+        }
+      else if (!g_error_matches (local_error, G_IO_ERROR,
+                                 G_IO_ERROR_NOT_FOUND))
+        {
+          /* EACCES or something? Give it the benefit of the doubt */
+          g_warning ("Not deleting \"%s/%s\": unable to stat ./keep: %s",
+                   self->mutable_parent, dent->d_name, local_error->message);
+          g_clear_error (&local_error);
+          continue;
+        }
+
+      g_clear_error (&local_error);
+
+      ref = g_build_filename (dent->d_name, ".ref", NULL);
+      temp_lock = pv_bwrap_lock_new (self->mutable_parent_fd, ref,
+                                     (PV_BWRAP_LOCK_FLAGS_CREATE |
+                                      PV_BWRAP_LOCK_FLAGS_WRITE),
+                                     &local_error);
+
+      if (temp_lock == NULL)
+        {
+          g_debug ("Ignoring \"%s/%s\": unable to get lock: %s",
+                   self->mutable_parent, dent->d_name,
+                   local_error->message);
+          g_clear_error (&local_error);
+          continue;
+        }
+
+      g_debug ("Deleting \"%s/%s\"...", self->mutable_parent, dent->d_name);
+
+      /* We have the lock, which would not have happened if someone was
+       * still using the runtime, so we can safely delete it. */
+      if (!glnx_shutil_rm_rf_at (self->mutable_parent_fd, dent->d_name,
+                                 NULL, &local_error))
+        {
+          g_debug ("Unable to delete %s/%s: %s",
+                   self->mutable_parent, dent->d_name, local_error->message);
+          g_clear_error (&local_error);
+          continue;
+        }
+    }
+
+  return TRUE;
+}
+
 static gboolean
 pv_runtime_init_mutable (PvRuntime *self,
                          GError **error)
@@ -357,8 +473,7 @@ pv_runtime_init_mutable (PvRuntime *self,
     return FALSE;
 
   /* Lock the parent directory. Anything that directly manipulates the
-   * temporary runtimes (notably the deploy-runtime and run-in-steamrt
-   * scripts in SteamLinuxRuntime) is expected to do the same, so that
+   * temporary runtimes is expected to do the same, so that
    * it cannot be deleting temporary runtimes at the same time we're
    * creating them.
    *
@@ -373,6 +488,13 @@ pv_runtime_init_mutable (PvRuntime *self,
     return glnx_prefix_error (error, "Unable to lock \"%s/%s\"",
                               self->mutable_parent, ".ref");
 
+  /* GC old runtimes (if they have become unused) before we create a
+   * new one. This means we should only ever have one temporary runtime
+   * copy per game that is run concurrently. */
+  if ((self->flags & PV_RUNTIME_FLAGS_GC_RUNTIMES) != 0 &&
+      !pv_runtime_garbage_collect (self, mutable_lock, error))
+    return FALSE;
+
   temp_dir = g_build_filename (self->mutable_parent, "tmp-XXXXXX", NULL);
 
   if (g_mkdtemp (temp_dir) == NULL)
diff --git a/src/runtime.h b/src/runtime.h
index 078b47caa4bfc8ebcb343c87087221f48b49141d..3c004c72254b4782baff9fc056e327efc110cc6f 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -33,6 +33,7 @@
  * PvRuntimeFlags:
  * @PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK: Use host system graphics stack
  * @PV_RUNTIME_FLAGS_GENERATE_LOCALES: Generate missing locales
+ * @PV_RUNTIME_FLAGS_GC_RUNTIMES: Garbage-collect old temporary runtimes
  * @PV_RUNTIME_FLAGS_NONE: None of the above
  *
  * Flags affecting how we set up the runtime.
@@ -41,12 +42,14 @@ typedef enum
 {
   PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK = (1 << 0),
   PV_RUNTIME_FLAGS_GENERATE_LOCALES = (1 << 1),
+  PV_RUNTIME_FLAGS_GC_RUNTIMES = (1 << 2),
   PV_RUNTIME_FLAGS_NONE = 0
 } PvRuntimeFlags;
 
 #define PV_RUNTIME_FLAGS_MASK \
   (PV_RUNTIME_FLAGS_HOST_GRAPHICS_STACK \
    | PV_RUNTIME_FLAGS_GENERATE_LOCALES \
+   | PV_RUNTIME_FLAGS_GC_RUNTIMES \
    )
 
 typedef struct _PvRuntime PvRuntime;
diff --git a/src/wrap.c b/src/wrap.c
index 386a14b02a811287d828ef0cfffc3f27d314c69c..2791c167fc7134bbd0d2d2528c3a1ea117f61627 100644
--- a/src/wrap.c
+++ b/src/wrap.c
@@ -330,6 +330,7 @@ static char **opt_env_if_host = NULL;
 static char *opt_fake_home = NULL;
 static char *opt_freedesktop_app_id = NULL;
 static char *opt_steam_app_id = NULL;
+static gboolean opt_gc_runtimes = TRUE;
 static gboolean opt_generate_locales = TRUE;
 static char *opt_home = NULL;
 static gboolean opt_host_fallback = FALSE;
@@ -525,6 +526,15 @@ static GOptionEntry options[] =
     G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_steam_app_id,
     "Make --unshare-home use ~/.var/app/com.steampowered.AppN "
     "as home directory. [Default: $SteamAppId]", "N" },
+  { "gc-runtimes", '\0',
+    G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_gc_runtimes,
+    "If using --copy-runtime-into, garbage-collect old temporary "
+    "runtimes. [Default, unless $PRESSURE_VESSEL_GC_RUNTIMES is 0]",
+    NULL },
+  { "no-gc-runtimes", '\0',
+    G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_gc_runtimes,
+    "If using --copy-runtime-into, don't garbage-collect old "
+    "temporary runtimes.", NULL },
   { "generate-locales", '\0',
     G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_generate_locales,
     "If using --runtime, attempt to generate any missing locales. "
@@ -753,6 +763,7 @@ main (int argc,
   opt_remove_game_overlay = boolean_environment ("PRESSURE_VESSEL_REMOVE_GAME_OVERLAY",
                                                  FALSE);
   opt_share_home = tristate_environment ("PRESSURE_VESSEL_SHARE_HOME");
+  opt_gc_runtimes = boolean_environment ("PRESSURE_VESSEL_GC_RUNTIMES", TRUE);
   opt_generate_locales = boolean_environment ("PRESSURE_VESSEL_GENERATE_LOCALES", TRUE);
   opt_host_graphics = boolean_environment ("PRESSURE_VESSEL_HOST_GRAPHICS",
                                            TRUE);
@@ -1103,6 +1114,9 @@ main (int argc,
     {
       PvRuntimeFlags flags = PV_RUNTIME_FLAGS_NONE;
 
+      if (opt_gc_runtimes)
+        flags |= PV_RUNTIME_FLAGS_GC_RUNTIMES;
+
       if (opt_generate_locales)
         flags |= PV_RUNTIME_FLAGS_GENERATE_LOCALES;