diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index 60ab7c9f2e2186527a1b463d1ed49eaebb30d4a9..ef5156a431ff4aec37044e3c8f943227c1bc83b8 100644
--- a/src/metabase/core.clj
+++ b/src/metabase/core.clj
@@ -20,7 +20,8 @@
                       [routes :as routes]
                       [sample-data :as sample-data]
                       [setup :as setup]
-                      [task :as task])
+                      [task :as task]
+                      [util :as u])
             (metabase.models [setting :refer [defsetting]]
                              [database :refer [Database]]
                              [user :refer [User]])))
@@ -67,6 +68,10 @@
       wrap-session                                    ; reads in current HTTP session and sets :session/key
       wrap-gzip))                                     ; GZIP response if client can handle it
 
+
+;;; ## ---------------------------------------- LIFECYCLE ----------------------------------------
+
+
 (defn- -init-create-setup-token
   "Create and set a new setup token, and open the setup URL on the user's system."
   []
@@ -127,7 +132,8 @@
   true)
 
 
-;; ## Jetty (Web) Server
+;;; ## ---------------------------------------- Jetty (Web) Server ----------------------------------------
+
 
 (def ^:private jetty-instance
   (atom nil))
@@ -157,9 +163,10 @@
     (reset! jetty-instance nil)))
 
 
-(defn -main
-  "Launch Metabase in standalone mode."
-  [& args]
+;;; ## ---------------------------------------- App Main ----------------------------------------
+
+
+(defn- start-normally []
   (log/info "Starting Metabase in STANDALONE mode")
   (try
     ;; run our initialization process
@@ -169,3 +176,21 @@
     (catch Exception e
       (.printStackTrace e)
       (log/error "Metabase Initialization FAILED: " (.getMessage e)))))
+
+(defn- run-cmd [cmd & args]
+  (let [cmd->fn {:migrate (fn [direction]
+                            (db/migrate (keyword direction)))}]
+    (if-let [f (cmd->fn cmd)]
+      (do (apply f args)
+          (println "Success.")
+          (System/exit 0))
+      (do (println "Unrecognized command:" (name cmd))
+          (println "Valid commands are:\n" (u/pprint-to-str (map name (keys cmd->fn))))
+          (System/exit 1)))))
+
+(defn -main
+  "Launch Metabase in standalone mode."
+  [& [cmd & args]]
+  (if cmd
+    (apply run-cmd (keyword cmd) args) ; run a command like `java -jar metabase.jar migrate release-locks` or `lein run migrate release-locks`
+    (start-normally)))                 ; with no command line args just start Metabase normally
diff --git a/src/metabase/db.clj b/src/metabase/db.clj
index b10bca61cb3c577d442862e25ab29c1c1f7c89f2..492594f028863afeb2ee914fbf13e8f7093853f3 100644
--- a/src/metabase/db.clj
+++ b/src/metabase/db.clj
@@ -64,21 +64,31 @@
   "migrations/liquibase.json")
 
 (defn migrate
-  "Migrate the database `:up`, `:down`, or `:print`."
-  [jdbc-connection-details direction]
-  (try
-    (jdbc/with-db-transaction [conn jdbc-connection-details]
-      (let [^Database  database  (-> (DatabaseFactory/getInstance)
-                                     (.findCorrectDatabaseImplementation (JdbcConnection. (jdbc/get-connection conn))))
-            ^Liquibase liquibase (Liquibase. changelog-file (ClassLoaderResourceAccessor.) database)]
-        (case direction
-          :up    (.update liquibase "")
-          :down  (.rollback liquibase 10000 "")
-          :print (let [writer (StringWriter.)]
-                   (.update liquibase "" writer)
-                   (.toString writer)))))
-    (catch Throwable e
-      (throw (DatabaseException. e)))))
+  "Migrate the database:
+
+   *  `:up`            - Migrate up
+   *  `:down`          - Rollback *all* migrations
+   *  `:print`         - Just print the SQL for running the migrations, don't actually run them.
+   *  `:release-locks` - Manually release migration locks left by an earlier failed migration.
+                         (This shouldn't be necessary now that we run migrations inside a transaction,
+                         but is available just in case)."
+  ([direction]
+   (migrate @jdbc-connection-details direction))
+  ([jdbc-connection-details direction]
+   (try
+     (jdbc/with-db-transaction [conn jdbc-connection-details]
+       (let [^Database  database  (-> (DatabaseFactory/getInstance)
+                                      (.findCorrectDatabaseImplementation (JdbcConnection. (jdbc/get-connection conn))))
+             ^Liquibase liquibase (Liquibase. changelog-file (ClassLoaderResourceAccessor.) database)]
+         (case direction
+           :up            (.update liquibase "")
+           :down          (.rollback liquibase 10000 "")
+           :print         (let [writer (StringWriter.)]
+                            (.update liquibase "" writer)
+                            (.toString writer))
+           :release-locks (.forceReleaseLocks liquibase))))
+     (catch Throwable e
+       (throw (DatabaseException. e))))))
 
 
 ;; ## SETUP-DB
@@ -122,10 +132,10 @@
 
   ;; Run through our DB migration process and make sure DB is fully prepared
   (if auto-migrate
-    (migrate @jdbc-connection-details :up)
+    (migrate :up)
     ;; if we are not doing auto migrations then print out migration sql for user to run manually
     ;; then throw an exception to short circuit the setup process and make it clear we can't proceed
-    (let [sql (migrate @jdbc-connection-details :print)]
+    (let [sql (migrate :print)]
       (log/info (str "Database Upgrade Required\n\n"
                      "NOTICE: Your database requires updates to work with this version of Metabase.  "
                      "Please execute the following sql commands on your database before proceeding.\n\n"