diff --git a/scripts/source_development_defaults.sh b/scripts/source_development_defaults.sh
--- a/scripts/source_development_defaults.sh
+++ b/scripts/source_development_defaults.sh
@@ -52,4 +52,4 @@
 fi
 
 # For development and local testing, point to localstack
-export AWS_ENDPOINT=http://localhost:4566
+export LOCALSTACK_ENDPOINT=http://localhost:4566
diff --git a/services/identity/src/config.rs b/services/identity/src/config.rs
--- a/services/identity/src/config.rs
+++ b/services/identity/src/config.rs
@@ -4,7 +4,8 @@
 use std::{env, fmt, fs, io, path::Path};
 
 use crate::constants::{
-  AUTH_TOKEN, SECRETS_DIRECTORY, SECRETS_FILE_EXTENSION, SECRETS_FILE_NAME,
+  AUTH_TOKEN, LOCALSTACK_ENDPOINT, SECRETS_DIRECTORY, SECRETS_FILE_EXTENSION,
+  SECRETS_FILE_NAME,
 };
 
 pub static CONFIG: Lazy<Config> =
@@ -19,6 +20,7 @@
   pub server_keypair: KeyPair<RistrettoPoint>,
   // this is temporary, while the only authorized caller is ashoat's keyserver
   pub keyserver_auth_token: String,
+  pub localstack_endpoint: Option<String>,
 }
 
 impl Config {
@@ -27,12 +29,15 @@
     path.push(SECRETS_DIRECTORY);
     path.push(SECRETS_FILE_NAME);
     path.set_extension(SECRETS_FILE_EXTENSION);
-    let keypair = get_keypair_from_file(path)?;
-    let auth_token =
+    let server_keypair = get_keypair_from_file(path)?;
+    let keyserver_auth_token =
       env::var(AUTH_TOKEN).unwrap_or_else(|_| String::from("test"));
+    let localstack_endpoint = env::var(LOCALSTACK_ENDPOINT).ok();
+
     Ok(Self {
-      server_keypair: keypair,
-      keyserver_auth_token: auth_token,
+      server_keypair,
+      keyserver_auth_token,
+      localstack_endpoint,
     })
   }
 }
@@ -40,7 +45,9 @@
 impl fmt::Debug for Config {
   fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     f.debug_struct("Config")
-      .field("server_keypair", &"redacted")
+      .field("server_keypair", &"** redacted **")
+      .field("keyserver_auth_token", &"** redacted **")
+      .field("localstack_endpoint", &self.localstack_endpoint)
       .finish()
   }
 }
diff --git a/services/identity/src/constants.rs b/services/identity/src/constants.rs
--- a/services/identity/src/constants.rs
+++ b/services/identity/src/constants.rs
@@ -104,3 +104,7 @@
 // Nonce
 
 pub const NONCE_LENGTH: usize = 17;
+
+// LocalStack
+
+pub const LOCALSTACK_ENDPOINT: &str = "LOCALSTACK_ENDPOINT";
diff --git a/services/identity/src/database.rs b/services/identity/src/database.rs
--- a/services/identity/src/database.rs
+++ b/services/identity/src/database.rs
@@ -13,6 +13,7 @@
 use opaque_ke::{errors::ProtocolError, ServerRegistration};
 use tracing::{debug, error, info, warn};
 
+use crate::config::CONFIG;
 use crate::constants::{
   ACCESS_TOKEN_SORT_KEY, ACCESS_TOKEN_TABLE,
   ACCESS_TOKEN_TABLE_AUTH_TYPE_ATTRIBUTE, ACCESS_TOKEN_TABLE_CREATED_ATTRIBUTE,
@@ -36,8 +37,22 @@
 
 impl DatabaseClient {
   pub fn new(aws_config: &SdkConfig) -> Self {
+    let client = match &CONFIG.localstack_endpoint {
+      Some(endpoint) => {
+        info!(
+          "Configuring DynamoDB client to use LocalStack endpoint: {}",
+          endpoint
+        );
+        let ddb_config_builder =
+          aws_sdk_dynamodb::config::Builder::from(aws_config)
+            .endpoint_url(endpoint);
+        Client::from_conf(ddb_config_builder.build())
+      }
+      None => Client::new(aws_config),
+    };
+
     DatabaseClient {
-      client: Arc::new(Client::new(aws_config)),
+      client: Arc::new(client),
     }
   }