diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -838,7 +838,6 @@
     'DrydockLeaseSearchEngine' => 'applications/drydock/query/DrydockLeaseSearchEngine.php',
     'DrydockLeaseStatus' => 'applications/drydock/constants/DrydockLeaseStatus.php',
     'DrydockLeaseViewController' => 'applications/drydock/controller/DrydockLeaseViewController.php',
-    'DrydockLocalCommandInterface' => 'applications/drydock/interface/command/DrydockLocalCommandInterface.php',
     'DrydockLog' => 'applications/drydock/storage/DrydockLog.php',
     'DrydockLogController' => 'applications/drydock/controller/DrydockLogController.php',
     'DrydockLogListController' => 'applications/drydock/controller/DrydockLogListController.php',
@@ -846,6 +845,7 @@
     'DrydockLogQuery' => 'applications/drydock/query/DrydockLogQuery.php',
     'DrydockLogSearchEngine' => 'applications/drydock/query/DrydockLogSearchEngine.php',
     'DrydockManagementCloseWorkflow' => 'applications/drydock/management/DrydockManagementCloseWorkflow.php',
+    'DrydockManagementCommandWorkflow' => 'applications/drydock/management/DrydockManagementCommandWorkflow.php',
     'DrydockManagementLeaseWorkflow' => 'applications/drydock/management/DrydockManagementLeaseWorkflow.php',
     'DrydockManagementReleaseWorkflow' => 'applications/drydock/management/DrydockManagementReleaseWorkflow.php',
     'DrydockManagementWorkflow' => 'applications/drydock/management/DrydockManagementWorkflow.php',
@@ -4555,7 +4555,6 @@
     'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockLeaseStatus' => 'DrydockConstants',
     'DrydockLeaseViewController' => 'DrydockLeaseController',
-    'DrydockLocalCommandInterface' => 'DrydockCommandInterface',
     'DrydockLog' => array(
       'DrydockDAO',
       'PhabricatorPolicyInterface',
@@ -4566,6 +4565,7 @@
     'DrydockLogQuery' => 'DrydockQuery',
     'DrydockLogSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'DrydockManagementCloseWorkflow' => 'DrydockManagementWorkflow',
+    'DrydockManagementCommandWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementLeaseWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementReleaseWorkflow' => 'DrydockManagementWorkflow',
     'DrydockManagementWorkflow' => 'PhabricatorManagementWorkflow',
diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php
@@ -119,11 +119,37 @@
   }
 
   public function getInterface(
+    DrydockBlueprint $blueprint,
     DrydockResource $resource,
     DrydockLease $lease,
     $type) {
-    // TODO: Actually do stuff here, this needs work and currently makes this
-    // entire exercise pointless.
+
+    $viewer = PhabricatorUser::getOmnipotentUser();
+
+    switch ($type) {
+      case DrydockCommandInterface::INTERFACE_TYPE:
+        $credential_phid = $blueprint->getFieldValue('credentialPHID');
+        $binding_phid = $resource->getAttribute('almanacBindingPHID');
+
+        $binding = id(new AlmanacBindingQuery())
+          ->setViewer($viewer)
+          ->withPHIDs(array($binding_phid))
+          ->executeOne();
+        if (!$binding) {
+          // TODO: This is probably a permanent failure, destroy this resource?
+          throw new Exception(
+            pht(
+              'Unable to load binding "%s" to create command interface.',
+              $binding_phid));
+        }
+
+        $interface = $binding->getInterface();
+
+        return id(new DrydockSSHCommandInterface())
+          ->setConfig('credentialPHID', $credential_phid)
+          ->setConfig('host', $interface->getAddress())
+          ->setConfig('port', $interface->getPort());
+    }
   }
 
   public function getFieldSpecifications() {
diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php
@@ -1,61 +1,23 @@
 <?php
 
 /**
- * @task lease      Lease Acquisition
- * @task resource   Resource Allocation
- * @task log        Logging
+ * @task lease Lease Acquisition
+ * @task resource Resource Allocation
+ * @task interface Resource Interfaces
+ * @task log Logging
  */
 abstract class DrydockBlueprintImplementation extends Phobject {
 
   private $activeResource;
   private $activeLease;
-  private $instance;
 
   abstract public function getType();
-  abstract public function getInterface(
-    DrydockResource $resource,
-    DrydockLease $lease,
-    $type);
 
   abstract public function isEnabled();
 
   abstract public function getBlueprintName();
   abstract public function getDescription();
 
-  public function getBlueprintClass() {
-    return get_class($this);
-  }
-
-  protected function loadLease($lease_id) {
-    // TODO: Get rid of this?
-    $query = id(new DrydockLeaseQuery())
-      ->setViewer(PhabricatorUser::getOmnipotentUser())
-      ->withIDs(array($lease_id))
-      ->execute();
-
-    $lease = idx($query, $lease_id);
-
-    if (!$lease) {
-      throw new Exception(pht("No such lease '%d'!", $lease_id));
-    }
-
-    return $lease;
-  }
-
-  protected function getInstance() {
-    if (!$this->instance) {
-      throw new Exception(
-        pht('Attach the blueprint instance to the implementation.'));
-    }
-
-    return $this->instance;
-  }
-
-  public function attachInstance(DrydockBlueprint $instance) {
-    $this->instance = $instance;
-    return $this;
-  }
-
   public function getFieldSpecifications() {
     return array();
   }
@@ -105,6 +67,7 @@
     DrydockResource $resource,
     DrydockLease $lease);
 
+
   final public function releaseLease(
     DrydockBlueprint $blueprint,
     DrydockResource $resource,
@@ -236,6 +199,16 @@
     DrydockLease $lease);
 
 
+/* -(  Resource Interfaces  )------------------------------------------------ */
+
+
+  abstract public function getInterface(
+    DrydockBlueprint $blueprint,
+    DrydockResource $resource,
+    DrydockLease $lease,
+    $type);
+
+
 /* -(  Logging  )------------------------------------------------------------ */
 
 
@@ -308,7 +281,7 @@
     $this->log(
       pht(
         "Blueprint '%s': Created New Template",
-        $this->getBlueprintClass()));
+        get_class($this)));
 
     return $resource;
   }
diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
--- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
+++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php
@@ -122,18 +122,11 @@
   }
 
   public function getInterface(
+    DrydockBlueprint $blueprint,
     DrydockResource $resource,
     DrydockLease $lease,
     $type) {
-
-    switch ($type) {
-      case 'command':
-        return $this
-          ->loadLease($resource->getAttribute('lease.host'))
-          ->getInterface($type);
-    }
-
-    throw new Exception(pht("No interface of type '%s'.", $type));
+    // TODO: This blueprint doesn't work at all.
   }
 
 }
diff --git a/src/applications/drydock/interface/DrydockInterface.php b/src/applications/drydock/interface/DrydockInterface.php
--- a/src/applications/drydock/interface/DrydockInterface.php
+++ b/src/applications/drydock/interface/DrydockInterface.php
@@ -2,12 +2,12 @@
 
 abstract class DrydockInterface extends Phobject {
 
-  private $config;
+  private $config = array();
 
   abstract public function getInterfaceType();
 
-  final public function setConfiguration(array $config) {
-    $this->config = $config;
+  final public function setConfig($key, $value) {
+    $this->config[$key] = $value;
     return $this;
   }
 
diff --git a/src/applications/drydock/interface/command/DrydockCommandInterface.php b/src/applications/drydock/interface/command/DrydockCommandInterface.php
--- a/src/applications/drydock/interface/command/DrydockCommandInterface.php
+++ b/src/applications/drydock/interface/command/DrydockCommandInterface.php
@@ -2,6 +2,8 @@
 
 abstract class DrydockCommandInterface extends DrydockInterface {
 
+  const INTERFACE_TYPE = 'command';
+
   private $workingDirectory;
 
   public function setWorkingDirectory($working_directory) {
@@ -14,7 +16,7 @@
   }
 
   final public function getInterfaceType() {
-    return 'command';
+    return self::INTERFACE_TYPE;
   }
 
   final public function exec($command) {
@@ -38,7 +40,7 @@
   protected function applyWorkingDirectoryToArgv(array $argv) {
     if ($this->getWorkingDirectory() !== null) {
       $cmd = $argv[0];
-      $cmd = "(cd %s; {$cmd})";
+      $cmd = "(cd %s && {$cmd})";
       $argv = array_merge(
         array($cmd),
         array($this->getWorkingDirectory()),
diff --git a/src/applications/drydock/interface/command/DrydockLocalCommandInterface.php b/src/applications/drydock/interface/command/DrydockLocalCommandInterface.php
deleted file mode 100644
--- a/src/applications/drydock/interface/command/DrydockLocalCommandInterface.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-final class DrydockLocalCommandInterface extends DrydockCommandInterface {
-
-  public function getExecFuture($command) {
-    $argv = func_get_args();
-    $argv = $this->applyWorkingDirectoryToArgv($argv);
-
-    return newv('ExecFuture', $argv);
-  }
-
-}
diff --git a/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php b/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
--- a/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
+++ b/src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
@@ -2,35 +2,19 @@
 
 final class DrydockSSHCommandInterface extends DrydockCommandInterface {
 
-  private $passphraseSSHKey;
+  private $credential;
   private $connectTimeout;
 
-  private function openCredentialsIfNotOpen() {
-    if ($this->passphraseSSHKey !== null) {
-      return;
-    }
-
-    $credential = id(new PassphraseCredentialQuery())
-      ->setViewer(PhabricatorUser::getOmnipotentUser())
-      ->withIDs(array($this->getConfig('credential')))
-      ->needSecrets(true)
-      ->executeOne();
-
-    if ($credential === null) {
-      throw new Exception(
-        pht(
-          'There is no credential with ID %d.',
-          $this->getConfig('credential')));
-    }
+  private function loadCredential() {
+    if ($this->credential === null) {
+      $credential_phid = $this->getConfig('credentialPHID');
 
-    if ($credential->getProvidesType() !==
-      PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE) {
-      throw new Exception(pht('Only private key credentials are supported.'));
+      $this->credential = PassphraseSSHKey::loadFromPHID(
+        $credential_phid,
+        PhabricatorUser::getOmnipotentUser());
     }
 
-    $this->passphraseSSHKey = PassphraseSSHKey::loadFromPHID(
-      $credential->getPHID(),
-      PhabricatorUser::getOmnipotentUser());
+    return $this->credential;
   }
 
   public function setConnectTimeout($timeout) {
@@ -39,30 +23,36 @@
   }
 
   public function getExecFuture($command) {
-    $this->openCredentialsIfNotOpen();
+    $credential = $this->loadCredential();
 
     $argv = func_get_args();
     $argv = $this->applyWorkingDirectoryToArgv($argv);
     $full_command = call_user_func_array('csprintf', $argv);
 
-    $command_timeout = '';
-    if ($this->connectTimeout !== null) {
-      $command_timeout = csprintf(
-        '-o %s',
-        'ConnectTimeout='.$this->connectTimeout);
+    $flags = array();
+    $flags[] = '-o';
+    $flags[] = 'LogLevel=quiet';
+
+    $flags[] = '-o';
+    $flags[] = 'StrictHostKeyChecking=no';
+
+    $flags[] = '-o';
+    $flags[] = 'UserKnownHostsFile=/dev/null';
+
+    $flags[] = '-o';
+    $flags[] = 'BatchMode=yes';
+
+    if ($this->connectTimeout) {
+      $flags[] = '-o';
+      $flags[] = 'ConnectTimeout='.$this->connectTimeout;
     }
 
     return new ExecFuture(
-      'ssh '.
-      '-o LogLevel=quiet '.
-      '-o StrictHostKeyChecking=no '.
-      '-o UserKnownHostsFile=/dev/null '.
-      '-o BatchMode=yes '.
-      '%C -p %s -i %P %P@%s -- %s',
-      $command_timeout,
+      'ssh %Ls -l %P -p %s -i %P %s -- %s',
+      $flags,
+      $credential->getUsernameEnvelope(),
       $this->getConfig('port'),
-      $this->passphraseSSHKey->getKeyfileEnvelope(),
-      $this->passphraseSSHKey->getUsernameEnvelope(),
+      $credential->getKeyfileEnvelope(),
       $this->getConfig('host'),
       $full_command);
   }
diff --git a/src/applications/drydock/management/DrydockManagementCommandWorkflow.php b/src/applications/drydock/management/DrydockManagementCommandWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/drydock/management/DrydockManagementCommandWorkflow.php
@@ -0,0 +1,66 @@
+<?php
+
+final class DrydockManagementCommandWorkflow
+  extends DrydockManagementWorkflow {
+
+  protected function didConstruct() {
+    $this
+      ->setName('command')
+      ->setSynopsis(pht('Run a command on a leased resource.'))
+      ->setArguments(
+        array(
+          array(
+            'name' => 'lease',
+            'param' => 'id',
+            'help' => pht('Lease ID.'),
+          ),
+          array(
+            'name' => 'argv',
+            'wildcard' => true,
+            'help' => pht('Command to execute.'),
+          ),
+        ));
+  }
+
+  public function execute(PhutilArgumentParser $args) {
+    $lease_id = $args->getArg('lease');
+    if (!$lease_id) {
+      throw new PhutilArgumentUsageException(
+        pht(
+          'Use %s to specify a lease.',
+          '--lease'));
+    }
+
+    $argv = $args->getArg('argv');
+    if (!$argv) {
+      throw new PhutilArgumentUsageException(
+        pht(
+          'Specify a command to run.'));
+    }
+
+    $lease = id(new DrydockLeaseQuery())
+      ->setViewer($this->getViewer())
+      ->withIDs(array($lease_id))
+      ->executeOne();
+    if (!$lease) {
+      throw new Exception(
+        pht(
+          'Unable to load lease with ID "%s"!',
+          $lease_id));
+    }
+
+    // TODO: Check lease state, etc.
+
+    $interface = $lease->getInterface(DrydockCommandInterface::INTERFACE_TYPE);
+
+    list($stdout, $stderr) = call_user_func_array(
+      array($interface, 'execx'),
+      array('%Ls', $argv));
+
+    fprintf(STDOUT, $stdout);
+    fprintf(STDERR, $stderr);
+
+    return 0;
+  }
+
+}
diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php
--- a/src/applications/drydock/storage/DrydockBlueprint.php
+++ b/src/applications/drydock/storage/DrydockBlueprint.php
@@ -173,6 +173,24 @@
     return $this;
   }
 
+  public function getInterface(
+    DrydockResource $resource,
+    DrydockLease $lease,
+    $type) {
+
+    $interface = $this->getImplementation()
+      ->getInterface($this, $resource, $lease, $type);
+
+    if (!$interface) {
+      throw new Exception(
+        pht(
+          'Unable to build resource interface of type "%s".',
+          $type));
+    }
+
+    return $interface;
+  }
+
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */