diff --git a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryCreateController.php
@@ -20,6 +20,7 @@
     // the latter two cases, we show only a few of the pages.
 
     $repository = null;
+    $service = null;
     switch ($this->edit) {
       case 'remote':
       case 'policy':
@@ -40,6 +41,38 @@
         $this->requireApplicationCapability(
           DiffusionCreateRepositoriesCapability::CAPABILITY);
 
+        // Pick a random open service to allocate this repository on, if any
+        // exist. If there are no services, we aren't in cluster mode and
+        // will allocate locally. If there are services but none permit
+        // allocations, we fail.
+        $services = id(new AlmanacServiceQuery())
+          ->setViewer(PhabricatorUser::getOmnipotentUser())
+          ->withServiceClasses(
+            array(
+              'AlmanacClusterRepositoryServiceType',
+            ))
+          ->execute();
+        if ($services) {
+          // Filter out services which do not permit new allocations.
+          foreach ($services as $key => $possible_service) {
+            if ($possible_service->getAlmanacPropertyValue('closed')) {
+              unset($services[$key]);
+            }
+          }
+
+          if (!$services) {
+            throw new Exception(
+              pht(
+                'This install is configured in cluster mode, but all '.
+                'available repository cluster services are closed to new '.
+                'allocations. At least one service must be open to allow '.
+                'new allocations to take place.'));
+          }
+
+          shuffle($services);
+          $service = head($services);
+        }
+
         $cancel_uri = $this->getApplicationURI('new/');
         break;
       default:
@@ -110,6 +143,7 @@
         $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
         $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
         $type_push = PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY;
+        $type_service = PhabricatorRepositoryTransaction::TYPE_SERVICE;
 
         $xactions = array();
 
@@ -141,8 +175,13 @@
             ->getControl('activate')->getValue();
           $xactions[] = id(clone $template)
             ->setTransactionType($type_activate)
-            ->setNewValue(
-              ($activate == 'start'));
+            ->setNewValue(($activate == 'start'));
+
+          if ($service) {
+            $xactions[] = id(clone $template)
+              ->setTransactionType($type_service)
+              ->setNewValue($service->getPHID());
+          }
 
           $default_local_path = PhabricatorEnv::getEnvConfig(
             'repository.default-local-path');
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
@@ -575,6 +575,21 @@
       ->setUser($viewer)
       ->setActionList($actions);
 
+    $service_phid = $repository->getAlmanacServicePHID();
+    if ($service_phid) {
+      $handles = $this->loadViewerHandles(array($service_phid));
+      $v_service = $handles[$service_phid]->renderLink();
+    } else {
+      $v_service = phutil_tag(
+        'em',
+        array(),
+        pht('Local'));
+    }
+
+    $view->addProperty(
+      pht('Storage Service'),
+      $v_service);
+
     $view->addProperty(
       pht('Storage Path'),
       $repository->getHumanReadableDetail('local-path'));
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStorageController.php
@@ -33,8 +33,28 @@
 
     $title = pht('Edit %s', $repository->getName());
 
+    $service_phid = $repository->getAlmanacServicePHID();
+    if ($service_phid) {
+      $handles = $this->loadViewerHandles(array($service_phid));
+      $v_service = $handles[$service_phid]->renderLink();
+    } else {
+      $v_service = phutil_tag(
+        'em',
+        array(),
+        pht('Local'));
+    }
+
     $form = id(new AphrontFormView())
       ->setUser($user)
+      ->appendChild(
+        id(new AphrontFormMarkupControl())
+          ->setLabel(pht('Storage Service'))
+          ->setValue($v_service))
+      ->appendChild(
+        id(new AphrontFormMarkupControl())
+          ->setName('local')
+          ->setLabel(pht('Storage Path'))
+          ->setValue($v_local))
       ->appendRemarkupInstructions(
         pht(
           "You can not adjust the local path for this repository from the ".
@@ -43,11 +63,6 @@
           $repository->getCallsign(),
           $user->getUsername()))
       ->appendChild(
-        id(new AphrontFormMarkupControl())
-          ->setName('local')
-          ->setLabel(pht('Local Path'))
-          ->setValue($v_local))
-      ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($edit_uri, pht('Done')));
 
diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
--- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -40,6 +40,7 @@
     $types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL;
     $types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS;
     $types[] = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME;
+    $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE;
 
     $types[] = PhabricatorTransactions::TYPE_EDGE;
     $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
@@ -95,6 +96,8 @@
         return $object->shouldAllowDangerousChanges();
       case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
         return $object->getDetail('clone-name');
+      case PhabricatorRepositoryTransaction::TYPE_SERVICE:
+        return $object->getAlmanacServicePHID();
     }
   }
 
@@ -127,6 +130,7 @@
       case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
       case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
       case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
+      case PhabricatorRepositoryTransaction::TYPE_SERVICE:
         return $xaction->getNewValue();
       case PhabricatorRepositoryTransaction::TYPE_NOTIFY:
       case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE:
@@ -198,6 +202,9 @@
       case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
         $object->setDetail('clone-name', $xaction->getNewValue());
         return;
+      case PhabricatorRepositoryTransaction::TYPE_SERVICE:
+        $object->setAlmanacServicePHID($xaction->getNewValue());
+        return;
       case PhabricatorRepositoryTransaction::TYPE_ENCODING:
         // Make sure the encoding is valid by converting to UTF-8. This tests
         // that the user has mbstring installed, and also that they didn't type
@@ -306,6 +313,7 @@
       case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL:
       case PhabricatorRepositoryTransaction::TYPE_DANGEROUS:
       case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME:
+      case PhabricatorRepositoryTransaction::TYPE_SERVICE:
         PhabricatorPolicyFilter::requireCapability(
           $this->requireActor(),
           $object,
diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php
--- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php
@@ -24,6 +24,7 @@
   const TYPE_CREDENTIAL = 'repo:credential';
   const TYPE_DANGEROUS = 'repo:dangerous';
   const TYPE_CLONE_NAME = 'repo:clone-name';
+  const TYPE_SERVICE = 'repo:service';
 
   // TODO: Clean up these legacy transaction types.
   const TYPE_SSH_LOGIN = 'repo:ssh-login';
@@ -52,8 +53,13 @@
 
     switch ($this->getTransactionType()) {
       case self::TYPE_PUSH_POLICY:
-        $phids[] = $old;
-        $phids[] = $new;
+      case self::TYPE_SERVICE:
+        if ($old) {
+          $phids[] = $old;
+        }
+        if ($new) {
+          $phids[] = $new;
+        }
         break;
     }
 
@@ -367,6 +373,26 @@
             $old,
             $new);
         }
+      case self::TYPE_SERVICE:
+        if (strlen($old) && !strlen($new)) {
+          return pht(
+            '%s moved storage for this repository from %s to local.',
+            $this->renderHandleLink($author_phid),
+            $this->renderHandleLink($old));
+        } else if (!strlen($old) && strlen($new)) {
+          // TODO: Possibly, we should distinguish between automatic assignment
+          // on creation vs explicit adjustment.
+          return pht(
+            '%s set storage for this repository to %s.',
+            $this->renderHandleLink($author_phid),
+            $this->renderHandleLink($new));
+        } else {
+          return pht(
+            '%s moved storage for this repository from %s to %s.',
+            $this->renderHandleLink($author_phid),
+            $this->renderHandleLink($old),
+            $this->renderHandleLink($new));
+        }
     }
 
     return parent::getTitle();