{"slug": "build-an-ai-powered-developer-portal-with-backstage-and-net", "title": "Build an AI-Powered Developer Portal with Backstage and .NET", "summary": "Technical guide for platform engineers and .NET developers on building an AI-powered Internal Developer Portal (IDP) using Backstage. The tutorial explains how to create a .NET CLI tool that scans source code and uses a local AI model (Ollama) to automatically generate service catalog metadata, solving the common problem of stale documentation. The guide emphasizes automation over manual YAML maintenance and includes instructions for scaffolding sample .NET services and integrating them with Backstage.", "body_md": "# Build an AI-Powered Developer Portal with Backstage and .NET\n\nWant to apply AI, not just read about it? Most tutorials stop at a \"Hello World\" chatbot. We are going to build something that actually solves a common engineering headache: stale documentation.\n\n## Who this is for\n\nThis guide is for platform engineers and .NET developers who need to organize a growing software landscape without forcing teams to manually write YAML files.\n\n## What you will build\n\nYou will build a **dynamic developer portal** using [Backstage](https://backstage.io/) that automatically populates its service catalog. We will use a .NET CLI tool to scan source code and use local AI ([Ollama](https://ollama.com/)) to generate summaries.\n\n-**Source repo:**[demo-backstage-catalog-generator](https://github.com/bgener/demo-backstage-catalog-generator) -** Constraint:** We use local inference only. No source code ever leaves your machine.\n\nHave you ever needed to update a service, but forgot what it does? Or spent time trying to understand code you have not touched in months? We usually solve this with a README.md that nobody updates, or a wiki that rots.\n\nAn Internal Developer Portal (IDP) solves this by making the software landscape visible, but only if the data is fresh. Automation is the only way to avoid the \"stale metadata\" trap.\n\nTip:\n\nWant to skip ahead?Check out the[complete working demo on]with all\n\nGitHub\n\nthe code ready to run!\n\n## Prerequisites\n\nBefore we start, make sure you have the following installed:\n\n[.NET SDK 8+](https://dotnet.microsoft.com/en-us/download)-\n[Node.js](https://nodejs.org/)(includes`npx`\n\nand`yarn`\n\n) -\n[Ollama](https://ollama.com/)with the`llama3:8b`\n\nmodel pulled - A GitHub account (for hosting and deployment)\n\n## Why Does an Internal Developer Portal Matter?\n\nThe term \"Internal Developer Portal\" (IDP) can be a little misleading, since it sounds like a tool exclusively for developers only. In reality, it functions as an**internal organizational portal focused entirely on your software portfolio**. Unlike a general-purpose SharePoint site where everything is dumped in one place and nothing is easy to find, an IDP is deliberately narrow in scope. It covers your software landscape and nothing else, which is exactly what makes it powerful.\n\nAn IDP becomes the**single source of truth** for your engineering organization. It answers critical questions across every role:\n\n-**Engineers:** Which services exist? Who owns them? What do they do? What are the APIs and how do I call them? -**Team leads and architects:** What is the team composition? Which squad owns which set of services? What architectural decisions have been made and why? -**New joiners:** How do I get up to speed on a codebase I have never seen before? -**Platform and operations teams:** What is running in production, who is responsible, and what is the lifecycle status?\n\nBeyond just listing services, a mature IDP centralizes**Architecture Decision Records (ADRs)**, arguably one of its most valuable features. ADRs capture *why* a decision was made, not just what was decided. Without a central place to surface them, they rot in forgotten wiki pages or git repositories that no one thinks to check.\n\nThe challenge is keeping all of this populated and accurate. If you rely on engineers to manually maintain metadata YAML files, the data grows stale within weeks. Automation is the only sustainable path.\n\n## Formulating the Architecture\n\nHere's the plan to extract metadata from source code and present it visually:\n\n-\n**Backstage:** the UI layer where engineers browse and discover services, APIs, documentation, and team ownership -**.NET Core:** a CLI tool that scans project folders, extracts metadata, and generates Backstage-compatible YAML -**Ollama:** runs AI inference locally, so your source code never leaves your machine -**Static hosting:** deploy to Netlify, Azure Static Web Apps, or any provider of your choice\n\nInfo:\n\nWhy Ollama?Because it runs locally and you do not want to expose your code\n\nto the AI agents over the public internet. You do not know what and how they\n\nuse it for, and it does not feel safe. If your employer finds out, you're\n\ndone.\n\n## Setting Up the Project Infrastructure\n\nYou can either follow along and build everything from scratch, or clone the [demo repository](https://github.com/bgener/demo-backstage-catalog-generator) to get started immediately.**To clone the demo:**```\ngit clone https://github.com/bgener/demo-backstage-catalog-generator.git\ncd demo-backstage-catalog-generator\nollama pull llama3:8b\nollama serve\n```\n\nTip:\n\nWe use`llama3:8b`\n\nspecifically. It is significantly faster for local\n\ninference than the full-size model and produces more consistent, concise\n\noutput for our use case. If you have a powerful GPU, feel free to use`llama3`\n\ninstead.\n\nNext, scaffold the baseline .NET services. We’ll create one Web API and one MVC project:\n\n```\nmkdir Backstage-Dev-Portal\ncd Backstage-Dev-Portal\n\ndotnet new webapi -n ServiceA\ndotnet new mvc -n ServiceB\n\ndotnet new sln -n Backstage-Dev-Portal\ndotnet sln add ServiceA/ServiceA.csproj\ndotnet sln add ServiceB/ServiceB.csproj\n```\n\nYou can replace the default controllers with real logic later. These raw services represent the uncataloged microservices in your organization.\n\n## Building a Smart Catalog Generator in .NET\n\nWe will build a .NET CLI tool using [OllamaSharp](https://github.com/awaescher/OllamaSharp). It scans each project, sends relevant files to the local AI model, and generates a single `catalog-info.yaml`\n\nfile containing all services, ready for Backstage to consume.\n\nCreate the tool and add the required package:\n\n```\ndotnet new console -n ProjectSummarizer\ncd ProjectSummarizer\ndotnet add package OllamaSharp\n```\n\nInstead of sending every file to the AI, we take a smarter approach to avoid token limits and save compute time. We will send only `*.csproj`\n\n, `Program.cs`\n\n, and the folder structure. This is all the context the AI needs to understand the project structure and purpose.\n\nReplace `Program.cs`\n\nwith the implementation below. The full version is in the [demo repository](https://github.com/bgener/demo-backstage-catalog-generator). Here we focus on the key parts.\n\nFirst, set up the Ollama client and configure the system prompt. This is the most fragile part of the chain: the system prompt has to force the model into a YAML-safe format without it hallucinating markdown backticks:\n\n``` js\nvar ollamaApiClient = new OllamaApiClient(\n    new Uri(\"http://localhost:11434\")) { SelectedModel = \"llama3:8b\" };\n\nvar chat = new Chat(ollamaApiClient, systemPrompt:\n    \"You are a technical documentation assistant. \" +\n    \"You produce concise, YAML-safe summaries of .NET projects. \" +\n    \"Output only plain text, no markdown, no bullet points, no quotes, no colons, no newlines.\");\n```\n\nInstead of sending every file to the AI, we only send `*.csproj`\n\n, `Program.cs`\n\n, and the folder structure. This is all the context the model needs.\n\nWarning:\n\nPrompt sanitization is critical.If your`Program.cs`\n\ncontains complex\n\nstring literals or nested colons, the AI might pass them through to your YAML,\n\nbreaking the Backstage parser. Always sanitize the output before writing the\n\nfile.\n\n``` js\nvar sb = new StringBuilder();\nsb.AppendLine($\"Project: {projectName}\");\nsb.AppendLine(\"Folder structure:\");\nAppendFolderStructure(projectDir, sb, \"\");\nsb.AppendLine(File.ReadAllText(csprojPath));\n\nvar programPath = Directory.GetFiles(projectDir, \"Program.cs\", SearchOption.AllDirectories)\n    .FirstOrDefault();\nif (programPath != null)\n    sb.AppendLine(File.ReadAllText(programPath));\n```\n\nThe prompt itself uses few-shot examples to guide the model toward the output format we want:\n\n``` js\nvar prompt = \"Summarize the project in 1-2 sentences based on the files provided. \" +\n             \"Do not output anything else. \" +\n             \"Examples of good output: \" +\n             \"REST API service providing weather forecasts with temperature data\\n\" +\n             \"ASP.NET MVC application with React frontend for managing todo items\\n\\n\"\n             + sb.ToString();\n\nawait foreach (var token in chat.SendAsync(prompt, cts.Token))\n    summaryBuilder.Append(token);\n```\n\nFinally, each summary is sanitized and assembled into a Backstage-compatible YAML entry:\n\n``` js\nvar summary = summaryBuilder.ToString().Trim()\n    .Replace(\"\\n\", \" \").Replace(\":\", \" -\").Replace(\"\\\"\", \"'\");\n\nvar yamlEntry = $@\"\napiVersion: backstage.io/v1alpha1\nkind: Component\nmetadata:\n  name: {projectName.ToLowerInvariant()}\n  description: \"\"{summary}\"\"\nspec:\n  type: service\n  lifecycle: production\n  owner: group:default/engineering\";\n```\n\nRun the generator against the target directory (use `.`\n\nif you are already in the project root):\n\n```\ndotnet run --project ProjectSummarizer -- .\n```\n\nYou should see the AI streaming its summaries in real time:\n\nMost of the real work here is figuring out the prompt. Even a tiny change can produce a completely different output. I encourage you to experiment with the system prompt and the user prompt to see how it affects quality. That is the real learning here.\n\n## Integrating the AI Catalog with Backstage\n\nWith the `catalog-info.yaml`\n\nready, we can integrate it into a Backstage instance. Install Backstage:\n\n```\nnpx @backstage/create-app\n```\n\nFollow the prompts to name it `dev-portal`\n\n.\n\nNow point Backstage to your generated catalog file. Open `app-config.yaml`\n\nin the `dev-portal`\n\ndirectory and add the following under the `catalog`\n\nsection:\n\n```\ncatalog:\n  locations:\n    - type: file\n      target: ../Backstage-Dev-Portal/catalog-info.yaml\n      rules:\n        - allow: [Component]\n```\n\nThis tells Backstage where to find the AI-generated service metadata. The `target`\n\npath is relative to the Backstage root directory. Adjust it to point to wherever your generator wrote the `catalog-info.yaml`\n\n.\n\nTo run it locally:\n\n```\ncd dev-portal\nyarn dev\n```\n\nOpen `http://localhost:3000`\n\nin your browser. You should see all your services listed in the **Software Catalog** with AI-generated summaries visible in the description column.\n\n## Deploying the Portal\n\nTo host this, build your portal as a static site:\n\n```\nyarn build:static\n```\n\nPush the output to GitHub and deploy to any**static hosting provider**: Netlify, Azure Static Web Apps, Vercel, or even self-hosted on Kubernetes. Set the build command to `yarn build:static`\n\nand the publish directory to `dist`\n\n.\n\nInfo:\n\nA static Backstage build is great for read-only catalogs. If you need dynamic\n\nfeatures likeauthentication,real-time plugin backends, orwrite, you will need to deploy the full Backstage backend as a Node.js\n\noperations\n\nservice instead.\n\n## Automating with CI/CD\n\nThe real value comes from running the catalog generator automatically. Here is a GitHub Actions workflow that regenerates summaries on every push to `main`\n\n:\n\n```\nname: Update Backstage Catalog\non:\n  push:\n    branches: [main]\n\njobs:\n  generate-catalog:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup .NET\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: ‘8.0.x’\n\n      - name: Install and start Ollama\n        run: |\n          curl -fsSL https://ollama.com/install.sh | sh\n          ollama serve &\n          sleep 5\n          ollama pull llama3:8b\n\n      - name: Generate catalog\n        run: dotnet run --project ProjectSummarizer -- \"$GITHUB_WORKSPACE\"\n\n      - name: Commit updated catalog\n        run: |\n          git config user.name \"github-actions\"\n          git config user.email \"github-actions@github.com\"\n          git add catalog-info.yaml\n          git diff --cached --quiet || git commit -m \"chore: regenerate AI catalog summaries\"\n          git push\n```\n\nWarning:\n\nCI Performance:Running Ollama in CI uses CPU-only inference by default. A\n\n`llama3:8b`\n\nsummary takes about 20-30 seconds per project on a standard GitHub\n\nrunner. For a large monorepo, your CI bill will spike. Consider using a\n\npe", "url": "https://wpnews.pro/news/build-an-ai-powered-developer-portal-with-backstage-and-net", "canonical_source": "https://dev.to/bgener/build-an-ai-powered-developer-portal-with-backstage-and-net-300j", "published_at": "2026-05-21 15:58:15+00:00", "updated_at": "2026-05-21 16:06:16.899470+00:00", "lang": "en", "topics": ["artificial-intelligence", "developer-tools", "open-source", "enterprise-software"], "entities": ["Backstage", ".NET", "Ollama", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/build-an-ai-powered-developer-portal-with-backstage-and-net", "markdown": "https://wpnews.pro/news/build-an-ai-powered-developer-portal-with-backstage-and-net.md", "text": "https://wpnews.pro/news/build-an-ai-powered-developer-portal-with-backstage-and-net.txt", "jsonld": "https://wpnews.pro/news/build-an-ai-powered-developer-portal-with-backstage-and-net.jsonld"}}