diff --git a/controller/Cargo.toml b/controller/Cargo.toml
index 2c3af62..7157501 100644
--- a/controller/Cargo.toml
+++ b/controller/Cargo.toml
@@ -16,7 +16,7 @@ serde.workspace = true
 slog-async.workspace = true
 slog.workspace = true
 sqlx = { version = "0.8.3", default-features = false, features = [
-    "macros", "postgres", "runtime-tokio", "tls-rustls", "time", "uuid"
+    "macros", "migrate", "postgres", "runtime-tokio", "tls-rustls", "time", "uuid"
   ] }
 tokio.workspace = true
 trace-request = { path = "../trace-request" }
diff --git a/controller/build.rs b/controller/build.rs
new file mode 100644
index 0000000..d506869
--- /dev/null
+++ b/controller/build.rs
@@ -0,0 +1,5 @@
+// generated by `sqlx migrate build-script`
+fn main() {
+    // trigger recompilation when a new migration is added
+    println!("cargo:rerun-if-changed=migrations");
+}
diff --git a/controller/src/bin/patagia-controller.rs b/controller/src/bin/patagia-controller.rs
index a3a492c..a73f4a7 100644
--- a/controller/src/bin/patagia-controller.rs
+++ b/controller/src/bin/patagia-controller.rs
@@ -74,6 +74,8 @@ async fn main() -> Result<()> {
 
     let pg = PgPool::connect(&database_url).await?;
 
+    sqlx::migrate!().run(&pg).await?;
+
     let ctx = ControllerContext::new(pg);
     let api = api::api()?;
     ServerBuilder::new(api, Arc::new(ctx), logger)
diff --git a/controller/src/lib.rs b/controller/src/lib.rs
index 2d12df1..2774561 100644
--- a/controller/src/lib.rs
+++ b/controller/src/lib.rs
@@ -3,3 +3,4 @@ pub mod context;
 
 mod user;
 mod version;
+