SETUP GUIDES · SELF-HOSTED
Add self-hosted Claspine to an existing app
Your app keeps its code, database and users; Claspine adds sign-in, notifications, files, AI, payments and audit next to them.
You do not rewrite anything to use Claspine. A Spring Boot app adds Claspine as a library: one dependency, a few settings, and Claspine creates its own tables (all named spine_platform_…) in your database at the first start, next to yours. Any other backend (Node.js, Python, Go and others) keeps running as it is and calls the Claspine engine, a Docker image next to it (Run the Claspine engine). This guide covers Spring Boot step by step; the decisions in it (which database, whose login) are the same for the engine. Tested with Spring Boot 3.5 and Spring Security.
Words you’ll see
- License key
- The SPINE-FOUNDER-… or SPINE-AGENCY-… code that identifies your self-hosted license. Quote it when you contact support. It is not a password and cannot download anything by itself.
- Download token
- A secret starting with spk_ that lets your builds download Claspine’s packages. It works like a password for Maven and npm: keep it in an environment variable or a CI secret, never in your code.
- Session
- What keeps a person signed in. Claspine gives your app a session token after sign-in; every later request carries it. Claspine checks it, ends it at sign-out, and can end every session of a person at once.
- Sign-in provider
- The service that checks who a person is: Firebase Authentication (email and password, Google, Apple, phone), or Claspine’s own one-time codes sent by SMS, WhatsApp or email. Claspine trusts its answer and opens a session.
Before you start
- Java 17 or newer and Spring Boot 3.5. (Spring Boot 4 is not supported yet. Other languages: the engine.)
- A self-hosted license and download token, with Maven or Gradle pointed at Claspine Packages: Install self-hosted Claspine.
- Your database: PostgreSQL, MongoDB or Google Cloud Firestore. Claspine can share the one your app already uses.
Steps
- Add Claspine to the build. Import the BOM
com.grahmur:grahmur-platform-bomwith the release you use, then addcom.grahmur:grahmur-platform-backend(every capability; simplest) andcom.grahmur:grahmur-platform-guardrailswith test scope (snippet below). Smaller choices:grahmur-platform-starter-core(sign-in, permissions, audit),grahmur-platform-starter-saas(core plus notifications, files, payments, features), or single modules such asgrahmur-platform-module-notifications. - Choose where Claspine keeps its data. Set
platform.persistence.type:postgresuses yourspring.datasource,mongo(the default) usesspring.data.mongodb.uri,firestoreusesplatform.firestore.project-id. Claspine creates its tables or collections at start and never touches yours. If your app itself uses MongoDB while Claspine uses PostgreSQL or Firestore, addplatform.persistence.application-mongo: trueso your MongoDB keeps working. - Describe your app once with
platform.bootstrap:environment(DEVELOPMENT locally, PRODUCTION for real users; it cannot change later, so use one database per environment),hosts(the hostnames people reach your app on, such aslocalhostorshop.example.com),product.key(a short lower-case name that never changes) and the sign-in providers. The first start creates everything; later starts add what you add. - Decide whose login it is. Move sign-in to Claspine: follow Sign-in in a Spring Boot app, then remove your own login controller, session table and login filter, so there is one source of truth. Your controllers take an
AuthenticatedPrincipalparameter. Keep your own login: change nothing about sign-in, and pass your own user ID wherever Claspine asks who acted (Keep your own login); send email and messages to addresses withDirectMessages(Notifications in self-hosted Claspine). - If the app uses Spring Security and you moved sign-in to Claspine: Claspine checks the session before Spring Security runs, so let Spring Security allow requests through and turn off its CSRF check (Claspine refuses cookie requests that change data without the
X-Requested-Withheader). Use theSecurityFilterChainin the snippet, or removespring-boot-starter-securityif you used it only for login. - Add the guardrail test
src/test/java/…/ProductBoundaryTest.java(snippet). It fails the build when code, including code an AI agent wrote, reaches into Claspine’s internals instead of itsapipackages. - Start the app. The log shows
Claspine setup for product 'shop': product=… deployment=… hosts=[localhost]. Sign in once (withsandbox: truelocally:POST /api/v1/auth/sessionswith{"provider":"sandbox-identity","credential":"sandbox:shop:ravi","transport":"BEARER"}) and call one of your own endpoints with the returned session token. - Before production: switch the sign-in provider from
sandboxto Firebase or one-time codes, setenvironment: PRODUCTIONin the production database, and run./mvnw verifyso the guardrail test runs in CI.
<!-- pom.xml -->
<dependencyManagement><dependencies><dependency>
<groupId>com.grahmur</groupId><artifactId>grahmur-platform-bom</artifactId>
<version>0.31.0</version><type>pom</type><scope>import</scope>
</dependency></dependencies></dependencyManagement>
<dependency><groupId>com.grahmur</groupId><artifactId>grahmur-platform-backend</artifactId></dependency>
<dependency><groupId>com.grahmur</groupId><artifactId>grahmur-platform-guardrails</artifactId><scope>test</scope></dependency>
# application.yml
spring:
datasource: { url: jdbc:postgresql://localhost:5432/shop, username: shop, password: ${DB_PASSWORD} }
platform:
persistence:
type: postgres # or mongo (default) or firestore
bootstrap:
enabled: true
environment: DEVELOPMENT # PRODUCTION for real users; cannot change later
hosts: [localhost] # hostnames only
product: { key: shop, name: Shop }
identity-providers:
sandbox: true # local testing only
# firebase-project-id: my-firebase-project
// SecurityConfig.java: only if the app uses Spring Security and sign-in moved to Claspine
@Bean
SecurityFilterChain security(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(a -> a.anyRequest().permitAll()) // Claspine decides who is signed in
.csrf(c -> c.disable()) // Claspine checks X-Requested-With
.httpBasic(b -> b.disable()).formLogin(f -> f.disable())
.build();
}
// A controller: signed-out callers get 401 automatically
@GetMapping("/api/me")
Map<String, String> me(AuthenticatedPrincipal principal) {
return Map.of("accountId", principal.accountId().value());
}
// src/test/java/com/acme/shop/ProductBoundaryTest.java
@AnalyzeClasses(locations = SpineGuardrails.ProjectClasses.class, importOptions = ImportOption.DoNotIncludeTests.class)
class ProductBoundaryTest {
@ArchTest
static final ArchTests spine_guardrails = ArchTests.in(SpineGuardrails.class);
} What goes where in Claspine
| Console field | Value |
|---|---|
| platform.persistence.type | postgres, mongo (default) or firestore: where Claspine keeps its own tables. Your app’s data stays where it is. |
| platform.bootstrap.environment | DEVELOPMENT, TEST, STAGING or PRODUCTION. Sandbox sign-in works only in DEVELOPMENT and TEST. Fixed after the first start. |
| platform.bootstrap.hosts | Hostnames your users reach the app on (no IP addresses; localhost also covers 127.0.0.1 and the Android emulator in DEVELOPMENT/TEST). |
| platform.bootstrap.product.key | A short lower-case name for the app, for example shop. Defaults to spring.application.name. Never change it. |
| Imports in your code | Only com.grahmur.platform.modules.<capability>.api and com.grahmur.platform.foundation packages. ProductBoundaryTest enforces this. |
Keep it safe
- Never read or change the spine_platform_… tables yourself, not even with a quick SQL fix: Claspine refuses it at runtime, in tests and at startup. Use the capability APIs (java_api on the MCP server lists them).
- One login: when sign-in moves to Claspine, delete the old session table and login filter instead of keeping both.
- Sandbox sign-in proves nothing (anyone can claim any user). Claspine refuses it in STAGING and PRODUCTION; never run real users on a DEVELOPMENT deployment.
- Optional extra defence: give Claspine its own database user (platform.persistence.postgres.datasource.*, platform.persistence.mongo.uri), so your app’s credentials cannot reach Claspine’s tables at all.
Claspine stores the values encrypted, never shows them again in full, and never sends them to your app or SDK.
If something goes wrong
- POST /api/v1/auth/sessions answers 401 and the log says “Using generated security password”.
- Spring Security is blocking Claspine’s sign-in. Add the SecurityFilterChain from the snippet (permitAll, CSRF off), or remove spring-boot-starter-security if it only did login.
- Requests answer 404 HOST_NOT_AVAILABLE.
- The Host of the request is not in platform.bootstrap.hosts. Add it and restart (hosts are added, never removed).
- PlatformDataAccessDeniedException, or startup stops naming a spine_platform_ table.
- Your code names a Claspine table. Replace it with the capability API; for lookups use ProductConfiguration.findByKey, ApplicationBootstrap.current() and similar.
- The app tries to connect to MongoDB on localhost:27017 although Claspine uses PostgreSQL.
- Your own code uses MongoDB: set spring.data.mongodb.uri, or platform.persistence.application-mongo: false if it does not.
- Hibernate wants to change or validate spine_platform_ tables.
- Use spring.jpa.hibernate.ddl-auto: validate or none and manage your own tables with migrations; Claspine creates its tables itself.