Production
Deploy Sagaweaw to production in 5 steps. No extra containers. No dashboard repo to clone. One environment variable.
1. Add the dependency
2. Write your sagas
3. Set one environment variable
4. Deploy normally
5. Open the dashboard in your browser
Step 1 — Add the dependency
// Gradle (Kotlin DSL)
implementation("dev.sagaweaw:sagaweaw-spring-boot-starter:1.0.14")
<!-- Maven -->
<dependency>
<groupId>dev.sagaweaw</groupId>
<artifactId>sagaweaw-spring-boot-starter</artifactId>
<version>1.0.14</version>
</dependency>
Kotlin DSL? Add sagaweaw-kotlin too — see the Kotlin guide →.
Step 2 — Write your sagas
Implement SagaDefinition, annotate with @Saga. See the Quickstart → for a full example.
Step 3 — Set one environment variable
Generate a strong token once and store it in your secret manager:
openssl rand -hex 32
# → e3b0c44298fc1c149afb...
Set it as an environment variable on your server (or in your .env / secret manager):
SAGAWEAW_TOKEN=<your-generated-token>
In application.properties, reference it:
sagaweaw.observability.token=${SAGAWEAW_TOKEN}
sagaweaw.kafka.enabled=false
Without SAGAWEAW_TOKEN, every observability endpoint returns 403. This is intentional — the dashboard is never exposed accidentally.
Step 4 — Deploy normally
Deploy your Spring Boot application as you always do. Sagaweaw requires no additional services.
What happens on first boot:
- Sagaweaw's embedded Flyway runs automatically
- Creates 5 tables in your existing database:
sagas,saga_steps,saga_events,outbox_messages,dead_letters - Uses a separate history table (
sagaweaw_schema_history) — never conflicts with your own Flyway migrations - Your sagas are registered and ready
You will see logs like:
Flyway Community Edition ... by Redgate
Database: jdbc:postgresql://... (PostgreSQL ...)
Successfully applied 2 migration(s) to schema "public" on schema history table "sagaweaw_schema_history"
Step 5 — Open the dashboard
Navigate to your API URL followed by /sagaweaw:
https://your-api.com/sagaweaw
You will see the login screen. Enter your SAGAWEAW_TOKEN and press Enter.
The dashboard shows:
- Live feed — every saga execution in real time
- Step timeline — per-saga step-by-step view with timing, retries, and context
- Dead letters — failed sagas with full error trace and one-click reprocess
- Metrics — volume, success rate, P50/P95 latency, stuck sagas
Fields annotated with @SagaMask on your context class are stored and displayed as [REDACTED]. Use it for tokens, PII, or any sensitive data your saga carries:
public record Context(
@SagaMask String customerEmail,
@SagaMask String paymentToken,
UUID orderId
) implements SagaContext { ... }
Security hardening
The defaults are safe for development. In production, harden the three protection layers below.
Rate limiting on login
Prevent brute-force attacks on the dashboard token:
sagaweaw.observability.auth.max-attempts=10
sagaweaw.observability.auth.lockout-minutes=15
After max-attempts consecutive failures from the same IP, the endpoint returns 429 Too Many Requests with a Retry-After header. Set max-attempts=0 to disable rate limiting.
Zero-downtime token rotation
Rotate the dashboard token without a maintenance window:
# Generate the new token
openssl rand -hex 32
# New token — becomes the active credential immediately
sagaweaw.observability.token=${SAGAWEAW_TOKEN}
# Old token — accepted in parallel during the grace period
sagaweaw.observability.previous-token=${SAGAWEAW_PREVIOUS_TOKEN}
Deploy with both set. Once all clients have reconnected with the new token, remove previous-token. Any request authenticated with the old token is logged as a WARN, so you know when rotation is complete.
Context size limit
Prevent oversized payloads from reaching the database:
sagaweaw.engine.max-context-bytes=65536
SpringSagaEngine measures the serialized context before any write. If the limit is exceeded, SagaContextTooLargeException is thrown with the saga name, the limit, and the actual size. The message hints at @SagaMask for large fields that don't need to be stored.
Set to 0 to disable the check (default).
Wiring production traffic
@AutoStart and the trigger endpoint are for development. In production, call SagaManager.start() where the business event happens:
@Service
public class PaymentService {
private final SagaManager sagaManager;
public void processPayment(PaymentRequest req) {
sagaManager.start(PixPaymentSaga.class, new PixPaymentSaga.Context(
UUID.randomUUID().toString(),
req.getDictKey(),
req.getAmount(),
req.getPayerId()
));
}
}
Remove @AutoStart and sagaweaw.auto-start.enabled from your production profile.
Free tier limits in multi-instance deployments
The open-source version works correctly for single-instance deployments. With 2+ pods sharing the same database, the internal schedulers (retry, outbox relay, retention) have no distributed lock — multiple pods may process the same item simultaneously.
For single-pod production deployments (most use cases) this is not an issue.
For multi-pod deployments at scale, Sagaweaw Cloud adds managed distributed locking, a hosted dashboard accessible from anywhere without VPN, and multi-environment support.
Configuration reference
| Property | Default | Description |
|---|---|---|
sagaweaw.observability.token | — | Required. Token for dashboard and API access |
sagaweaw.observability.previous-token | — | Previous token accepted during zero-downtime rotation |
sagaweaw.observability.auth.max-attempts | 10 | Failed login attempts before IP lockout (0 = disabled) |
sagaweaw.observability.auth.lockout-minutes | 15 | Lockout duration after max-attempts is reached |
sagaweaw.engine.max-context-bytes | 0 | Max serialized context size in bytes (0 = disabled) |
sagaweaw.kafka.enabled | true | Set false if not using Kafka |
sagaweaw.dashboard.mode | embedded | embedded serves at /sagaweaw; standalone expects Vite dev server |
sagaweaw.dashboard.path | /sagaweaw | URL path for embedded mode |
sagaweaw.flyway.enabled | true | Set false to manage schema manually |
sagaweaw.health.stuck-threshold-minutes | 15 | Minutes before a stuck saga trips the health indicator |
sagaweaw.alerts.webhook-url | — | Slack / PagerDuty / Teams webhook for dead letter alerts |
sagaweaw.data.retention-days | 0 | Archive completed sagas after N days (0 = never) |
sagaweaw.data.failed-retention-days | 90 | Keep failed sagas for N days (LGPD/PCI-DSS audit trail) |
Next steps
- Local development → — Testing with @AutoStart and the Vite dev server
- Observability → — Full API reference for the dashboard endpoints
- Integrations → — Prometheus, Grafana, OpenTelemetry