A self-hosted Backend-as-a-Service platform for LAN environments and private VPCs — where data sovereignty, offline operation, and network-level control matter more than cloud scalability.
AppBase gives small organizations (clinics, schools, small businesses) the same developer experience as Firebase or Supabase — auth, file storage, and a database API — without sending a single byte outside their network.
You deploy one instance on any machine. Developers register apps against it, receive scoped API keys, and build on top of the platform. Everything runs on your hardware, on your network, with no external dependencies.
Existing BaaS solutions fall into one of two failure modes for this use case:
| Solution | Problem |
|---|---|
| Firebase / Supabase Cloud | Cloud-dependent, data leaves the network, not viable for compliance-sensitive environments |
| Supabase self-hosted | Complex infrastructure, requires DevOps expertise, not designed for LAN-first operation |
| Appwrite | Monolithic, no LAN-native networking, no built-in service discovery |
| PocketBase | Single binary with no multi-app isolation, no network-layer features |
| Dokku / Coolify | Solves deployment, not BaaS — no auth/storage/database API surface |
The gap: No existing solution combines BaaS services with LAN-native networking in a single platform deployable on commodity hardware in under 10 minutes.
Target users: Healthcare clinics, schools, local government offices, and small engineering teams that cannot use cloud BaaS for compliance or cost reasons, and cannot operate Kubernetes-level infrastructure.
- Authentication — User registration, login, session management, API key issuance and revocation
- File Storage — Bucket-based storage, upload/download, file versioning, scoped per app
- Database API — Collection management, full CRUD, records scoped to API key
- Admin Dashboard — App registration, user management, storage usage, audit log viewer
- REST API — Documented API surface designed for third-party app consumption
- Multi-app isolation — Each registered app gets isolated auth, storage, and database namespaces
- Container orchestration — Docker SDK integration, isolated container per app instance
- Port management — Dynamic port assignment, tracking, and reclamation
- Reverse proxy routing —
app-name.AppBase.localroutes to the correct container automatically - mDNS service discovery — Apps announce themselves on LAN; clients discover without manual configuration
- Health checks — Periodic liveness checks against each container
- Auto-restart — Failed containers are detected and restarted automatically
- Network isolation — Each app container on its own Docker network, no cross-app traffic by design
- Observability dashboard — Live network topology, node health, port map, traffic visibility
AppBase follows a single-tenant deployment model. Each organization runs its own isolated instance. This is a deliberate decision — not a limitation — consistent with the data sovereignty requirement of the target use case. This is the same model used by GitLab, Gitea, and Outline.
M1 is a single app-scoped BaaS instance. It contains:
apps/api/— the Fastify BaaS APIapps/dashboard/— the app-specific dashboard UIdata/appbase.sqlite— the single SQLite database for that one app instancedata/storage/— the single storage namespace for that one app instance
This is not a throwaway prototype. It is the first real product slice: one app, one API, one dashboard, one SDK, one database, one storage layer.
When multi-app provisioning is introduced, AppBase adds a master control plane at appbase.local:
- The master provisions app-specific BaaS instances from the base AppBase image
- Each app instance gets an isolated port, database, and storage namespace
- The master monitors health, tracks infra state, and manages routing metadata
- App-specific dashboards and APIs are exposed under dedicated subdomains
The routing model is:
appbase.local— master control planedashboard.<app>.appbase.local— app-specific BaaS dashboardapi.<app>.appbase.local— app-specific BaaS API<app>.appbase.local— reserved for hosted user applications later
LAN / Private VPC
│
└── AppBase Host
│
├── Master Process (port 80)
│ ├── Control Plane API
│ ├── Infra / Health Monitor
│ ├── App Router (reverse proxy)
│ ├── Docker SDK (container lifecycle)
│ ├── mDNS Announcer
│ └── Routing Metadata
│
├── App BaaS Instance: inventory-system (port 3101)
│ ├── Dashboard UI
│ ├── Auth API
│ ├── Storage API
│ └── Database API
│
└── App BaaS Instance: password-manager (port 3102)
├── Dashboard UI
├── Auth API
├── Storage API
└── Database API
For a deeper, implementation-level view of both the current M1 architecture and the target platform architecture, see docs/ARCHITECTURE.md. For the public request/response contract consumed by SDKs and client apps, see docs/API-SPEC.md.
The client libraries @appbase-pfe/types and @appbase-pfe/sdk are published to the public npm registry (see ADR-007 for scope and naming). Install into any app that talks to your deployed AppBase API:
npm install @appbase-pfe/sdkPoint AppBase.init at your API base URL and credentials; the wire format is defined in docs/API-SPEC.md. Optional React exports: import { … } from "@appbase-pfe/sdk/react" (install react ≥ 18).
Maintainers: release process and pack checks are in docs/PUBLISHING-SDK.md.
| Layer | Technology |
|---|---|
| Language | TypeScript |
| API Framework | Fastify |
| Auth Library | better-auth (with API key plugin) |
| ORM | Drizzle ORM |
| DB Driver | better-sqlite3 |
| API Documentation | OpenAPI via @fastify/swagger + Swagger UI |
| Logging | Pino (via Fastify logger) |
| Frontend | Next.js 16 |
| Database | SQLite (data/appbase.sqlite in M1, per-app DB in M2+) |
| Caching | Deferred to M2 (SQLite is fast enough for M1) |
| Real-time | Server-Sent Events (SSE) |
| Job Queue | BullMQ (in-process) |
| Container Management | Docker SDK (dockerode) |
| Service Discovery | mDNS |
| Reverse Proxy | Caddy (programmatic config) |
| Monorepo | Turborepo |
| Testing | Vitest (unit), Vitest (integration), Playwright (e2e) |
| CI | GitHub Actions |
AppBase/
├── apps/
│ ├── api/ # Core BaaS API
│ ├── dashboard/ # Admin UI (Next.js)
│ ├── todo-app/ # Next.js todo demo (`workspace:*` SDK)
│ └── todo-vanilla-npm/ # Vite + TS demo (SDK from **npm**)
├── packages/
│ ├── sdk/ # JS/TS client SDK (@appbase-pfe/sdk)
│ ├── db/ # Schema + migrations
│ ├── types/ # Shared TypeScript interfaces
│ └── config/ # Shared tsconfig, eslint, prettier
├── .github/
│ └── workflows/
│ ├── ci.yml # lint, typecheck, unit tests (every push)
│ ├── integration.yml # integration tests (PR to main)
│ └── e2e.yml # Playwright (PR to main)
├── docs/
│ └── adr/ # Architecture Decision Records
├── turbo.json
├── package.json
├── README.md
├── CONTRIBUTING.md
└── LICENSE
Ship something real first. The full architecture above is the locked vision — the MVP is the shortest path to a demoable, end-to-end developer experience that proves the concept.
One app-specific BaaS instance. One dashboard. One API. One SQLite database. One storage namespace. Consumed by a real demo app through an SDK.
| Feature | MVP | Deferred |
|---|---|---|
| Auth (register, login, refresh) | ✅ | — |
| Storage (upload, download, scoped to user) | ✅ | — |
| Database API (collections, CRUD, real-time SSE) | ✅ | — |
| API key issuance + validation | ✅ | — |
| SDK (JS/TS, wraps all 3 services) | ✅ | — |
| App-specific dashboard (basic — API keys, users, usage) | ✅ | — |
| Deployment service / app provisioning | ❌ | M2 |
| Multi-app isolation | ❌ | M2 |
| Container orchestration (dockerode) | ❌ | M2 |
| Caddy + subdomain routing | ❌ | M3 |
| mDNS service discovery | ❌ | M3 |
| Health monitor + auto-restart | ❌ | M3 |
| Frontend hosting | ❌ | M3/M4 |
appbase.local master control plane |
❌ | M2 |
In M1, the BaaS unit is one API plus one app-specific dashboard. The demo application is external and consumes the API through the SDK. There is no deployment service, no master control plane, and no multi-app routing yet.
The public AppBase contract is the BaaS API consumed by SDKs and external clients. Dashboard authentication is separate from that public contract and may use a simpler browser-oriented auth flow.
Single host
│
├── Dashboard UI → localhost:3001
├── BaaS API → localhost:3000
├── SQLite → data/appbase.sqlite
└── File storage → data/storage/
The API side of that BaaS unit is a single Fastify process with three service plugins behind API key validation middleware (/auth/register and /auth/login are public):
localhost:3000
│
├── /auth
│ ├── POST /register
│ ├── POST /login
│ ├── POST /refresh
│ ├── POST /logout
│ └── reset handled in dashboard (M1)
│
├── /storage
│ ├── POST /buckets/:bucket/upload
│ ├── GET /buckets/:bucket/:fileId
│ ├── DELETE /buckets/:bucket/:fileId
│ └── GET /buckets/:bucket
│
├── /db
│ ├── POST /collections/:collection
│ ├── GET /collections/:collection
│ ├── GET /collections/:collection/:id
│ ├── PUT /collections/:collection/:id
│ ├── DELETE /collections/:collection/:id
│ └── GET /collections/:collection/subscribe (SSE)
│
└── /admin
├── GET /users
├── GET /storage/usage
└── GET /audit-log
The full public request/response contract, headers, and error format are defined in docs/API-SPEC.md.
In M1, password reset is handled through the app-specific dashboard rather than a public API endpoint.
The SDK is what makes this feel like Amplify and not just a REST API. It needs to do three things internally: store and refresh tokens automatically, inject the ID token into every storage/db request header, and manage the SSE subscription lifecycle.
import { AppBase } from '@appbase-pfe/sdk'
const client = AppBase.init({
endpoint: 'http://localhost:3000',
apiKey: 'hs_live_xxxx'
})
// Auth
await client.auth.signUp({ email, password })
const session = await client.auth.signIn({ email, password })
// Storage — ID token injected automatically
await client.storage.upload('avatars', file)
const url = await client.storage.getUrl('avatars', fileId)
// DB — scoped to user automatically
await client.db.collection('passwords').create({ site, username, encrypted })
const items = await client.db.collection('passwords').list()
// Real-time
client.db.collection('passwords').subscribe((change) => {
console.log('record changed', change)
})M1 uses a single SQLite file: data/appbase.sqlite. This is correct for the MVP because there is only one app-specific BaaS instance and no deployment service yet.
In M2, the storage model splits cleanly:
data/master.sqlitefor the control planedata/{appId}/app.sqlitefor each provisioned appdata/{appId}/storage/for each app's files
That keeps M1 simple without forcing the master/provisioning layer to exist before it is actually needed.
Built vertically — one thin slice end to end first, then fill out:
Week 1 — Auth + API key middleware complete. SDK auth module working against it.
Week 2 — DB API complete (CRUD, no SSE yet). SDK db module working. Demo app (password manager) stores and retrieves passwords. First demoable checkpoint.
Week 3 — Storage complete. SDK storage module. Demo app stores file attachments.
Week 4 — SSE real-time on DB. SDK subscribe(). Demo app updates live without refresh. Basic app-specific dashboard. Single docker run command starts the BaaS unit.
Deliverable: one app-specific BaaS unit, a working SDK, and a password manager demo that runs fully offline.
- Master control plane at
appbase.local - App provisioning / deletion service
- Docker SDK integration (
dockerode) - App creation spins up an isolated BaaS instance
- Per-app SQLite and storage namespaces
- Port assignment and management
- Master tracks app state and lifecycle
- Reverse proxy routing (
dashboard.<app>.appbase.local,api.<app>.appbase.local) - mDNS service announcement and discovery
- Health checks with auto-restart
- Network isolation between app containers
- Reserve
<app>.appbase.localfor hosted user applications
- Network topology dashboard
- Live health status and port map
- API documentation
- Full end-to-end demo scenario (offline, multi-app, auto-restart)
- Start the AppBase BaaS unit on a local machine
- Open the app-specific dashboard on
localhost:3001 - Generate an API key and inspect app users, storage usage, and records
- Point the password manager demo app at
http://localhost:3000 - The password manager authenticates users, stores credentials, uploads files, and receives live updates — all via the SDK
- Pull the network cable to the internet. Everything still works.
- Open
appbase.local - Create a new app (
password-manager) from the master control plane - AppBase provisions a BaaS instance, assigns a port, and registers routing metadata
- Open
dashboard.password-manager.appbase.localto manage that app's BaaS - Point the demo app at
api.password-manager.appbase.local - Later, host the actual user-facing application at
password-manager.appbase.local
- LAN-native service discovery (mDNS)
- Container network isolation and management
- Reverse proxy configuration and dynamic routing
- Failure detection and automatic recovery
- Network observability and topology visualization
- REST API design for third-party consumption
- Multi-app isolation patterns
- Background job processing architecture
- Session-based auth with API key management
- File versioning and bucket-based storage
Architecture Decision Records are maintained in /docs/adr/. Current decisions documented:
ADR-001— API framework selectionADR-002— ORM and migration strategyADR-003— Auth implementation strategy
See CONTRIBUTING.md.
See LICENSE.
AppBase is a Final Year Project (PFE) for a Network and Telecommunications Engineering degree. It sits at the intersection of software engineering and network engineering, designed to be both academically rigorous and practically useful.