diff --git a/package.json b/package.json
index 57c45121b1f10058476b0eef314f16c855025356..a2cd5c9afcad895d5997cb7f7c328c4225b311cc 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,42 @@
 {
-    "name": "browserid"
-  , "version": "0.0.1"
-  , "private": true
-  , "dependencies": {
-      "JSONSelect": "0.3.0"
-    , "bcrypt": "0.4.1"
-    , "compute-cluster": "0.0.6"
-    , "connect": "1.7.2"
-    , "client-sessions": "0.0.3"
-    , "connect-cookie-session": "0.0.2"
-    , "connect-logger-statsd": "0.0.1"
-    , "ejs": "0.4.3"
-    , "express": "2.5.0"
-    , "jwcrypto": "0.1.1"
-    , "mustache": "0.3.1-dev"
-    , "mysql" : "0.9.5"
-    , "node-gettext": "0.1.1"
-    , "node-statsd": "https://github.com/downloads/lloyd/node-statsd/3a73de.tgz"
-    , "nodemailer": "0.1.18"
-    , "optimist" : "0.2.8"
-    , "postprocess": "0.0.3"
-    , "semver": "1.0.12"
-    , "temp": "0.2.0"
-    , "uglify-js": "1.0.6"
-    , "uglifycss": "0.0.4"
-    , "urlparse": "0.0.1"
-    , "vows": "0.5.13"
-    , "winston" : "0.5.6"
-  }
-  , "scripts": {
-    "postinstall": "./scripts/generate_ephemeral_keys.sh",
-    "test": "./scripts/run_all_tests.sh",
-    "start": "./scripts/run_locally.js"
-  },
-  "engines": {
-    "node": ">= 0.6.2"
-  }
+    "name": "browserid",
+    "version": "0.0.1",
+    "private": true,
+    "dependencies": {
+        "JSONSelect": "0.4.0",
+        "bcrypt": "0.4.1",
+        "compute-cluster": "0.0.6",
+        "connect": "1.7.2",
+        "client-sessions": "0.0.3",
+        "connect-cookie-session": "0.0.2",
+        "connect-logger-statsd": "0.0.1",
+        "ejs": "0.4.3",
+        "express": "2.5.0",
+        "jwcrypto": "0.1.1",
+        "mustache": "0.3.1-dev",
+        "mysql": "0.9.5",
+        "node-gettext": "0.1.1",
+        "node-statsd": "https://github.com/downloads/lloyd/node-statsd/3a73de.tgz",
+        "nodemailer": "0.1.18",
+        "optimist": "0.2.8",
+        "postprocess": "0.0.3",
+        "semver": "1.0.12",
+        "temp": "0.2.0",
+        "uglify-js": "1.0.6",
+        "uglifycss": "0.0.4",
+        "urlparse": "0.0.1",
+        "winston": "0.5.6"
+    },
+    "devDependencies": {
+        "vows": "0.5.13",
+        "aws-lib": "0.0.5"
+    }, 
+    "scripts": {
+        "postinstall": "./scripts/generate_ephemeral_keys.sh",
+        "test": "./scripts/run_all_tests.sh",
+        "start": "./scripts/run_locally.js"
+    },
+    "engines": {
+        "node": ">= 0.6.2"
+    }
 }
diff --git a/scripts/deploy.js b/scripts/deploy.js
new file mode 100755
index 0000000000000000000000000000000000000000..77dd41a0cbff10eb53fd1026c9b858af91277664
--- /dev/null
+++ b/scripts/deploy.js
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+
+const
+aws = require('./deploy/aws.js');
+path = require('path');
+vm = require('./deploy/vm.js'),
+key = require('./deploy/key.js');
+
+var verbs = {};
+
+function checkErr(err) {
+  if (err) {
+    process.stderr.write('fatal error: ' + err + "\n");
+    process.exit(1);
+  }
+}
+
+verbs['deploy'] = function(args) {
+  vm.startImage(function(err, r) {
+    checkErr(err);
+    vm.waitForInstance(r.instanceId, function(err, r) {
+      console.log(err, r);
+    });
+  });
+};
+
+verbs['list'] = function(args) {
+  vm.list(function(err, r) {
+    checkErr(err);
+    console.log(JSON.stringify(r, null, 2));
+  });
+};
+
+var error = (process.argv.length <= 2); 
+
+if (!error) {
+  var verb = process.argv[2];
+  if (!verbs[verb]) error = "no such command: " + verb;
+  else {
+    verbs[verb](process.argv.slice(2));
+  }
+}
+
+if (error) {
+  if (typeof error === 'string') process.stderr.write('fatal error: ' + error + "\n\n");
+
+  process.stderr.write('A command line tool to deploy BrowserID onto Amazon\'s EC2\n');
+  process.stderr.write('Usage: ' + path.basename(__filename) +
+                       ' <' + Object.keys(verbs).join('|') + "> [args]\n");
+  process.exit(1);
+}
diff --git a/scripts/deploy/aws.js b/scripts/deploy/aws.js
new file mode 100644
index 0000000000000000000000000000000000000000..6641989e9a8dc348d0b4d843d4c594386ec64a91
--- /dev/null
+++ b/scripts/deploy/aws.js
@@ -0,0 +1,7 @@
+const
+awslib = require('aws-lib');
+
+module.exports = awslib.createEC2Client(process.env['AWS_ID'], process.env['AWS_SECRET'], {
+  version: '2011-12-15'
+});
+
diff --git a/scripts/deploy/key.js b/scripts/deploy/key.js
new file mode 100644
index 0000000000000000000000000000000000000000..d93da0158159b5d43f7361189c7fda12381e97ae
--- /dev/null
+++ b/scripts/deploy/key.js
@@ -0,0 +1,57 @@
+const
+aws = require('./aws.js'),
+path = require('path'),
+fs = require('fs'),
+child_process = require('child_process'),
+jsel = require('JSONSelect'),
+crypto = require('crypto');
+
+const keyPath = process.env['PUBKEY'] || path.join(process.env['HOME'], ".ssh", "id_rsa.pub");
+
+exports.read = function(cb) {
+  fs.readFile(keyPath, cb);
+};
+
+exports.fingerprint = function(cb) {
+  exports.read(function(err, buf) {
+    if (err) return cb(err);
+    var b = new Buffer(buf.toString().split(' ')[1], 'base64');
+    var md5sum = crypto.createHash('md5');
+    md5sum.update(b);
+    cb(null, md5sum.digest('hex'));
+  });
+/*
+  child_process.exec(
+    "ssh-keygen -lf " + keyPath,
+    function(err, r) {
+      if (!err) r = r.split(' ')[1];
+      cb(err, r);
+    });
+*/
+};
+
+exports.getName = function(cb) {
+  exports.fingerprint(function(err, fingerprint) {
+    if (err) return cb(err);
+
+    var keyName = "browserid deploy key (" + fingerprint + ")";
+
+    // is this fingerprint known?
+    aws.call('DescribeKeyPairs', {}, function(result) {
+      var found = jsel.match(":has(.keyName:val(?)) > .keyName", [ keyName ], result); 
+      if (found.length) return cb(null, keyName);
+
+      // key isn't yet installed!
+      exports.read(function(err, key) {
+        aws.call('ImportKeyPair', {
+          KeyName: keyName,
+          PublicKeyMaterial: new Buffer(key).toString('base64')
+        }, function(result) {
+          if (!result) return cb('null result from ec2 on key addition');
+          if (result.Errors) return cb(result.Errors.Error.Message);
+          cb(null, keyName);
+        });
+      });
+    });
+  });
+};
diff --git a/scripts/deploy/vm.js b/scripts/deploy/vm.js
new file mode 100644
index 0000000000000000000000000000000000000000..e8d43f0797249db8376473926fb0dc29e0a4bd35
--- /dev/null
+++ b/scripts/deploy/vm.js
@@ -0,0 +1,69 @@
+const
+aws = require('./aws.js');
+jsel = require('JSONSelect'),
+key = require('./key.js');
+
+const BROWSERID_TEMPLATE_IMAGE_ID = 'ami-51ac7d38';
+
+function extractInstanceDeets(horribleBlob) {
+  var instance = {};
+  ["instanceId", "imageId", "instanceState", "dnsName", "keyName", "instanceType",
+   "ipAddress"].forEach(function(key) {
+     if (horribleBlob[key]) instance[key] = horribleBlob[key];
+   });
+  return instance;
+}
+
+exports.list = function(cb) {
+  aws.call('DescribeInstances', {}, function(result) {
+    var instances = [];
+    jsel.forEach(".instancesSet > .item", result, function(item) {
+      instances.push(extractInstanceDeets(item));
+    });
+    cb(null, instances);
+  });
+};
+
+function returnSingleImageInfo(result, cb) {
+  if (!result) return cb('no results from ec2 api');
+  try { return cb(result.Errors.Error.Message); } catch(e) {};
+  try { 
+    result = jsel.match(".instancesSet > .item", result)[0];
+    cb(null, extractInstanceDeets(result));
+  } catch(e) {
+    return cb("couldn't extract new instance details from ec2 response: " + e);
+  } 
+}
+
+exports.startImage = function(cb) {
+  key.getName(function(err, r) {
+    if (err) return cb(err);
+    aws.call('RunInstances', {
+      ImageId: BROWSERID_TEMPLATE_IMAGE_ID,
+      KeyName: r,
+      InstanceType: 't1.micro',
+      MinCount: 1,
+      MaxCount: 1
+    }, function (result) {
+      returnSingleImageInfo(result, cb);
+    });
+  });
+};
+
+exports.waitForInstance = function(id, cb) {
+  aws.call('DescribeInstanceStatus', {
+    InstanceId: id
+  }, function(r) {
+    if (!r) return cb('no response from ec2');
+    if (!r.instanceStatusSet) return cb('malformed response from ec2');
+    if (Object.keys(r.instanceStatusSet).length) {
+      var deets = extractInstanceDeets(r.instanceStatusSet.item);
+      if (deets && deets.instanceState && deets.instanceState.name === 'running') {
+        return aws.call('DescribeInstances', { InstanceId: id }, function(result) {
+          returnSingleImageInfo(result, cb);
+        });
+      }
+    }
+    setTimeout(function(){ exports.waitForInstance(id, cb); }, 1000);
+  });
+};