Overview

AppCluster is a lightweight, .NET-native orchestration platform for self-hosted multi-server environments. It deploys .NET applications as isolated OS processes — no containers, no image registries, no Kubernetes.

Applications are packaged as dotnet publish output in a .zip file, uploaded to the cluster, and executed via dotnet {entry.dll} under a dedicated OS user with full resource isolation.

Key design principles:

  • Single binary — every node runs the same AppCluster.Node process
  • No external database — state replicated leader-to-follower, persisted per node in SQLite
  • Reconciliation-based — desired state vs. actual state, continuously converged
  • Plugin architecture — all functionality in hot-reloadable plugins

Architecture

Every server runs the same AppCluster.Node binary, which combines the REST API, leader/follower state replicator, plugin host, and local reconciler in a single ASP.NET Core application.

A custom leader-follower replicator elects a single leader for cluster-wide writes. State changes stream to every follower over SSE, and each node persists a single-row SQLite snapshot of the full cluster state. No separate control plane, no external database — HA requires 3 or 5 nodes.

Note: earlier releases used Raft (DotNext.Net.Cluster). As of April 2026 this was replaced with the simpler custom replicator described above.

Leader / Follower Replication Node 1 (Leader) <-------> Node 2 <-------> Node 3 | | | LocalReconciler LocalReconciler LocalReconciler | | | [app1, app2] [app1, app3] [app2, app3]

How Deployment Works

  1. User calls REST API on any node — writes are forwarded to the current leader
  2. Leader applies the command, persists a new snapshot, and emits a state-change event
  3. Every follower receives the change over SSE and updates its own snapshot
  4. Each node's LocalReconciler compares desired vs. actual state
  5. Reconciler starts, stops, or restarts apps as needed using plugins

Node Components

Each node runs these components in a single process:

  • REST API — Minimal API endpoints for all cluster operations
  • Cluster Replicator — Leader election, state replication, SQLite snapshot store
  • Plugin Host — Loads and manages hot-reloadable plugins via AssemblyLoadContext
  • Module Host — Runs optional standalone modules (reconcilers, LB, DNS, alerting, etc.) and multiplexes their UIs
  • LocalReconciler — Continuous reconciliation loop
  • YARP Reverse Proxy — Routes external traffic to running apps
  • Health Checker — HTTP/TCP probes with crash restart

Quick Start

Prerequisites: .NET 10 SDK

# Build the entire solution dotnet build # Run a single node dotnet run --project src/AppCluster.Node # The dashboard is available at http://localhost:5100

The node starts with four ports:

PortPurpose
5100REST API + Web GUI
5101Leader / follower state replication
5102Internal cluster communication
5103HTTPS (with SNI certificate selection)

Cluster Setup

For high availability, run 3 or 5 nodes. Start the first node normally — it becomes the leader:

# Start the first node dotnet run --project src/AppCluster.Node

Get the join key from the first node:

curl http://node1:5100/api/cluster/join-key

Join additional nodes to the cluster:

curl -X POST http://node2:5100/api/cluster/join \ -H "Content-Type: application/json" \ -d '{"leaderAddress": "http://node1:5100", "joinKey": "<key>"}'

Or run with CLI arguments:

dotnet run --project src/AppCluster.Node -- --data ./node2 --join <key>

Deploying Applications

Upload an Artifact

Package your application as a dotnet publish output compressed into a .zip file, then upload it to the cluster:

curl -X POST http://localhost:5100/api/artifacts/upload \ -F "file=@myapp.zip"

The cluster verifies the artifact's SHA256 hash and distributes it to all nodes.

Deploy

curl -X POST http://localhost:5100/api/deploy \ -H "Content-Type: application/json" \ -d '{"appName": "myapp", "artifactId": "<id>", "instances": 2}'

Deployment Strategies

  • Replace — Stop old instances, start new ones
  • Rolling — Gradually replace instances one at a time
  • Blue-Green — Run both versions, switch traffic after health checks pass

All strategies support automatic rollback if health checks fail.

Declarative Configuration

Define your desired state in YAML or JSON and apply it with the CLI:

dotnet run --project src/AppCluster.Cli -- apply cluster.yaml

Example cluster.yaml:

applications: - name: hello-app artifact: hello-world-1.0.0-20260405182710 entryAssembly: HelloApp.dll replicas: 2 resources: cpu: "0.5" memory: "256Mi" maxProcesses: 64 env: ASPNETCORE_URLS: "http://0.0.0.0:8080" secrets: - name: DATABASE_URL appId: hello-app

CLI Reference

CommandDescription
appcluster validate <file>Validate config file syntax
appcluster diff <file>Show what would change
appcluster apply <file>Apply desired state to the cluster
appcluster export -o <file>Export current cluster state

REST API Reference

All endpoints are under /api. Authentication is via the X-Api-Key header. Write requests on any node are automatically forwarded to the current leader.

Nodes & Status

MethodEndpointDescription
GET/api/nodesList cluster nodes
GET/api/appsList deployed applications
GET/api/deploymentsList deployments

Deployment

MethodEndpointDescription
POST/api/deployDeploy an application
POST/api/apps/{id}/stopStop an application
DELETE/api/apps/{id}Remove an application
POST/api/apps/{id}/rollbackRollback to previous version
GET/api/apps/{id}/historyGet deployment history

Artifacts

MethodEndpointDescription
POST/api/artifacts/uploadUpload a .zip artifact
GET/api/artifactsList artifacts
GET/api/artifacts/{id}Get artifact details

Secrets

MethodEndpointDescription
POST/api/secretsCreate a secret
GET/api/secretsList secrets (names only)
DELETE/api/secrets/{id}Delete a secret

Routes

MethodEndpointDescription
POST/api/routesCreate a route
GET/api/routesList routes
DELETE/api/routes/{id}Delete a route

Cluster Management

MethodEndpointDescription
POST/api/cluster/joinJoin a node to the cluster
GET/api/cluster/statusGet cluster status
GET/api/cluster/join-keyGet the join key
POST/api/cluster/rotate-keyRotate the join key

Authentication

MethodEndpointDescription
POST/api/auth/keysCreate an API key
GET/api/auth/keysList API keys
DELETE/api/auth/keys/{id}Revoke an API key

Audit & Webhooks

MethodEndpointDescription
GET/api/auditQuery audit log. Filters: userId (actor username), appId, action, ns, since, category, limit
GET/api/audit/streamSSE stream of new audit entries (replays a small backlog first)
POST/api/webhooksCreate a webhook
GET/api/webhooksList webhooks
DELETE/api/webhooks/{id}Delete a webhook

Users, Sessions & Layered RBAC v1.3.0+

The cluster has two layers of access control. Layer 1 is the existing namespace role system (viewer / operator / admin / super-admin per namespace). Layer 2 adds per-resource grants so you can give a user a specific capability (e.g. instance:debug on qa-*) without making them admin cluster-wide. The two layers compose with OR semantics — layer 2 only grants, never restricts.

MethodEndpointDescription
GET/api/auth/permissions?resource=&action=&namespace=&name= Decision API. Returns { allowed, reason, effectiveRole, ... } combining layer 1 + layer 2
GET/api/users/{userId}/permissions List grants for a user. Admin-or-self
POST/api/users/{userId}/permissions Create a grant. Body: { resourceType, action, namespace?, resourceName?, description? }
DELETE/api/users/{userId}/permissions/{permissionId} Revoke a grant
GET/api/permissions/resource-types Aggregator across every running module's /module-ui — powers the GUI's grant editor dropdowns
POST/api/users/{userId}/lock Block login pending review. Body: { reason? }
POST/api/users/{userId}/unlock Restore login access
POST/api/users/{userId}/force-logout Bump TokenInvalidationCutoffAt — rejects all tokens issued before now. Admin-or-self
POST/api/users/{userId}/2fa/admin-disable Admin recovery path when a user has lost their authenticator app
GET/api/users/{userId}/sessions List active sessions with IP / user-agent / last-seen / isCurrent flag. Admin-or-self
DELETE/api/users/{userId}/sessions/{sessionId} Revoke one session — marks it Revoked and deletes its token
POST/api/auth/logout Mark the current session revoked + delete its token (frontend logout calls this best-effort before clearing localStorage)

Databases v1.2.1+

DatabaseManager + Postgres / MySQL / MSSQL providers broker per-app and per-developer credentials against EXISTING external database servers — the cluster does not host the engines. Apps declare their database needs in DesiredDeployment.DatabaseRefs; the reconciler auto-provisions a SQL user, grant, and connection-string env var before pod start.

MethodEndpointDescription
POST/api/databases/serversRegister a database server (Postgres / MySQL / MSSQL)
GET/api/databases/serversList registered servers
DELETE/api/databases/servers/{id}Deregister + cascading cleanup of secrets and grants
POST/api/databases/servers/{id}/testProbe connectivity via the manager module
GET/api/databases/servers/{id}/databasesList managed databases on the server
POST/api/databases/servers/{id}/grants/developersCreate a per-developer grant; returns plaintext password + connection string once
GET/api/databases/grants/apps · /grants/developersList app or developer grants
GET / POST/api/databases/servers/{id}/driftDrift report (orphan grants / orphan users / expired-but-live) · /scan forces a fresh scan
POST/api/databases/servers/{id}/queryAd-hoc query for the in-browser workbench. Body: { database, sql, maxRows? }

Live Debugger v1.0.0+

The Debugger module attaches netcoredbg / vsdbg / msvsmon to a running pod and returns ready-to-paste IDE configs. Sessions are time-boxed (default 30 min, hard cap 240) and admin-gated — layer-2 RBAC permits scoping (instance, debug) grants per namespace.

MethodEndpointDescription
POST/api/instances/{id}/debug-attachSpawn a debugger bound to the pod's PID; returns VS Code launch.json + VS connection string + Rider profile
GET/api/debug-sessions[?appId=]List active sessions across the cluster (fan-out)
DELETE/api/debug-sessions/{id}End a session; the IDE is disconnected

Platform Isolation

AppCluster provides OS-level isolation for each deployed application:

FeatureLinuxWindows
User isolationDedicated OS user (0700)Local user + NTFS ACLs
FilesystemHome directory per appPer-app directory
NetworkNetwork namespaces + veth pairsWindows Firewall rules
Process visibility/proc with hidepid=2Job Objects
Resource limitscgroups v2Job Objects
Security profilesAppArmorN/A

Port Mapping

  • Linux: Network namespaces allow apps to use hardcoded ports internally. External access is mapped through veth pairs.
  • Windows: Applications must read their port from the ASPNETCORE_URLS environment variable.

Secrets Management

Secrets are encrypted with AES-256-GCM and stored in the replicated cluster state. They are injected into application processes as environment variables at startup — never written to disk in plain text. Secrets are namespace-scoped and can be referenced by modules using the secret:namespace/key syntax.

# Create a secret curl -X POST http://localhost:5100/api/secrets \ -H "Content-Type: application/json" \ -d '{"name": "DATABASE_URL", "value": "postgres://...", "appId": "myapp"}'

Routing & Reverse Proxy

AppCluster includes a built-in YARP reverse proxy that routes external traffic to your applications. Routes are configured dynamically via the API — no restart required.

# Create a route curl -X POST http://localhost:5100/api/routes \ -H "Content-Type: application/json" \ -d '{"host": "myapp.example.com", "appName": "myapp", "port": 8080}'

Health Checks

Configure HTTP or TCP health probes for each application. Failed health checks trigger automatic restart with exponential backoff.

  • HTTP probes — Send GET requests to a configurable path, expect 2xx response
  • TCP probes — Attempt TCP connection to the application port
  • Crash restart — Processes that exit unexpectedly are restarted automatically
  • Exponential backoff — Prevents restart loops for persistently failing apps

Authentication & RBAC

AppCluster supports multiple authentication methods for both interactive users and programmatic clients:

  • Username + password with BCrypt hashing
  • TOTP 2FA (Google Authenticator, Authy) with recovery codes
  • WebAuthn / Passkeys — Windows Hello, Touch ID, FIDO2 security keys
  • LDAP / Active Directory via the ActiveDirectory module with multi-provider support and periodic user/group sync
  • API tokens — per-user bearer tokens with optional role downscoping
  • Legacy API key — the X-Api-Key header is still supported for backward compatibility

RBAC is namespace-scoped: each user has a role per namespace. Roles, from least to most privileged:

  • Viewer — read-only access
  • Operator — deploy, stop, and manage applications
  • Admin — full access inside a namespace, including secrets, routes, and users
  • Super-Admin — cluster-wide administration (join keys, nodes, module install)

A default super-admin (admin / admin) is created on first run — change it immediately.

Plugins

All core functionality lives in 5 hot-reloadable plugins loaded via collectible AssemblyLoadContext. Plugins reference only the AppCluster.Contracts assembly and can be updated without restarting the node.

PluginPurpose
ArtifactStoreArtifact upload, storage, and SHA256 verification
ProcessManagerProcess start, stop, and lifecycle monitoring
IsolationManagerOS user isolation + AppArmor (Linux) / NTFS ACLs (Windows)
ResourceManagercgroups v2 (Linux) / Job Objects (Windows) resource limits
HealthReporterHealth checks and /proc metrics collection

See the Plugins page for detailed descriptions and downloads.

Modules

Optional modules are standalone ASP.NET Core apps deployed to the system namespace that talk to the node over named-pipe IPC. Each module exposes /health, /ready, and /module-ui endpoints, and its UI is multiplexed into the main dashboard.

Provider modules (DNS, SSL, load balancer, notification channels) follow a standard /api/provider/* pattern with capability discovery so the orchestrating module can plug in new backends without code changes.

ModulePurpose
Reconciler.Dotnet.NET app reconciler — deploy, health, exec. Auto-installed on node startup. Multi-replica per node with dynamic port allocation.
Reconciler.StaticStatic site reconciler using per-app Caddy processes
Reconciler.DockerDocker container reconciler — pulls images from the Registry module
RegistryContainer registry integration — reads registry:2 catalogs, manages credentials, resolves digests
LoadBalancersCentral load balancer manager; delegates to provider modules (LbNginx, LbHaproxy)
LbNginxNginx provider — SSH-based config sync, server provisioning, ACME challenges
LbHaproxyHAProxy provider — Data Plane API with SSH-fallback sync
CronJobsMaster-coordinated cron scheduling via Cronos; SSH execution; SQLite history
JobsInterval-based HTTP job scheduling
WebhooksWebhook delivery for cluster events
DnsManagerDNS orchestration — discovers and routes to provider modules
DnsProviderCloudflareCloudflare DNS provider (zones, records, Origin CA)
SslProviderLetsencryptLet's Encrypt certificates (HTTP-01 and DNS-01 challenges)
SslProviderInternalCaInternal certificate authority with PEM download
BackupRestoreEncrypted backup / restore ZIP snapshots with config, artifacts, and secrets
AlertingAlert rules on metrics and logs with acknowledge flow
SmtpProviderSMTP email notification channel (MailKit)
MetricsCollectorNode and app metric collection; dashboard UI with sparklines and gauges
ActiveDirectoryLDAP / AD authentication and periodic user/group sync

Configuration

Ports

PortPurposeConfigurable
5100REST API + Web GUIYes
5101Leader / follower state replicationYes
5102Internal cluster communicationYes
5103HTTPS (with SNI)Yes

Data Directory

By default, node data is stored in a data/ directory relative to the working directory. Override with the --data CLI argument:

dotnet run --project src/AppCluster.Node -- --data /var/lib/appcluster

Technology Stack

  • .NET 10, ASP.NET Core 10 (Minimal APIs)
  • Custom leader/follower state replicator — SSE stream + SQLite-persisted snapshots
  • YARP — Reverse proxy
  • Microsoft.Data.Sqlite — state, logs, deployment history, alerting, backups, AD cache
  • Serilog — Structured logging
  • YamlDotNet — Config parsing
  • SSH.NET — Load balancer and cron remote execution
  • Certes — ACME / Let's Encrypt
  • MailKit — SMTP email
  • Cronos — Cron expression parsing
  • BCrypt.Net-Next, Fido2, Otp.NET — Password, WebAuthn, and TOTP auth
  • System.DirectoryServices.Protocols — LDAP / Active Directory