DEV Community

Welcome Bonginhlahla Sithole
Welcome Bonginhlahla Sithole

Posted on

Meet AppPipe: The Lightweight, On-Premises Alternative to .NET Aspire

Modern cloud-native developer environments are fantastic. Frameworks like .NET Aspire have revolutionized local development by providing a unified developer dashboard, automatic service discovery, and OTLP telemetry collection.

But what happens when it's time to deploy your microservice topology on-premises?

If your target is IIS on Windows Server or a systemd service on Linux, you've likely realized that deploying the standard .NET Aspire stack on-prem is a complex puzzle. There is no native hosting model for IIS, gRPC telemetry port mapping is fragile, and the dashboard's constant WebSocket connections can consume excessive resources on on-premises virtual machines.

Enter AppPipe.Hosting—a lightweight, developer-friendly NuGet package designed specifically to bring the best features of .NET Aspire to your on-premises environments.


The Problem: On-Premises Microservice Orchestration is Hard

While cloud platforms have native orchestration (like Kubernetes or ECS), traditional Windows and Linux environments still host a massive volume of enterprise applications. When running microservices on IIS or Linux servers, developers face three major friction points:

  1. Port Conflicts & Service Discovery: Dynamically assigning ports to multiple microservices in IIS or systemd and injecting them into dependent services is tedious.
  2. Telemetry Aggregation: Running an OpenTelemetry Collector just to aggregate traces, logs, and metrics for a small on-prem cluster is heavy and complex to configure.
  3. Resource Exhaustion: Standard Blazor Interactive Server dashboards maintain constant WebSockets and high memory usage, which quickly drains limited hosting environments.

The Solution: AppPipe

AppPipe is a lightweight alternative that integrates a routing gateway, an in-memory telemetry store, and a visual dashboard directly into a single library.

graph TD
    Client(Browser/Client) -->|HTTP| Gateway

    subgraph User's Application Space
        Backend1[Backend Microservice A]
        Backend2[Backend Microservice B]
    end

    subgraph AppPipe.Hosting NuGet Package
        Gateway[AppPipe Gateway / YARP]
        TelemetryPort[Gateway Telemetry Port]
        Store[In-Memory Telemetry Store]
        Dashboard[Blazor Dashboard UI]

        TelemetryPort --> Store
        Store --> Dashboard
    end

    Gateway -->|Service Discovery Routing| Backend1
    Gateway -->|Service Discovery Routing| Backend2
    Backend1 -->|OTLP gRPC| TelemetryPort
    Backend2 -->|OTLP gRPC| TelemetryPort
Enter fullscreen mode Exit fullscreen mode

Key Capabilities:

  • ⚡ Service Discovery & Routing via YARP: Under the hood, AppPipe uses Microsoft's YARP (Yet Another Reverse Proxy). It serves as a single entry point for your client, proxying traffic to backend microservices automatically using clean, virtual paths.
  • 📊 OTLP Telemetry Collection: AppPipe exposes a dedicated HTTP/2 Kestrel endpoint that acts as a local OTLP collector. Your microservices export standard traces, logs, and metrics directly to the AppPipe Gateway, which parses and displays them in the dashboard.
  • 🖥️ Gorgeous Blazor Dashboard: View traces as waterfall flamegraphs, inspect console logs, and monitor metrics over time in a responsive Light/Dark theme dashboard.
  • ⚡ Dual Render Modes: Run the dashboard in full WebSocket InteractiveServer mode for real-time updates during development, or swap to Server-Side Rendering (SSR) mode in production to drastically reduce CPU and memory consumption.
  • 🚀 Built-in On-Prem Deployment Pipeline: Using ModularPipelines, AppPipe provides automatic scripts to publish your services, spin up dedicated IIS Application Pools, configure Windows sub-applications, and inject service endpoints and telemetry tokens safely.

How Easy Is It to Use?

Setting up AppPipe in your orchestration host project takes just a few lines of C#:

using AppPipe.Hosting;

var builder = AppPipeHostingApp.CreateBuilder(args);

// Define a backend worker microservice using compile-safe generated constant
var backend = builder.AddProject(AppPipeProjects.BackendWorker);

// Or register directly using raw string name:
// var backend = builder.AddProject("BackendWorker");

// Define a frontend API and declare its reference to BackendWorker
var frontend = builder.AddProject(AppPipeProjects.FrontendApi)
                      .WithReference(backend); // Injects service discovery variables automatically

var app = builder.Build();

// Run the host using AppPipeDevHostRunner
var runner = new AppPipeDevHostRunner(app);
await runner.RunAsync();
Enter fullscreen mode Exit fullscreen mode

Inside your microservices, register the standard OpenTelemetry exporter:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter()) // Automatically routes to AppPipe telemetry port
    .WithMetrics(metrics => metrics
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter());
Enter fullscreen mode Exit fullscreen mode

💾 Customizing the Telemetry Database (SQLite, SQL Server, ClickHouse, etc.)

By default, AppPipe retains telemetry in a circular in-memory buffer (InMemoryTelemetryStore). If you are running in production or want persistent diagnostic logs, you can easily plug in your own database.

AppPipe defines a simple dependency injection contract for storage using ITelemetryStore:

namespace AppPipe.Gateway.Services;

public interface ITelemetryStore
{
    ConcurrentDictionary<string, ParsedTrace> Traces { get; }
    ConcurrentQueue<ParsedLog> Logs { get; }
    ConcurrentQueue<ExportMetricsServiceRequest> Metrics { get; }

    void AddTrace(ExportTraceServiceRequest request);
    void AddLog(ExportLogsServiceRequest request);
    void AddMetric(ExportMetricsServiceRequest metric);

    event Action? OnTelemetryReceived;
}
Enter fullscreen mode Exit fullscreen mode

Implementing and Configuring a Custom Database

  1. Write Your Implementation: Create a class implementing ITelemetryStore that persists traces, logs, and metrics to your chosen database (e.g. PostgreSQL, SQLite, or ClickHouse):
public class SqliteTelemetryStore : ITelemetryStore
{
    // Implement parsing, writing, and querying logic here...
    public void AddTrace(ExportTraceServiceRequest request)
    {
        // Save to SQLite DB
        OnTelemetryReceived?.Invoke();
    }
    // ...
}
Enter fullscreen mode Exit fullscreen mode
  1. Register It in the DI Container: Register your custom telemetry store with the gateway builder during startup:
var builder = AppPipeHostingApp.CreateBuilder(args);

// Register custom Telemetry Store on the Gateway's DI container:
builder.ConfigureGateway(gatewayBuilder =>
{
    gatewayBuilder.Services.AddSingleton<ITelemetryStore, SqliteTelemetryStore>();
});

var app = builder.Build();

// Run the host using DevHostRunner
var runner = new DevHostRunner(app);
await runner.RunAsync();
Enter fullscreen mode Exit fullscreen mode

AppPipe will automatically bypass the default in-memory storage and route all parsed incoming OTLP exports to your database store instead!


Built for the Realities of On-Prem Hosting

When deploying to production IIS environments, AppPipe features specific optimizations to ensure maximum reliability and low overhead:

1. IIS Token Overwrite Filter

IIS integration validates incoming requests using an MS-ASPNETCORE-TOKEN pairing token. Since outgoing HTTP calls from your microservices carry mismatched pairing tokens from different AppPools, IIS natively rejects telemetry exports. AppPipe implements a custom startup filter at index 0 that overrides these headers for loopback connections on the telemetry port, allowing OTLP traffic to flow without compromising security.

2. High-Performance SSR Mode

To prevent Blazor Interactive Server from spawning idle WebSockets and consuming RAM on your servers, setting Dashboard:UseWebSockets to false configures the dashboard in pure SSR mode. All refreshes, trace waterfalls, and searches are handled cleanly with base-relative URLs that work perfectly under IIS sub-applications (e.g. /your-sub-app/).

3. In-Memory Cap

AppPipe retains up to 200 traces and metrics in-memory, ensuring you have enough diagnostic history to investigate issues without causing memory leak overhead.

4. Granular Service & AppPool Customization

Deploying in enterprise environments means configuring security and application lifecycles. AppPipe now provides a fluent C# API to configure:

  • Custom AppPools: Run services under dedicated resource pools.
  • Service Identities & Credentials: Run Windows Services or IIS AppPools under custom domain user credentials or system identities (like NetworkService or LocalSystem).
  • SCM Startup Types & Details: Control service startup triggers (auto, demand, disabled) and descriptions in Windows SCM.
  • Hosting Models: Force IIS hosting models (e.g. InProcess vs OutOfProcess).

🚀 Integrating AppPipe into DevOps Pipelines

In professional staging and production environments, you build your code once (generating DLLs) and deploy those pre-compiled artifacts across multiple environments. The target servers do not contain the source code or the .NET SDK.

AppPipe is built to accommodate this separation of concerns:

1. Build Once (CI)

On your build server, compile and publish your orchestrator and microservice projects:

dotnet publish samples/AppPipe.DevHost/AppPipe.DevHost.csproj -c Release -o ./publish/AppPipe.DevHost
dotnet publish samples/BackendWorker/BackendWorker.csproj -c Release -o ./publish/BackendWorker
dotnet publish samples/FrontendApi/FrontendApi.csproj -c Release -o ./publish/FrontendApi
Enter fullscreen mode Exit fullscreen mode

Package and upload the ./publish directory as a build artifact.

2. Deploy Anywhere (CD)

On the target machine, download your pre-compiled files and invoke the orchestrator with the --prepublished-dir option:

dotnet C:\inetpub\apps\AppPipe\AppPipe.DevHost.dll --deploy iis --prepublished-dir C:\inetpub\apps\AppPipe
Enter fullscreen mode Exit fullscreen mode

This flag tells the orchestrator to bypass .csproj checks and local compilation steps entirely, deploying direct to IIS or SCM from your pre-compiled DLLs.

3. Injecting Environment Configs & Secrets

To configure environment-specific settings (like AppPool names or site directories) and environment secrets (like service passwords), bind the topology definition to standard .NET Configuration:

var config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: true)
    .AddEnvironmentVariables(prefix: "APPIPE__")
    .AddCommandLine(args)
    .Build();

var appPool = config["BackendWorker:AppPoolName"] ?? "ProdPool";
var password = config["BackendWorker:ServicePassword"];

builder.AddProject("BackendWorker")
       .WithAppPool(appPool)
       .WithServiceAccount(@"DOMAIN\ServiceAccount")
       .WithServicePassword(password);
Enter fullscreen mode Exit fullscreen mode

4. Customizing the Dashboard Application Name & Details

You can customize the dashboard application name, its IIS AppPool, its sub-path, and its Windows Service details in exactly the same way as your other microservices:

// Define a custom name for the dashboard host
builder.HostProject = new AppPipeHostingProjectResource("EnterpriseDashboard", "");

// Configure the dashboard fluently
builder.HostProject.WithEndpoint(7001)
                   .WithIISSite("Default Web Site")
                   .WithAppPath("/") // Deployed at the root path '/' of the site/proxy
                   .WithAppPool("EnterpriseDashboardPool")
                   .WithServiceDisplayName("Enterprise Telemetry Gateway")
                   .WithServiceDescription("Aggregates telemetry from all active business microservices.");
Enter fullscreen mode Exit fullscreen mode

During your CD release stage, you can inject these settings dynamically:

  • Environment Variables: Map DevOps secrets/variables as environment variables (e.g., APPIPE__BackendWorker__ServicePassword $\rightarrow$ $(SecretPassword)).
  • Command Line Params: Pass them directly to the orchestrator:
  dotnet AppPipe.DevHost.dll --deploy iis --prepublished-dir C:\inetpub\apps\AppPipe --BackendWorker:AppPoolName "ProdPool" --BackendWorker:ServicePassword "$(SecretPassword)"
Enter fullscreen mode Exit fullscreen mode

Get Started Today

AppPipe is open-source and ready to streamline your on-premises microservices.

Top comments (0)