A production-ready .NET modular monolith for building multi-tenant SaaS products.
Fork it. Add your domain modules. Deploy.
- Multi-tenancy from day one -- tenant isolation, per-tenant config, and data partitioning built into the architecture so you ship product features, not plumbing
- Fork-first architecture -- merge drivers preserve your branding, config, and customizations when pulling upstream improvements
- Production-ready infrastructure -- observability, background jobs, file storage, email, caching, and CI/CD preconfigured and tested
- Comprehensive test coverage -- 6,000+ tests at 97%+ coverage across unit, integration, architecture, and E2E layers
Wallow provides the cross-cutting infrastructure every SaaS product needs out of the box -- identity, billing, notifications, messaging, file storage, and multi-tenant data isolation. You write the business logic.
The intended workflow is to fork this repo and build your product on top. Shared infrastructure improvements can be pulled from upstream into forks without conflicts.
New here? Start with the Fork Guide or the Developer Guide.
cd docker && docker compose up -dStarts PostgreSQL, Valkey, GarageHQ (S3), Mailpit, and Grafana.
dotnet run --project src/Wallow.Api # API → http://localhost:5001
dotnet run --project src/Wallow.Auth # Auth UI → http://localhost:5002
dotnet run --project src/Wallow.Web # Web UI → http://localhost:5003./scripts/run-tests.sh # all tests
./scripts/run-tests.sh billing # single moduleSee Testing for coverage, E2E, and CI details.
A modular monolith where each module is an autonomous bounded context following Clean Architecture. Modules communicate through Wolverine in-memory events via Shared.Contracts -- never direct references. Each module owns its own PostgreSQL schema.
src/
├── Wallow.Api/ # Host, middleware, routing
├── Wallow.Auth/ # Blazor: login, register, password reset
├── Wallow.Web/ # Blazor: dashboard and public pages
├── Modules/
│ ├── Identity/ # Auth, users, organizations, RBAC
│ ├── Billing/ # Payments, invoices, subscriptions
│ ├── Storage/ # File storage (S3-compatible)
│ ├── Notifications/ # In-app and push notifications
│ ├── Messaging/ # User-to-user conversations
│ ├── Announcements/ # System-wide announcements
│ ├── Inquiries/ # Inquiry and question submission
│ ├── ApiKeys/ # API key management
│ └── Branding/ # Tenant branding configuration
└── Shared/
├── Contracts/ # Cross-module event definitions
└── Kernel/ # Base classes, shared abstractions
Each module follows four layers: Domain (no dependencies) → Application → Infrastructure → API.
Deep dive: Architecture Assessment · Module Creation
| Feature | Description |
|---|---|
| Clean Architecture | Strict dependency rules per module with domain isolation |
| Domain-Driven Design | Entities, value objects, domain events, bounded contexts |
| CQRS | Command/query separation with Wolverine as mediator |
| Multi-Tenancy | Schema-per-tenant data isolation, configurable resolution (header, subdomain, JWT) |
| Event-Driven | Wolverine in-memory events between modules |
| Identity & RBAC | OpenIddict + ASP.NET Core Identity |
| Real-Time | Push notifications via SignalR |
| Observability | Serilog structured logging, OpenTelemetry tracing, Grafana dashboards |
| Audit Trail | Automatic entity change auditing via Audit.NET |
| Background Jobs | IJobScheduler abstraction backed by Hangfire |
| Workflows | Elsa 3 engine for long-running business processes |
| Purpose | Technology |
|---|---|
| Framework | .NET 10 |
| Database | PostgreSQL 18 |
| ORM | EF Core + Dapper (available for raw SQL reads) |
| CQRS & Messaging | Wolverine (in-memory) |
| Caching | Valkey (Redis-compatible) |
| Identity | OpenIddict + ASP.NET Core Identity |
| Real-time | SignalR |
| Validation | FluentValidation |
| Logging & Tracing | Serilog, OpenTelemetry |
| Testing | xUnit, Testcontainers, AwesomeAssertions |
6,078 tests across 45 assemblies, all passing.
| Metric | Coverage |
|---|---|
| Lines | 97.7% (13,457 / 13,771) |
| Branches | 89.5% (2,235 / 2,497) |
| Methods | 96.9% (1,735 / 1,789) |
Details: Testing Guide · Coverage · E2E Tests · CI
Wallow is designed to be customized without changing source code. All configuration flows through standard .NET mechanisms:
| Area | Config Source | What it controls |
|---|---|---|
| Branding | branding.json |
App name, icon, tagline, theme colors |
| Database | appsettings.json |
PostgreSQL and Valkey connection strings |
appsettings.json |
SMTP host, port, TLS, sender defaults | |
| Storage | appsettings.json |
S3 endpoint, bucket, ClamAV virus scanning |
| Observability | appsettings.json |
OpenTelemetry OTLP endpoints, service name |
| CORS | appsettings.json |
Allowed origins for API requests |
| Environment | Environment variables | Override any setting with Section__Key syntax |
Configuration loads in order: appsettings.json → appsettings.{Environment}.json → environment variables → user secrets (dev only).
Full reference with examples for Docker, Kubernetes, and all module options: Configuration Guide
| Service | URL |
|---|---|
| API | http://localhost:5001 |
| API Docs (Scalar) | http://localhost:5001/scalar/v1 |
| Auth UI | http://localhost:5002 |
| Web UI | http://localhost:5003 |
| Docs | http://localhost:5004 |
| Mailpit | http://localhost:8025 |
| GarageHQ (S3) | http://localhost:3900 |
| Grafana | http://localhost:3001 |
Credentials and config: Configuration Guide
| Guide | Description |
|---|---|
| Developer Guide | Day-to-day development workflow |
| Fork Guide | Creating a new product from Wallow |
| Configuration | Environment variables, branding, settings |
| Architecture | Design decisions and patterns |
| Module Creation | Adding new modules |
| Deployment | CI/CD, Docker, and production setup |
| Versioning | Conventional Commits and release-please |
| Observability | Logging, tracing, and dashboards |