Building an AI Chat app with .NET Aspire, Ollama/OpenAI, Postgres, and Redis

With .NET Aspire, you can orchestrate a full AI chat system — backend, model, data store, and frontend — from one place.
This sample shows how Aspire can manage a large language model (LLM), a Postgres conversation database, a Redis message broker, and a React-based chat UI, all within a single orchestration file.

Folder layout

11_AIChat/
├─ AppHost/ # Aspire orchestration
├─ ChatApi/ # .NET 9 backend API (SignalR + EF)
├─ chatui/ # React + Vite frontend
├─ ServiceDefaults/ # shared settings (logging, health, OTEL)
└─ README.md

Overview

This example demonstrates:

  • AI model orchestration with local Ollama or hosted OpenAI
  • Postgres database for conversation history
  • Redis for live chat streaming and cancellation coordination
  • Chat API using ASP.NET Core + SignalR
  • React/Vite frontend for real-time conversations
  • Full Docker Compose publishing via Aspire

The AppHost (orchestration)

AppHost/Program.cs

var builder = DistributedApplication.CreateBuilder(args);

// Publish this as a Docker Compose application
builder.AddDockerComposeEnvironment("env")
       .WithDashboard(db => db.WithHostPort(8085))
       .ConfigureComposeFile(file =>
       {
           file.Name = "aspire-ai-chat";
       });

// The AI model definition
var model = builder.AddAIModel("llm");

if (OperatingSystem.IsMacOS())
{
    model.AsOpenAI("gpt-4o-mini");
}
else
{
    model.RunAsOllama("phi4", c =>
    {
        c.WithGPUSupport();
        c.WithLifetime(ContainerLifetime.Persistent);
    })
    .PublishAsOpenAI("gpt-4o-mini");
}

// Postgres for conversation history
var pgPassword = builder.AddParameter("pg-password", secret: true);

var db = builder.AddPostgres("pg", password: pgPassword)
                .WithDataVolume(builder.ExecutionContext.IsPublishMode ? "pgvolume" : null)
                .WithPgAdmin()
                .AddDatabase("conversations");

// Redis for message streams + coordination
var cache = builder.AddRedis("cache").WithRedisInsight();

// Chat API service
var chatapi = builder.AddProject<Projects.ChatApi>("chatapi")
                     .WithReference(model).WaitFor(model)
                     .WithReference(db).WaitFor(db)
                     .WithReference(cache).WaitFor(cache);

// Frontend served via Vite
builder.AddNpmApp("chatui", "../chatui")
       .WithNpmPackageInstallation()
       .WithHttpEndpoint(env: "PORT")
       .WithReverseProxy(chatapi.GetEndpoint("http"))
       .WithExternalHttpEndpoints()
       .WithOtlpExporter()
       .WithEnvironment("BROWSER", "none");

builder.Build().Run();
Continue reading “Building an AI Chat app with .NET Aspire, Ollama/OpenAI, Postgres, and Redis”

Processing Azure Service Bus messages locally with .NET Aspire

You don’t need a cloud namespace to prototype a queue-driven worker. With .NET Aspire, you can spin up an Azure Service Bus emulator, wire a Worker Service to a queue, and monitor it all from the Aspire dashboard—no external dependencies.

This post shows a minimal setup:

  • Aspire AppHost that runs the Service Bus emulator
  • A queue (my-queue) + a dead-letter queue
  • A Worker Service that consumes messages
  • Built-in enqueue commands to test locally

Folder layout

10_AzureServiceBus/
├─ AppHost/ # Aspire orchestration
├─ ServiceDefaults/ # shared logging, health, etc.
├─ WorkerService/ # background processor
└─ README.md

To create it:

dotnet new worker -n WorkerService
dotnet new aspire-apphost -n AppHost
dotnet new aspire-servicedefaults -n ServiceDefaults

AppHost: Service Bus emulator + queues

var builder = DistributedApplication.CreateBuilder(args);

// Add Azure Service Bus
var serviceBus = builder.AddAzureServiceBus("servicebus")
                        .RunAsEmulator(e => e.WithLifetime(ContainerLifetime.Persistent))
                        .WithCommands();

var serviceBusQueue = serviceBus.AddServiceBusQueue("my-queue");
serviceBus.AddServiceBusQueue("dead-letter-queue");

// Add the worker and reference the queue
builder.AddProject<Projects.WorkerService>("workerservice")
    .WithReference(serviceBusQueue)
    .WaitFor(serviceBusQueue);

builder. Build().Run();
Continue reading “Processing Azure Service Bus messages locally with .NET Aspire”

Running Azure Functions locally with .NET Aspire + Azurite

You can orchestrate an Azure Function right inside .NET Aspire, complete with a local Azurite storage emulator and a single place (the Aspire dashboard) to observe everything. This post walks through a minimal setup using the isolated worker model on .NET 9.

What we’ll build

  • A simple HTTP-triggered Azure Function (/api/Hello)
  • An Azurite container for Storage (Blob/Queue/Table)
  • An Aspire AppHost that:
    • starts Azurite,
    • wires the Function’s AzureWebJobsStorage,
    • exposes the Function on port 7071

Create the project

# Templates
dotnet new install Microsoft.Azure.Functions.Worker.ProjectTemplates
dotnet new func -n AzureFunction -F net9.0

dotnet new install Microsoft.Azure.Functions.Worker.ItemTemplates
dotnet new http -n Hello -A Anonymous -p:n AzureFunction

# Aspire orchestration
dotnet new aspire-apphost -n AppHost
dotnet new aspire-servicedefaults -n ServiceDefaults

# (macOS) Core Tools for local runs
brew tap azure/functions
brew install azure-functions-core-tools@4
echo 'export PATH="/opt/homebrew/opt/azure-functions-core-tools@4/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

func --version

Folder layout

09_AzureFunction/
├─ AppHost/
├─ ServiceDefaults/
├─ AzureFunction.csproj
├─ Program.cs
├─ Hello.cs
├─ host.json
├─ local.settings.json
└─ README.md
Continue reading “Running Azure Functions locally with .NET Aspire + Azurite”

From Aspire to Docker Compose in one command

One of my favorite Aspire tricks is publishing your local orchestration to a plain Docker Compose file. No bespoke YAML hand-crafting: define your resources in C#, run one command, and get a production-shaped compose you can run anywhere Docker is available.

This post shows a tiny demo that publishes PostgreSQL, Redis, and the Aspire Dashboard to Docker Compose.

AppHost: enable the Docker publisher

var builder = DistributedApplication.CreateBuilder(args);

// Enables Docker publisher (gives the compose project a stable name)
builder.AddDockerComposeEnvironment("aspire-docker-demo");

var postgres = builder.AddPostgres("database")
    .WithDataVolume();

var database = postgres.AddDatabase("demo-db");

var redis = builder.AddRedis("cache");

builder. Build().Run();

What this does

  • AddDockerComposeEnvironment("aspire-docker-demo")
    Opts the app into the Docker publisher and sets the Compose project name (used for networks, volume names, etc.).
  • Postgres + volume
    WithDataVolume() ensures a named Docker volume is created for data durability.
  • AddDatabase("demo-db")
    Declares a logical DB (helpful when apps bind to it; not strictly needed for Compose output).
  • Redis
    Adds a simple cache service.

Publish to Docker Compose

From the AppHost directory:

aspire publish -o docker-compose-artifacts

This generates a docker-compose.yaml (and related files) under ./docker-compose-artifacts/.

Continue reading “From Aspire to Docker Compose in one command”

Developing .NET Aspire inside a Dev Container (VS Code)

Running .NET Aspire inside a Dev Container gives you a reproducible, fully-tooled environment: Docker, .NET 9 SDK, Aspire CLI, and VS Code extensions—all preconfigured. No more “works on my machine.”

This post shows how to bootstrap an Aspire starter app and wire up a Dev Container so anyone can open the project and hit Run.

1) Create the starter app

dotnet new install Aspire.ProjectTemplates --force
dotnet new aspire-starter -n HelloAspire

You’ll get a solution with:

HelloAspire/
├─ HelloAspire.AppHost/          # Aspire orchestrator
├─ HelloAspire.ApiService/       # Minimal API
├─ HelloAspire.Web/              # Web frontend
├─ HelloAspire.ServiceDefaults/  # Shared defaults
└─ HelloAspire.sln

2) Add a Dev Container

Create .devcontainer/devcontainer.json:

// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
  "name": ".NET Aspire",
  "image": "mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm",
  "features": {
    "ghcr.io/devcontainers/features/docker-in-docker:2": {},
    "ghcr.io/devcontainers/features/powershell:1": {}
  },

  "hostRequirements": {
    "cpus": 8,
    "memory": "32gb",
    "storage": "64gb"
  },

  "onCreateCommand": "curl -sSL https://aspire.dev/install.sh | bash",
  "postStartCommand": "dotnet dev-certs https --trust",

  "customizations": {
    "vscode": {
      "extensions": [
        "ms-dotnettools.csdevkit",
        "GitHub.copilot-chat",
        "GitHub.copilot"
      ]
    }
  }
}
Continue reading “Developing .NET Aspire inside a Dev Container (VS Code)”

Building a Dynamic Command Sample with .NET Aspire

In previous posts, we’ve seen how .NET Aspire can orchestrate multi-service setups — from running Python APIs to launching Vite frontends.
But Aspire can also do something more subtle and powerful: dynamically control how resources start and run — even with custom arguments at runtime.

This post demonstrates a minimal example showing how to build an Aspire interactive command that lets you input arguments on the fly before launching a console app.

Folder layout

Here’s the structure of this sample:

06_DynamicCMDSample/
├─ App/
│  └─ Program.cs
├─ AppHost/
│  └─ Program.cs
├─ ServiceDefaults/
│  └─ (default configuration)
└─ README.md

This layout follows the standard Aspire application template:

  • App → a simple console project
  • AppHost → the orchestration layer (the Aspire “brain”)
  • ServiceDefaults → common defaults like logging and configuration
Continue reading “Building a Dynamic Command Sample with .NET Aspire”

Running a Python MCP server inside .NET Aspire

.NET Aspire isn’t limited to .NET microservices — you can orchestrate Python components just as easily.
In this post, we’ll look at how to run a Python MCP (Model Context Protocol) server inside an Aspire application, integrate it with the MCP Inspector, and expose it via a Dev Tunnel for testing.

Folder Layout

05_PythonMCP/
├─ AppHost.cs
├─ PythonMCP.csproj
├─ appsettings.json
├─ appsettings.Development.json
├─ NuGet.config
├─ README.md
└─ pymcp/
   ├─ main.py
   ├─ requirements.txt
   └─ (optional) .venv/

AppHost.cs

Here’s the full orchestration file:

var builder = DistributedApplication.CreateBuilder(args);

var inspector = builder.AddMcpInspector("mcp-inspector");

var tunnel = builder.AddDevTunnel("dev-tunnel");

var pyMcpServer = builder.AddPythonApp(
    name: "pymcp",
    appDirectory: "./pymcp",
    scriptPath: "main.py"
).WithHttpEndpoint(env: "PORT");

tunnel.WithReference(pyMcpServer, allowAnonymous: true);

inspector.WithMcpServer(pyMcpServer);

builder. Build().Run();
Continue reading “Running a Python MCP server inside .NET Aspire”

Running a Python FastAPI (Uvicorn) service from .NET Aspire

NET Aspire isn’t limited to .NET services. You can orchestrate a Python service (FastAPI on Uvicorn) right alongside your .NET components—same lifecycle, unified logs, single dotnet run.

Below is a minimal setup that boots a FastAPI app with health endpoint, exposes it via Aspire, and keeps everything tidy in one solution.

Folder layout

04_PythonUvicorn/
├─ AppHost.cs
├─ PythonUvicorn.csproj
├─ appsettings.json
├─ appsettings.Development.json
├─ README.md
└─ uvicornapp-api/          # Python app lives here
   ├─ main.py
   ├─ requirements.txt
   └─ (optional) .venv/

AppHost.cs (Aspire)

var builder = DistributedApplication.CreateBuilder(args);

var uvicorn = builder.AddUvicornApp(
        name: "uvicornapp",
        projectDirectory: "./uvicornapp-api",
        appName: "main:app"
    )
    .WithHttpEndpoint(targetPort: 8000)   // app listens on 8000
    .WithExternalHttpEndpoints();         // optional, expose to host

builder. Build().Run();
Continue reading “Running a Python FastAPI (Uvicorn) service from .NET Aspire”

Running a Vite Frontend from .NET Aspire

One of the nicest features of .NET Aspire is that it can orchestrate not just APIs and databases, but also frontends.
That means your backend, frontend, and supporting infrastructure can all be started with a single dotnet run — no separate terminal tabs, npm installs, or manual steps.

Let’s look at a minimal example where Aspire runs a Vite dev server directly from your project.

Folder Layout

Here’s the structure we’ll use:

03_ViteApps/
├─ AppHost.cs
├─ ViteApps.csproj
├─ appsettings.json
├─ appsettings.Development.json
└─ my-vite-app-npm/        # Vite project (package.json, src, index.html)

The .NET Aspire project (AppHost.cs) lives at the root, and your Vite app sits inside my-vite-app-npm/.

Continue reading “Running a Vite Frontend from .NET Aspire”

Building a simple reverse proxy with .NET Aspire and YARP

With .NET Aspire, you can spin up distributed applications and infrastructure components with almost no boilerplate.
Here’s a minimal example that uses YARP (Yet Another Reverse Proxy) to serve a static frontend:

var builder = DistributedApplication.CreateBuilder(args);

builder.AddYarp("frontend")
    .WithStaticFiles("./web");

builder. Build().Run();

That’s all it takes to host a frontend inside an Aspire-managed app!

Continue reading “Building a simple reverse proxy with .NET Aspire and YARP”

Website Powered by WordPress.com.

Up ↑