<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris Ayers</title>
    <description>The latest articles on DEV Community by Chris Ayers (@chris_ayers).</description>
    <link>https://dev.to/chris_ayers</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F238853%2F04e0591b-6030-4abf-bb3f-d45d37c118ba.png</url>
      <title>DEV Community: Chris Ayers</title>
      <link>https://dev.to/chris_ayers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chris_ayers"/>
    <language>en</language>
    <item>
      <title>NDC Security 2026</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/ndc-security-2026-1h3n</link>
      <guid>https://dev.to/chris_ayers/ndc-security-2026-1h3n</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pf1oxy59xtdtu01mkap.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pf1oxy59xtdtu01mkap.jpg" alt="NDC Security 2026 - MITRE ATT&amp;amp;CK for Developers" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m excited to be speaking at &lt;a href="https://ndcsecurity.com/" rel="noopener noreferrer"&gt;NDC Security 2026&lt;/a&gt; in Oslo, March 2-5! I’ll be presenting &lt;strong&gt;MITRE ATT&amp;amp;CK for Developers&lt;/strong&gt; — showing how developers can go beyond the OWASP Top 10 and use the ATT&amp;amp;CK framework to think like attackers and build stronger defenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  About NDC Security
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ndcsecurity.com/" rel="noopener noreferrer"&gt;NDC Security&lt;/a&gt; is a dedicated security conference for software developers, held at the Radisson Blu Scandinavia Hotel in Oslo. With 66 sessions and over 60 speakers, it bridges the gap between development and security — designed for developers who want to build secure software and security professionals who want to understand modern development practices.&lt;/p&gt;

&lt;p&gt;This year features an all-new &lt;strong&gt;OWASP x NDC Security&lt;/strong&gt; track, bringing OWASP content directly into the conference lineup.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Talk: MITRE ATT&amp;amp;CK for Developers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Wednesday, March 4 at 10:20 — Room 3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most developers know the OWASP Top 10, but fewer know the MITRE ATT&amp;amp;CK framework. In this talk, I’ll cover how ATT&amp;amp;CK complements OWASP, walk through real attack chains with code examples in Python, C#, and JavaScript, and show practical detection patterns you can implement in your applications. The goal: think like an attacker, build like a defender.&lt;/p&gt;

&lt;p&gt;For a preview, check out my blog post on &lt;a href="https://dev.to/posts/mitre-attack-framework/"&gt;MITRE ATT&amp;amp;CK for Developers: Beyond OWASP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conference Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keynote
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Michael Howard&lt;/strong&gt; opens the conference with &lt;strong&gt;25 Years of the Microsoft SDL&lt;/strong&gt; — a look back at how the Security Development Lifecycle has shaped how we build secure software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workshops (March 2-3)
&lt;/h3&gt;

&lt;p&gt;The conference kicks off with two days of hands-on workshops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bulletproof APIs: Hands-On API Security&lt;/strong&gt; — Philippe De Ryck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hack Yourself First: How to Go on the Cyber-Offence&lt;/strong&gt; — Scott Helme&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity and Access Control for Modern Applications using ASP.NET 10&lt;/strong&gt; — Anders Abel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Building and Deploying Secure AI: Practical Strategies for Developers&lt;/strong&gt; — Jim Manico&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attack and Secure AI Apps - Wargame Edition&lt;/strong&gt; — Davide Cioccia&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-Stack Pentesting Laboratory&lt;/strong&gt; — Dawid Czagan&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Notable Sessions
&lt;/h3&gt;

&lt;p&gt;A few talks I’m looking forward to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Injection Attacks in LLM-Powered Applications&lt;/strong&gt; — Magno Logan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASP.NET Core Meets OWASP Top 10 2025&lt;/strong&gt; — Anders Abel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Securing Model Context Protocol (MCP)&lt;/strong&gt; — Jim Manico&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting Authorization Right in .NET&lt;/strong&gt; — Michele Leroux Bustamante&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beyond the Commit: Weaponizing and Hardening GitHub Actions&lt;/strong&gt; — Niek Palm&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your Website Is Running Code You’ve Never Seen&lt;/strong&gt; — Scott Helme&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  See You There
&lt;/h2&gt;

&lt;p&gt;If you’re attending NDC Security, come say hello! You can check out the full agenda and grab tickets at &lt;a href="https://ndcsecurity.com/" rel="noopener noreferrer"&gt;ndcsecurity.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>infosec</category>
      <category>news</category>
      <category>security</category>
    </item>
    <item>
      <title>Promoted to Principal Software Engineer at Microsoft</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/promoted-to-principal-software-engineer-at-microsoft-1aea</link>
      <guid>https://dev.to/chris_ayers/promoted-to-principal-software-engineer-at-microsoft-1aea</guid>
      <description>&lt;p&gt;I’m thrilled to share that I’ve been promoted to &lt;strong&gt;Principal Software Engineer&lt;/strong&gt; at Microsoft!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey
&lt;/h2&gt;

&lt;p&gt;It’s been an incredible four and a half years at Microsoft, and this milestone still feels a bit surreal. When I &lt;a href="https://dev.to/posts/time-to-follow-my-dream-im-joining-microsoft/"&gt;joined Microsoft in October 2021&lt;/a&gt; as a Senior Customer Engineer on the Fast Track for Azure (FTA) team, I was living a dream I’d had since childhood. I never imagined the journey would bring me here.&lt;/p&gt;

&lt;p&gt;During my time on the FTA team, I worked directly with customers adopting Azure, helping them architect and build cloud-native solutions. It was an amazing experience - every engagement brought new challenges, new architectures, and new opportunities to learn and teach.&lt;/p&gt;

&lt;p&gt;In late 2024, I &lt;a href="https://dev.to/posts/new-team-new-focus/"&gt;moved to the Azure Reliability (AzRel) Risk SRE team&lt;/a&gt;, a shift that pushed me into entirely new territory. As the lead for the Platform Reliability Pillar, I dove into outage analysis, building AI-driven tooling with OpenAI and Semantic Kernel to help engineers understand failures and strengthen Azure’s reliability at scale. It was a big change, but it reinforced something I’ve always believed - growth comes from stepping into the unfamiliar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;As a Principal Software Engineer, I’m continuing to focus on Azure reliability - building tools and systems that help keep the platform running smoothly for millions of users worldwide. I’m also doubling down on the things I’m passionate about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building&lt;/strong&gt; - AI-powered tooling for reliability engineering, distributed systems, and cloud-native applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speaking&lt;/strong&gt; - sharing what I’ve learned at conferences around the world, from NDC and Techorama to DevSum and Aspire Conf&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt; - organizing DevOps Days Tampa Bay, active with Tampa Devs and multiple local meetups, mentoring, and contributing to open source&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing&lt;/strong&gt; - more blog posts, more samples, more sharing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Does Principal Mean at Microsoft
&lt;/h2&gt;

&lt;p&gt;For those curious about what this level means at Microsoft, I wanted to share some context. At Microsoft, engineering levels form a career ladder, and the jump from Senior to Principal represents a meaningful shift in scope, impact, and expectations.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Senior Software Engineer&lt;/strong&gt; (levels 63-64) typically executes complex features, leads projects within a single team, designs components, reviews code, and mentors team members. The focus is largely on the &lt;em&gt;how&lt;/em&gt; - delivering high-quality work within established frameworks.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Principal Software Engineer&lt;/strong&gt; (levels 65-67) operates differently. The role shifts toward strategic technical leadership - defining architecture across multiple teams, solving organization-wide problems, and aligning technology decisions with business goals. The focus moves to the &lt;em&gt;why&lt;/em&gt; - long-term strategy, navigating high ambiguity, and innovating beyond existing solutions.&lt;/p&gt;

&lt;p&gt;Some of the key differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope of Impact&lt;/strong&gt; - Senior engineers own projects within a team. Principals influence entire organizations, divisions, or large-scale technical strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical Focus&lt;/strong&gt; - Principals handle high ambiguity and innovate beyond existing solutions. Seniors work within established frameworks to deliver features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leadership&lt;/strong&gt; - Principals guide technical direction, manage stakeholders, and mentor other senior engineers. Seniors focus more on day-to-day implementation and mentoring junior staff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For me, this promotion reflects the way I’ve been working - constantly sharing knowledge, educating the team, and helping set technical direction. Whether it’s building alignment across orgs and teams, driving architecture decisions, or connecting people and ideas across organizational boundaries, the Principal role formalizes what I believe great engineering looks like at scale.&lt;/p&gt;

&lt;p&gt;At its core, it comes down to a &lt;strong&gt;growth mindset&lt;/strong&gt;. The willingness to step into ambiguity, learn continuously, and lift others up along the way. That’s what got me here, and it’s what I’ll keep doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gratitude
&lt;/h2&gt;

&lt;p&gt;None of this happens alone. I’m grateful for the incredible teammates and mentors I’ve had on both teams, the managers who championed my growth, and the community that keeps pushing me to learn and share. And of course, my family - who support all the late nights, conference travel, and the constant tinkering.&lt;/p&gt;

&lt;p&gt;If you’re on your own career journey, here’s what I’d say: don’t be afraid to take the leap into unfamiliar territory. The skills you’ve built are more transferable than you think, and the best growth happens outside your comfort zone.&lt;/p&gt;

&lt;p&gt;Here’s to the next chapter. Let’s build something great.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>career</category>
      <category>microsoft</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Aspire CLI Part 3 - MCP for AI Coding Agents</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/aspire-cli-part-3-mcp-for-ai-coding-agents-5d8j</link>
      <guid>https://dev.to/chris_ayers/aspire-cli-part-3-mcp-for-ai-coding-agents-5d8j</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/posts/aspire-cli-getting-started/"&gt;Part 1&lt;/a&gt;, we covered creating and running Aspire apps. In &lt;a href="https://dev.to/posts/aspire-cli-part-2/"&gt;Part 2&lt;/a&gt;, we explored deployment and CI/CD. Now let’s look at one of Aspire’s most exciting features: MCP (Model Context Protocol) support for AI coding agents.&lt;/p&gt;

&lt;p&gt;Aspire MCP enables AI coding agents to understand and interact with your distributed applications. Your AI assistant can query resource states, debug with real-time logs, and investigate telemetry across your entire system — without you having to copy/paste anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Aspire MCP?
&lt;/h2&gt;

&lt;p&gt;The Aspire MCP server is a local &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; server that connects your AI coding assistants to your running distributed application. Starting with Aspire 9.0, MCP support has been available through manual dashboard configuration, and Aspire 13.1 added the &lt;code&gt;aspire mcp init&lt;/code&gt; command for automatic setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;aspire mcp&lt;/code&gt; commands are being renamed to &lt;code&gt;aspire agent&lt;/code&gt; in a future release. The generated configs already use &lt;code&gt;aspire agent mcp&lt;/code&gt;. See the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli/aspire-mcp" rel="noopener noreferrer"&gt;MCP sample&lt;/a&gt; for a complete working example with Postgres, Redis, and pre-configured AI agent support.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It bridges the gap between your running distributed application and AI coding assistants like GitHub Copilot, Claude Code, Cursor, and OpenAI Codex.&lt;/p&gt;

&lt;p&gt;With Aspire MCP, your AI assistant can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query resources&lt;/strong&gt; — Get resource states, source, endpoints, health status, and available commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug with logs&lt;/strong&gt; — Access real-time console logs from any resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Investigate telemetry&lt;/strong&gt; — Analyze structured logs and distributed traces across services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute commands&lt;/strong&gt; — Run resource commands directly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discover integrations&lt;/strong&gt; — Find and understand available Aspire hosting integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This transforms your AI assistant from a code generator into a development partner that understands your running system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Transport Modes
&lt;/h2&gt;

&lt;p&gt;Before setting up MCP, it helps to understand the two transport modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;STDIO&lt;/strong&gt; (CLI approach) — The AI assistant launches &lt;code&gt;aspire mcp start&lt;/code&gt; as a subprocess. No URL or API key needed. This is the recommended approach for Aspire 13.1+.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP&lt;/strong&gt; (manual/dashboard approach) — The Aspire dashboard exposes an HTTP endpoint with URL + API key authentication. This is the approach for Aspire 9.0-13.0 or when you need more control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1: Using the Aspire CLI (Recommended)
&lt;/h3&gt;

&lt;p&gt;Starting with Aspire 13.1, the &lt;code&gt;aspire mcp init&lt;/code&gt; command detects supported AI assistant environments and creates the appropriate configuration files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd your-aspire-project
aspire mcp init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command detects supported AI assistants in your environment and generates configuration files. Currently supported assistants for automatic setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual Studio Code&lt;/strong&gt; — Generates &lt;code&gt;.vscode/mcp.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt; — Generates Copilot CLI configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; — Generates &lt;code&gt;.claude/&lt;/code&gt; configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenCode&lt;/strong&gt; — Generates OpenCode configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The generated config uses STDIO transport, launching &lt;code&gt;aspire mcp start&lt;/code&gt; as a subprocess. For example, Visual Studio Code gets a &lt;code&gt;.vscode/mcp.json&lt;/code&gt; like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "servers": {
 "aspire": {
 "type": "stdio",
 "command": "aspire",
 "args": ["mcp", "start"]
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don’t already have an &lt;code&gt;AGENTS.md&lt;/code&gt; file in your project, one is created automatically with context about your Aspire application to help AI assistants understand your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Manual Configuration (Aspire 9.0-13.0)
&lt;/h3&gt;

&lt;p&gt;For older Aspire versions, or when you need more control:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run your Aspire app with &lt;code&gt;aspire run&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open the Aspire dashboard&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;MCP&lt;/strong&gt; button in the top right corner&lt;/li&gt;
&lt;li&gt;Use the displayed settings to configure your AI assistant&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The dashboard provides these settings for HTTP-based MCP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aspire MCP endpoint address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;http&lt;/code&gt; (streamable-HTTP MCP server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x-mcp-api-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HTTP header for securing access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This approach supports additional assistants including Visual Studio, Cursor, and OpenAI Codex. Consult your tool’s MCP documentation for configuration details.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Tools Available
&lt;/h2&gt;

&lt;p&gt;Once connected, your AI assistant gains access to several powerful tools:&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_resources&lt;/code&gt;&lt;/strong&gt; — Lists all resources with state, health status, source, endpoints, and commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;execute_resource_command&lt;/code&gt;&lt;/strong&gt; — Executes commands on specific resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Logging and Telemetry
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_console_logs&lt;/code&gt;&lt;/strong&gt; — Gets console logs for a resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_structured_logs&lt;/code&gt;&lt;/strong&gt; — Retrieves structured logs, optionally filtered by resource&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_traces&lt;/code&gt;&lt;/strong&gt; — Lists distributed traces, optionally filtered by resource name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_trace_structured_logs&lt;/code&gt;&lt;/strong&gt; — Gets structured logs for a specific trace&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integration Discovery
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_integrations&lt;/code&gt;&lt;/strong&gt; — Shows available Aspire hosting integrations with package IDs and versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;get_integration_docs&lt;/code&gt;&lt;/strong&gt; — Retrieves documentation for a specific integration package&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AppHost Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;list_apphosts&lt;/code&gt;&lt;/strong&gt; — Lists all AppHost connections, showing which are within the working directory scope and which are outside&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;select_apphost&lt;/code&gt;&lt;/strong&gt; — Switches context to a specific AppHost&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example Prompts
&lt;/h2&gt;

&lt;p&gt;After configuring MCP, try these prompts with your AI assistant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;“Are all my resources running?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“Show me the last 50 log entries from the API service”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“Analyze HTTP request performance for the webfrontend”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“What traces show errors in the last 5 minutes?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“Restart unhealthy resources”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“What Aspire integrations are available for Redis?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Excluding Resources from MCP
&lt;/h2&gt;

&lt;p&gt;You may have resources with sensitive data that shouldn’t be exposed to AI assistants. Annotate them in your AppHost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = DistributedApplication.CreateBuilder(args);

var apiservice = builder.AddProject&amp;lt;Projects.Api&amp;gt;("api")
 .ExcludeFromMcp(); // This resource hidden from MCP

builder.AddProject&amp;lt;Projects.Web&amp;gt;("web")
 .WithReference(apiservice);

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This excludes the resource &lt;strong&gt;and its associated telemetry&lt;/strong&gt; from all MCP results.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Example
&lt;/h2&gt;

&lt;p&gt;Consider a polyglot application using the Aspire CLI with AI-assisted development. Here’s a sample AppHost from the &lt;a href="https://github.com/davidfowl/aspire-13-samples" rel="noopener noreferrer"&gt;Aspire 13 samples&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#:package Aspire.Hosting.OpenAI@13.0.0
#:package Aspire.Hosting.Python@13.0.0
#:package Aspire.Hosting.Docker@13-*
#:sdk Aspire.AppHost.Sdk@13.0.0

var builder = DistributedApplication.CreateBuilder(args);

builder.AddDockerComposeEnvironment("dc");

var openai = builder.AddOpenAI("openai");

builder.AddUvicornApp("ai-agent", "./agent", "main:app")
 .WithUv()
 .WithExternalHttpEndpoints()
 .WithEnvironment("OPENAI_API_KEY", openai.Resource.Key);

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running &lt;code&gt;aspire mcp init&lt;/code&gt; and &lt;code&gt;aspire run&lt;/code&gt;, you can ask your AI assistant things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;“Is the ai-agent resource healthy?”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“Show me any errors in the agent logs”&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;“What OpenTelemetry traces have the longest duration?”&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI sees the Python service, the OpenAI connection, health check status, logs, and traces — all in real time. No more copying log output into chat windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported AI Assistants
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Assistant&lt;/th&gt;
&lt;th&gt;&lt;code&gt;aspire mcp init&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Manual (Dashboard)&lt;/th&gt;
&lt;th&gt;Docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Visual Studio Code Copilot&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers" rel="noopener noreferrer"&gt;MCP servers&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot CLI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.github.com/copilot/how-tos/use-copilot-agents/use-copilot-cli#add-an-mcp-server" rel="noopener noreferrer"&gt;Add MCP server&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.claude.com/en/docs/claude-code/mcp" rel="noopener noreferrer"&gt;MCP configuration&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenCode&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual Studio&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://learn.microsoft.com/visualstudio/ide/mcp-servers" rel="noopener noreferrer"&gt;MCP configuration&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cursor.com/docs/context/mcp#installing-mcp-servers" rel="noopener noreferrer"&gt;Installing MCP servers&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI Codex&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;&lt;a href="https://developers.openai.com/codex/mcp/" rel="noopener noreferrer"&gt;MCP setup&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Securing the API Key
&lt;/h2&gt;

&lt;p&gt;When using the manual/HTTP configuration, the &lt;code&gt;x-mcp-api-key&lt;/code&gt; secures access to MCP. Your AI assistant needs access to this key — use your tool’s secure storage to avoid committing it to source control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Visual Studio Code Example&lt;/strong&gt; — Use &lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers#_input-variables-for-sensitive-data" rel="noopener noreferrer"&gt;input variables&lt;/a&gt; to prompt for the key at connection time rather than hardcoding it in &lt;code&gt;mcp.json&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If you’re using &lt;code&gt;aspire mcp init&lt;/code&gt; (STDIO transport), there’s no API key to manage — authentication is handled by the subprocess communication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Self-Signed Certificate Issues
&lt;/h3&gt;

&lt;p&gt;Some AI assistants don’t support self-signed HTTPS certificates. If you encounter connection issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you use the &lt;code&gt;http&lt;/code&gt; launch profile, you’re already set&lt;/li&gt;
&lt;li&gt;For HTTPS, configure just the MCP endpoint to use HTTP in &lt;code&gt;launchSettings.json&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "profiles": {
 "https": {
 "environmentVariables": {
 "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:16036",
 "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
 }
 }
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Caution&lt;/strong&gt; : This removes transport security from MCP communication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Data Size Limits
&lt;/h3&gt;

&lt;p&gt;AI models have context limits. Aspire MCP automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Truncates large data fields (like exception stack traces)&lt;/li&gt;
&lt;li&gt;Omits older items from large telemetry collections&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Traditional AI coding assistants work with static code. Aspire MCP connects them to your &lt;strong&gt;running system&lt;/strong&gt;. This enables scenarios like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Why is the checkout service slow?”&lt;/strong&gt; - The AI can analyze traces and identify the bottleneck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Debug this 500 error”&lt;/strong&gt; - The AI accesses structured logs to find the root cause&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Is my database connection healthy?”&lt;/strong&gt; - The AI checks resource health status directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking you to describe your system, the AI can observe it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/get-started/configure-mcp/" rel="noopener noreferrer"&gt;Configure MCP Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/dashboard/mcp-server/" rel="noopener noreferrer"&gt;Aspire MCP Server Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/reference/cli/commands/aspire-mcp-init/" rel="noopener noreferrer"&gt;aspire mcp init Command Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/reference/cli/commands/aspire-mcp-start/" rel="noopener noreferrer"&gt;aspire mcp start Command Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/aspire/issues?q=is%3Aopen+label%3Amcp" rel="noopener noreferrer"&gt;Open MCP Issues on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli/aspire-mcp" rel="noopener noreferrer"&gt;Blog Post MCP Sample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Aspire MCP represents a shift in how we interact with distributed applications during development. By giving AI assistants real-time access to your running system, you unlock debugging and development workflows that weren’t possible before.&lt;/p&gt;

&lt;p&gt;Try &lt;code&gt;aspire mcp init&lt;/code&gt; in your next project and see how it transforms your AI-assisted development experience.&lt;/p&gt;

&lt;p&gt;Until next time, happy Aspiring!&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/aspire-cli-getting-started/"&gt;Getting Started with the Aspire CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/aspire-cli-part-2/"&gt;Aspire CLI Part 2 - Deployment and Pipelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>dotnet</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Aspire CLI Part 2 - Deployment and Pipelines</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/aspire-cli-part-2-deployment-and-pipelines-2j75</link>
      <guid>https://dev.to/chris_ayers/aspire-cli-part-2-deployment-and-pipelines-2j75</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/posts/aspire-cli-getting-started/"&gt;Part 1&lt;/a&gt;, we covered the basics of the Aspire CLI: creating projects with &lt;code&gt;aspire new&lt;/code&gt;, adding Aspire to existing apps with &lt;code&gt;aspire init&lt;/code&gt;, running with &lt;code&gt;aspire run&lt;/code&gt;, and managing integrations with &lt;code&gt;aspire add&lt;/code&gt; and &lt;code&gt;aspire update&lt;/code&gt;. Now let’s dive into deployment and CI/CD pipelines.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Prerequisite:&lt;/strong&gt; Aspire 13 requires .NET SDK 10.0.100 or later. Make sure you have it installed before using the commands in this post.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Publish and Deploy Model
&lt;/h2&gt;

&lt;p&gt;Aspire separates deployment into two distinct phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; (&lt;code&gt;aspire publish&lt;/code&gt;) — Generates intermediate, parameterized deployment artifacts (Compose files, Kubernetes manifests, Bicep templates, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; (&lt;code&gt;aspire deploy&lt;/code&gt;) — Resolves parameters and applies those artifacts to a target environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This separation is intentional. Published assets contain &lt;strong&gt;placeholders&lt;/strong&gt; instead of concrete values — secrets and environment-specific configuration are injected later at deploy time. This keeps sensitive data out of your artifacts and enables the same published output to target multiple environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;aspire publish&lt;/code&gt; - Generate Deployment Artifacts
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;aspire publish&lt;/code&gt; command generates deployment artifacts based on the &lt;strong&gt;compute environments&lt;/strong&gt; configured in your AppHost. A compute environment represents a target platform and determines what gets generated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire publish -o ./artifacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output depends on which hosting integration packages you’ve added:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;NuGet Package&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Publish&lt;/th&gt;
&lt;th&gt;Deploy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Aspire.Hosting.Docker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;🧪 Preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Aspire.Hosting.Kubernetes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;🧪 Preview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Aspire.Hosting.Azure.AppContainers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Azure Container Apps&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes (Preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Aspire.Hosting.Azure.AppService&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Azure App Service&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes (Preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If no integration supports publishing, &lt;code&gt;aspire publish&lt;/code&gt; will tell you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No resources in the distributed application model support publishing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parameterized Output
&lt;/h3&gt;

&lt;p&gt;Published artifacts contain placeholders rather than concrete values. For example, a Docker Compose publish might generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
 pg:
 image: "docker.io/library/postgres:17.2"
 environment:
 POSTGRES_PASSWORD: "${PG_PASSWORD}"
 ports:
 - "8000:5432"
 api:
 image: "${API_IMAGE}"
 environment:
 ConnectionStrings__db: "Host=pg;Port=5432;Username=postgres;Password=${PG_PASSWORD};Database=db"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice &lt;code&gt;${PG_PASSWORD}&lt;/code&gt; and &lt;code&gt;${API_IMAGE}&lt;/code&gt; are &lt;strong&gt;not resolved&lt;/strong&gt; during publish. You supply their values at deploy time — through environment variables, &lt;code&gt;.env&lt;/code&gt; files, or CI/CD pipeline secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Compose Example
&lt;/h3&gt;

&lt;p&gt;The most common pattern from the &lt;a href="https://github.com/davidfowl/aspire-13-samples" rel="noopener noreferrer"&gt;Aspire samples&lt;/a&gt; uses Docker Compose as the compute environment. See the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli/aspire-docker-compose" rel="noopener noreferrer"&gt;Docker Compose sample&lt;/a&gt; for a complete working example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#:package Aspire.Hosting.Docker@13-*
#:sdk Aspire.AppHost.Sdk@13.0.0

var builder = DistributedApplication.CreateBuilder(args);

builder.AddDockerComposeEnvironment("dc");

var postgres = builder.AddPostgres("postgres")
 .WithDataVolume()
 .WithPgAdmin();
var db = postgres.AddDatabase("db");

builder.AddCSharpApp("api", "./api")
 .WithHttpHealthCheck("/health")
 .WithExternalHttpEndpoints()
 .WaitFor(db)
 .WithReference(db);

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the standard workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire run # Run locally
aspire publish -o ./artifacts # Generate Docker Compose files
aspire deploy # Deploy to Docker Compose
aspire do docker-compose-down-dc # Tear down the deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Azure Container Apps Example
&lt;/h3&gt;

&lt;p&gt;For Azure, add the Azure Container Apps environment. See the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli/aspire-container-apps" rel="noopener noreferrer"&gt;Azure Container Apps sample&lt;/a&gt; for a complete working example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#:package Aspire.Hosting.Azure.AppContainers@13.0.0
#:package Aspire.Hosting.Azure.Storage@13.0.0
#:sdk Aspire.AppHost.Sdk@13.0.0

var builder = DistributedApplication.CreateBuilder(args);

builder.AddAzureContainerAppEnvironment("env");

var storage = builder.AddAzureStorage("storage")
 .RunAsEmulator();

var blobs = storage.AddBlobContainer("images");

builder.AddProject&amp;lt;Projects.Api&amp;gt;("api")
 .WithExternalHttpEndpoints()
 .WithReference(blobs);

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publishing this generates &lt;strong&gt;Bicep templates&lt;/strong&gt; that you can review, customize, and deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire publish -o ./azure-artifacts # Generates Bicep files
aspire deploy # Deploys to Azure Container Apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Kubernetes Example
&lt;/h3&gt;

&lt;p&gt;For Kubernetes, add the hosting package and configure a compute environment. See the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli/aspire-kubernetes" rel="noopener noreferrer"&gt;Kubernetes sample&lt;/a&gt; for a complete working example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = DistributedApplication.CreateBuilder(args);

var k8s = builder.AddKubernetesEnvironment("k8s");

var api = builder.AddProject&amp;lt;Projects.Api&amp;gt;("api")
 .WithExternalHttpEndpoints();

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate and deploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Generate Kubernetes manifests
aspire publish -o ./k8s-output

# Apply with kubectl or Helm
kubectl apply -f ./k8s-output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multiple Compute Environments
&lt;/h3&gt;

&lt;p&gt;If you add multiple compute environments, Aspire needs to know which resource goes where. Use &lt;code&gt;WithComputeEnvironment&lt;/code&gt; to disambiguate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var k8s = builder.AddKubernetesEnvironment("k8s-env");
var compose = builder.AddDockerComposeEnvironment("docker-env");

builder.AddProject&amp;lt;Projects.Frontend&amp;gt;("frontend")
 .WithComputeEnvironment(k8s);

builder.AddProject&amp;lt;Projects.Backend&amp;gt;("backend")
 .WithComputeEnvironment(compose);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, Aspire throws an ambiguous environment exception at publish time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;aspire deploy&lt;/code&gt; - Deploy to a Target
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;aspire deploy&lt;/code&gt; command resolves parameters and applies published artifacts to a target environment. This command is in &lt;strong&gt;preview&lt;/strong&gt; and under active development.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The deploy command may change as it matures. Keep an eye on the &lt;a href="https://devblogs.microsoft.com/dotnet" rel="noopener noreferrer"&gt;.NET Blog&lt;/a&gt; and the &lt;a href="https://aspire.dev/deployment/overview/" rel="noopener noreferrer"&gt;Aspire deployment docs&lt;/a&gt; for updates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What happens depends on your compute environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docker Compose&lt;/strong&gt; — Builds images, resolves variables, runs &lt;code&gt;docker compose up&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Container Apps&lt;/strong&gt; — Provisions Azure resources, builds and pushes container images, deploys apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes&lt;/strong&gt; — Generates manifests and applies them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Docker Compose deployments, the workflow from the official samples is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire run # Run locally
aspire deploy # Deploy to Docker Compose
aspire do docker-compose-down-dc # Teardown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Azure deployments, &lt;code&gt;aspire deploy&lt;/code&gt; prompts for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Azure sign-in&lt;/strong&gt; and subscription selection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource group&lt;/strong&gt; creation or selection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt; for Azure resources&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The command then provisions infrastructure, builds containers, pushes to ACR, and deploys — all in one step.&lt;/p&gt;

&lt;p&gt;For non-interactive deployment (CI/CD), set these environment variables to skip the prompts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Azure__SubscriptionId=&amp;lt;your-subscription-id&amp;gt;
Azure__Location=&amp;lt;azure-region&amp;gt;
Azure__ResourceGroup=&amp;lt;resource-group-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;aspire do&lt;/code&gt; - Pipeline Automation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;aspire do&lt;/code&gt; command executes pipeline steps defined by hosting integrations. Use &lt;code&gt;aspire do diagnostics&lt;/code&gt; to discover what steps are available and their dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# List available pipeline steps
aspire do diagnostics

# Tear down a Docker Compose deployment
aspire do docker-compose-down-dc

# The naming convention is: docker-compose-down-{environment-name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well-known pipeline steps include &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;publish&lt;/code&gt;, and &lt;code&gt;deploy&lt;/code&gt;. Resources can contribute custom steps — for example, Docker Compose adds teardown steps. This command is particularly useful in CI/CD pipelines and for managing environment lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;aspire exec&lt;/code&gt; - Run Commands in Resource Context
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;aspire exec&lt;/code&gt; command runs commands in the context of a specific resource with the correct connection strings and environment variables. This command is disabled by default — enable it first. See the &lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli/aspire-exec" rel="noopener noreferrer"&gt;exec sample&lt;/a&gt; for a complete working example with Postgres and Redis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Enable the exec feature
aspire config set features.execCommandEnabled true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use the &lt;code&gt;--resource&lt;/code&gt; (or &lt;code&gt;-r&lt;/code&gt;) flag to specify the target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Run EF Core migrations
aspire exec --resource mydb -- dotnet ef database update

# Open an interactive shell in a container
aspire exec --resource redis -- redis-cli

# Start a dependency and then run against it
aspire exec --start-resource mydb -- dotnet ef migrations add Init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--start-resource&lt;/code&gt; (or &lt;code&gt;-s&lt;/code&gt;) flag is useful when you need to start a resource (and its dependencies) before running a command against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Developer CLI (azd) Integration
&lt;/h2&gt;

&lt;p&gt;For production Azure deployments, the &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/" rel="noopener noreferrer"&gt;Azure Developer CLI&lt;/a&gt; (&lt;code&gt;azd&lt;/code&gt;) has first-class Aspire support and is the more mature deployment path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Initialize azd in your project directory
azd init

# Provision infrastructure and deploy in one command
azd up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During &lt;code&gt;azd init&lt;/code&gt;, you’ll:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select which services to expose to the internet&lt;/li&gt;
&lt;li&gt;Name your environment (e.g., &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Choose your Azure subscription and location&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;code&gt;azd up&lt;/code&gt; command handles the full lifecycle: &lt;code&gt;azd package&lt;/code&gt; → &lt;code&gt;azd provision&lt;/code&gt; → &lt;code&gt;azd deploy&lt;/code&gt;. Projects are packaged into containers, Azure resources are provisioned via Bicep, and containers are pushed to Azure Container Registry and deployed to Container Apps.&lt;/p&gt;

&lt;p&gt;Generated files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;azure.yaml&lt;/code&gt; — Maps Aspire AppHost services to Azure resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.azure/config.json&lt;/code&gt; — Active environment configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.azure/{env}/.env&lt;/code&gt; — Environment-specific overrides&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.azure/{env}/config.json&lt;/code&gt; — Public endpoint configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Using azd (Recommended for Azure)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy Aspire App

on:
 push:
 branches: [main]

jobs:
 deploy:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4

 - name: Setup .NET
 uses: actions/setup-dotnet@v4
 with:
 dotnet-version: '10.0.x'

 - name: Install azd
 uses: Azure/setup-azd@v2

 - name: Log in to Azure
 uses: azure/login@v2
 with:
 creds: ${{ secrets.AZURE_CREDENTIALS }}

 - name: Provision and Deploy
 run: azd up --no-prompt
 env:
 AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
 AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
 AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Aspire CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy with Aspire CLI

on:
 push:
 branches: [main]

jobs:
 deploy:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v4

 - name: Setup .NET
 uses: actions/setup-dotnet@v4
 with:
 dotnet-version: '10.0.x'

 - name: Install Aspire CLI
 run: curl -fsSL https://aspire.dev/install.sh | bash

 - name: Publish artifacts
 run: aspire publish -o ./artifacts
 working-directory: ./src/MyApp.AppHost

 - name: Deploy with Docker Compose
 run: |
 cd ./artifacts
 docker compose up --build -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Legacy Manifest Format
&lt;/h2&gt;

&lt;p&gt;Starting with Aspire 9.2, the single deployment manifest is being phased out in favor of the &lt;code&gt;aspire publish&lt;/code&gt; / &lt;code&gt;aspire deploy&lt;/code&gt; model with hosting integration extensibility. The legacy manifest is still available for debugging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire do publish-manifest --output-path ./diagnostics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a manifest snapshot for inspecting resource graphs and troubleshooting, but it’s not the primary deployment path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/deployment/overview/" rel="noopener noreferrer"&gt;Publishing and Deployment Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/reference/cli/overview/" rel="noopener noreferrer"&gt;Aspire CLI Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/deployment/azure/aca-deployment-aspire-cli/" rel="noopener noreferrer"&gt;Deploy to Azure Container Apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/" rel="noopener noreferrer"&gt;Azure Developer CLI with Aspire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/davidfowl/aspire-13-samples" rel="noopener noreferrer"&gt;Aspire Samples (davidfowl)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/aspire-samples" rel="noopener noreferrer"&gt;Official Aspire Samples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli" rel="noopener noreferrer"&gt;Blog Post Samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The publish/deploy model gives you flexibility: publish generates parameterized artifacts, and deploy resolves values and applies them. Whether you’re targeting Docker Compose for local staging, Kubernetes for container orchestration, or Azure Container Apps for managed hosting, the workflow is consistent.&lt;/p&gt;

&lt;p&gt;For production Azure deployments, I recommend &lt;code&gt;azd&lt;/code&gt; for its mature infrastructure-as-code capabilities. For Docker Compose and local deployment workflows, &lt;code&gt;aspire deploy&lt;/code&gt; is increasingly capable as it matures.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/posts/aspire-cli-part-3-mcp/"&gt;Part 3&lt;/a&gt;, we explore one of Aspire’s most exciting features: MCP (Model Context Protocol) integration, which lets AI coding agents like GitHub Copilot and Claude understand and interact with your running Aspire applications.&lt;/p&gt;

&lt;p&gt;Until next time, happy Aspiring!&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Posts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/aspire-cli-getting-started/"&gt;Getting Started with the Aspire CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/posts/aspire-cli-part-3-mcp/"&gt;Aspire CLI Part 3 - MCP for AI Coding Agents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>cli</category>
      <category>devops</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Migrating from Jekyll to Hugo Part 3: Deployment and Lessons Learned</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sun, 25 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/migrating-from-jekyll-to-hugo-part-3-deployment-and-lessons-learned-3ojc</link>
      <guid>https://dev.to/chris_ayers/migrating-from-jekyll-to-hugo-part-3-deployment-and-lessons-learned-3ojc</guid>
      <description>&lt;p&gt;In the final part of this series, I cover deploying Hugo to GitHub Pages and share the challenges I encountered.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;Here’s the workflow I use to deploy Hugo to GitHub Pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy Hugo site to Pages

on:
 push:
 branches: [main]
 workflow_dispatch:

permissions:
 contents: read

concurrency:
 group: "pages"
 cancel-in-progress: true

jobs:
 build:
 runs-on: ubuntu-latest
 permissions:
 contents: read
 pages: write
 id-token: write
 env:
 HUGO_VERSION: 0.154.5
 steps:
 - name: Checkout
 uses: actions/checkout@8e8c483db84b4bee # v6.0.2
 with:
 fetch-depth: 0
 persist-credentials: false
 submodules: recursive

 - name: Setup Hugo
 uses: peaceiris/actions-hugo@75d2e847 # v3.0.0
 with:
 hugo-version: ${{ env.HUGO_VERSION }}
 extended: true

 - name: Setup Pages
 id: pages
 uses: actions/configure-pages@983d7736 # v5.0.0

 - name: Cache Hugo resources
 uses: actions/cache@8b402f58 # v5.0.3
 with:
 path: |
 ${{ runner.temp }}/hugo_cache
 resources/_gen
 key: hugo-${{ runner.os }}-${{ hashFiles('content/ **', 'config/**', 'assets/**') }}
 restore-keys: |
 hugo-${{ runner.os }}-

 - name: Build with Hugo
 env:
 HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
 HUGO_ENVIRONMENT: production
 TZ: America/New_York
 run: |
 hugo \
 --gc \
 --minify \
 --baseURL "${{ steps.pages.outputs.base_url }}/"

 - name: Upload artifact
 uses: actions/upload-pages-artifact@7b1f4a76 # v4.0.0
 with:
 path: ./public

 deploy:
 needs: build
 runs-on: ubuntu-latest
 permissions:
 pages: write
 id-token: write
 environment:
 name: github-pages
 url: ${{ steps.deployment.outputs.page_url }}
 steps:
 - name: Deploy to GitHub Pages
 id: deployment
 uses: actions/deploy-pages@d6db9016 # v4.0.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SHA-pinned actions&lt;/strong&gt; - Every action is pinned to a commit SHA, not a mutable tag — critical for supply chain security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped permissions&lt;/strong&gt; - Minimal permissions declared per-job, not at the workflow level&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;submodules: recursive&lt;/code&gt;&lt;/strong&gt; - Required for the theme submodule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;fetch-depth: 0&lt;/code&gt;&lt;/strong&gt; - Needed for &lt;code&gt;.GitInfo&lt;/code&gt; and &lt;code&gt;.Lastmod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;persist-credentials: false&lt;/code&gt;&lt;/strong&gt; - Security best practice for checkout&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinned Hugo version&lt;/strong&gt; - &lt;code&gt;HUGO_VERSION&lt;/code&gt; env var ensures reproducible builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt; - Both &lt;code&gt;hugo_cache&lt;/code&gt; and &lt;code&gt;resources/_gen&lt;/code&gt; are cached to speed up builds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--gc --minify&lt;/code&gt;&lt;/strong&gt; - Clean up unused cache entries and optimize output&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Linting with Super-Linter
&lt;/h2&gt;

&lt;p&gt;In addition to the deploy workflow, I added a &lt;a href="https://github.com/super-linter/super-linter" rel="noopener noreferrer"&gt;Super-Linter&lt;/a&gt; workflow that runs on every PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Super-Linter

on:
 pull_request: null

concurrency:
 group: ${{ github.workflow }}-${{ github.ref }}
 cancel-in-progress: true

jobs:
 build:
 name: Lint
 runs-on: ubuntu-latest
 permissions:
 contents: read
 packages: read
 statuses: write
 steps:
 - name: Checkout code
 uses: actions/checkout@8e8c483db84b4bee # v6.0.1
 with:
 fetch-depth: 0
 persist-credentials: false

 - name: Super-linter
 uses: super-linter/super-linter@d5b0a2ab # v8.3.2
 env:
 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 VALIDATE_ALL_CODEBASE: 'false'
 # Auto-fix formatting on PR
 FIX_CSS_PRETTIER: 'true'
 FIX_HTML_PRETTIER: 'true'
 FIX_JSON_PRETTIER: 'true'
 FIX_MARKDOWN_PRETTIER: 'true'
 FIX_YAML_PRETTIER: 'true'
 # Disable linters that don't apply
 VALIDATE_JSCPD: 'false'
 VALIDATE_PYTHON: 'false'
 # Use repo markdownlint config
 MARKDOWN_CONFIG_FILE: '.markdownlint.yml'
 # Don't lint the theme submodule
 FILTER_REGEX_EXCLUDE: 'themes/.*'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches markdown issues, YAML errors, and formatting problems before they hit &lt;code&gt;main&lt;/code&gt;. The &lt;code&gt;FIX_*&lt;/code&gt; options automatically commit formatting corrections back to the PR branch, which saves a lot of manual cleanup. I exclude the &lt;code&gt;themes/&lt;/code&gt; directory since that’s third-party code.&lt;/p&gt;

&lt;p&gt;I also keep linting config files in the repo root:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.markdownlint.yml&lt;/code&gt; - Disables rules like &lt;code&gt;MD013&lt;/code&gt; (line length) and &lt;code&gt;MD033&lt;/code&gt; (inline HTML — needed for Hugo shortcodes)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.yaml-lint.yml&lt;/code&gt; - Warns on formatting issues without blocking&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.textlintrc&lt;/code&gt; - Terminology checks&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.eslintrc.yml&lt;/code&gt; - JavaScript linting for any custom scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: Preserving URLs
&lt;/h3&gt;

&lt;p&gt;Jekyll and Hugo generate different URL structures. To avoid breaking existing links, I used Hugo aliases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# In front matter
aliases:
 - /category/development/my-old-post-url/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For bulk redirects, I created &lt;code&gt;static/_redirects&lt;/code&gt; for Netlify-style redirects (works with some hosts).&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: Theme Version Compatibility
&lt;/h3&gt;

&lt;p&gt;I hit this warning early on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARN Module "blowfish" is not compatible with this Hugo version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt; : Pin both Hugo and theme versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# In GitHub Actions
env:
 HUGO_VERSION: 0.154.5

# Pin theme to specific tag
cd themes/blowfish
git checkout v2.97.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Challenge 3: Date Formatting
&lt;/h3&gt;

&lt;p&gt;Hugo uses Go’s reference time format. This tripped me up — Go doesn’t use &lt;code&gt;YYYY-MM-DD&lt;/code&gt; style format strings. Instead, it uses a specific reference time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Go reference time: Mon Jan 2 15:04:05 MST 2006
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So in Hugo templates, you format dates like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{/* Long date */}}
{{ .Date.Format "January 2, 2006" }}
{{/* Output: January 25, 2026 */}}

{{/* ISO date */}}
{{ .Date.Format "2006-01-02" }}
{{/* Output: 2026-01-25 */}}

{{/* With time */}}
{{ .Date.Format "Jan 2, 2006 3:04 PM" }}
{{/* Output: Jan 25, 2026 12:00 AM */}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic is that every component of the format is a specific number: month=1, day=2, hour=3, minute=4, second=5, year=6, timezone=7 (MST). Once you internalize that, it clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 4: Custom Layouts
&lt;/h3&gt;

&lt;p&gt;Some Jekyll layouts needed recreation. Hugo’s template lookup order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;layouts/&amp;lt;type&amp;gt;/&amp;lt;layout&amp;gt;.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;layouts/_default/&amp;lt;layout&amp;gt;.html&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Theme equivalents&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I started by copying theme layouts to my &lt;code&gt;layouts/&lt;/code&gt; folder and customizing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 5: Split Config Files
&lt;/h3&gt;

&lt;p&gt;Hugo supports splitting configuration across multiple files. Rather than one monolithic &lt;code&gt;config.toml&lt;/code&gt;, I use a &lt;code&gt;config/_default/&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config/_default/
├── hugo.toml # Core site settings
├── languages.en.toml
├── markup.toml # Goldmark, syntax highlighting
├── menus.en.toml
├── module.toml
└── params.toml # Theme parameters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps things organized — especially as Blowfish has many configurable params. One thing that helped: setting &lt;code&gt;buildFuture = true&lt;/code&gt; in &lt;code&gt;hugo.toml&lt;/code&gt; so scheduled posts show up locally during development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 6: RSS Feed URLs
&lt;/h3&gt;

&lt;p&gt;Jekyll’s feed was at &lt;code&gt;/feed.xml&lt;/code&gt;, but Hugo defaults to &lt;code&gt;/index.xml&lt;/code&gt;. To avoid breaking existing subscribers, I configured Hugo to output both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# hugo.toml
[outputs]
 home = ["HTML", "RSS", "FEED", "JSON"]

# Legacy feed.xml for backward compatibility with Jekyll
[outputFormats.FEED]
 mediaType = "application/rss+xml"
 baseName = "feed"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The custom &lt;code&gt;FEED&lt;/code&gt; output format needs a matching template, so I copied the theme’s &lt;code&gt;rss.xml&lt;/code&gt; into my layouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp themes/blowfish/layouts/_default/rss.xml layouts/_default/feed.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both &lt;code&gt;/index.xml&lt;/code&gt; and &lt;code&gt;/feed.xml&lt;/code&gt; are generated — existing subscribers keep working, and Hugo’s default feed works too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for Your Migration
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start fresh&lt;/strong&gt; - Create new Hugo site, don’t convert in place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrate incrementally&lt;/strong&gt; - Move posts in batches, test as you go&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;hugo server -D&lt;/code&gt;&lt;/strong&gt; - Shows drafts with hot reload&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read theme docs&lt;/strong&gt; - Blowfish has excellent documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test all pages&lt;/strong&gt; - Especially taxonomy and archive pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check mobile&lt;/strong&gt; - Verify responsive design works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate feeds&lt;/strong&gt; - Test RSS/Atom with a feed reader&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before and After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Jekyll&lt;/th&gt;
&lt;th&gt;Hugo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build time&lt;/td&gt;
&lt;td&gt;30+ seconds&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Ruby, Bundler, gems&lt;/td&gt;
&lt;td&gt;Single binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hot reload&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theme options&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Extensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The migration took a weekend of focused work, but it was absolutely worth it. Hugo’s speed and flexibility have made maintaining this blog much more enjoyable.&lt;/p&gt;

&lt;p&gt;The key is taking it step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up Hugo with your chosen theme&lt;/li&gt;
&lt;li&gt;Migrate content in batches&lt;/li&gt;
&lt;li&gt;Fix shortcodes and assets&lt;/li&gt;
&lt;li&gt;Set up deployment&lt;/li&gt;
&lt;li&gt;Test thoroughly before switching DNS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’re considering the switch, I hope this series helps. Feel free to reach out with questions!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/documentation/" rel="noopener noreferrer"&gt;Hugo Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blowfish.page/" rel="noopener noreferrer"&gt;Blowfish Theme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/hosting-and-deployment/hosting-on-github/" rel="noopener noreferrer"&gt;Hugo on GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/tools/migrations/#jekyll" rel="noopener noreferrer"&gt;Jekyll to Hugo Migration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>github</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Migrating from Jekyll to Hugo Part 2: Content Migration</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/migrating-from-jekyll-to-hugo-part-2-content-migration-1aff</link>
      <guid>https://dev.to/chris_ayers/migrating-from-jekyll-to-hugo-part-2-content-migration-1aff</guid>
      <description>&lt;p&gt;In Part 1, I covered why I switched from Jekyll to Hugo. Now let's dive into the actual content migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Front Matter Conversion
&lt;/h2&gt;

&lt;p&gt;Most Jekyll posts work with minimal changes, but there are key differences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Jekyll&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;post&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Post"&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2024-01-15&lt;/span&gt;
&lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;mermaid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# Hugo&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Post"&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2024-01-15&lt;/span&gt;
&lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Development&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Docker&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Containers&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key changes I made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Removed &lt;code&gt;layout: post&lt;/code&gt;&lt;/strong&gt; - Hugo infers layout from content location&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Converted tags/categories to arrays&lt;/strong&gt; - YAML list format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standardized capitalization&lt;/strong&gt; - Consistent taxonomy naming&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Removed &lt;code&gt;mermaid: true&lt;/code&gt;&lt;/strong&gt; - Blowfish auto-detects mermaid shortcodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;{{&amp;lt; alert "lightbulb" &amp;gt;}}&lt;br&gt;
&lt;strong&gt;Tip:&lt;/strong&gt; Run &lt;code&gt;hugo server&lt;/code&gt; while migrating so you can preview each converted post immediately and catch front matter issues early.&lt;br&gt;
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;h2&gt;
  
  
  Shortcode Conversions
&lt;/h2&gt;

&lt;p&gt;Jekyll uses Liquid templates while Hugo has its own shortcode system.&lt;/p&gt;
&lt;h3&gt;
  
  
  Images and Figures
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Jekyll --&amp;gt;&lt;/span&gt;
{% include figure.html src="/images/photo.jpg" caption="My caption" %}

&lt;span class="c"&gt;&amp;lt;!-- Hugo --&amp;gt;&lt;/span&gt;
{{&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="err"&gt;src="/images/photo.jpg"&lt;/span&gt; &lt;span class="err"&gt;caption="My&lt;/span&gt; &lt;span class="err"&gt;caption"&lt;/span&gt; &lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here's the Hugo figure shortcode in action:&lt;/p&gt;

&lt;p&gt;{{&amp;lt; figure src="images/logos/hugo-logo.svg" alt="Hugo Logo" caption="The Hugo logo rendered with the figure shortcode" class="mx-auto" width="200" &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;
  
  
  Code Blocks
&lt;/h3&gt;

&lt;p&gt;Standard fenced code blocks work the same, but Hugo adds features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Hugo with line numbers --&amp;gt;&lt;/span&gt;
{{&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;highlight&lt;/span&gt; &lt;span class="err"&gt;go&lt;/span&gt; &lt;span class="err"&gt;"linenos=table,hl_lines=3"&lt;/span&gt; &lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;}}
func main() {
    fmt.Println("Hello")
    fmt.Println("Highlighted!")
}
{{&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;highlight&lt;/span&gt; &lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what that looks like rendered with Hugo's syntax highlighting and line numbers:&lt;/p&gt;

&lt;p&gt;{{&amp;lt; highlight go "linenos=table,hl_lines=3" &amp;gt;}}&lt;br&gt;
func main() {&lt;br&gt;
    fmt.Println("Hello")&lt;br&gt;
    fmt.Println("Highlighted!")&lt;br&gt;
}&lt;br&gt;
{{&amp;lt; /highlight &amp;gt;}}&lt;/p&gt;
&lt;h3&gt;
  
  
  Mermaid Diagrams
&lt;/h3&gt;

&lt;p&gt;This was a bigger change. Jekyll with the mermaid plugin uses fenced code blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;!-- Jekyll --&amp;gt;


```mermaid
graph TD
    A --&amp;gt; B
```




&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hugo with Blowfish requires the mermaid shortcode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&amp;lt;!-- Hugo --&amp;gt;
{{&amp;lt;/* mermaid */&amp;gt;}}
graph TD
    A --&amp;gt; B
{{&amp;lt;/* /mermaid */&amp;gt;}}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote a quick script to find and convert these across all posts. Here's what a real mermaid diagram looks like after migration:&lt;/p&gt;

&lt;p&gt;{{&amp;lt; mermaid &amp;gt;}}&lt;br&gt;
flowchart LR&lt;br&gt;
    A["Jekyll Post\n(.md + Liquid)"] --&amp;gt;|migrate| B["Hugo Post\n(.md + Shortcodes)"]&lt;br&gt;
    B --&amp;gt; C{"hugo build"}&lt;br&gt;
    C --&amp;gt; D["Static HTML"]&lt;br&gt;
    C --&amp;gt; E["Processed Images"]&lt;br&gt;
    C --&amp;gt; F["Minified CSS/JS"]&lt;br&gt;
{{&amp;lt; /mermaid &amp;gt;}}&lt;/p&gt;
&lt;h2&gt;
  
  
  Static Assets
&lt;/h2&gt;

&lt;p&gt;Jekyll and Hugo organize assets differently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll&lt;/th&gt;
&lt;th&gt;Hugo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;assets/images/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static/images/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_data/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;data/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_includes/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;layouts/partials/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For images referenced in posts, I kept paths like &lt;code&gt;/images/photo.jpg&lt;/code&gt; which maps to &lt;code&gt;static/images/photo.jpg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; alert &amp;gt;}}&lt;br&gt;
&lt;strong&gt;Watch out:&lt;/strong&gt; Hugo's &lt;code&gt;assets/&lt;/code&gt; folder is for files processed by Hugo Pipes (SCSS, image resizing, fingerprinting). Use &lt;code&gt;static/&lt;/code&gt; for files served as-is. Mixing them up leads to 404s.&lt;br&gt;
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;
&lt;h2&gt;
  
  
  Handling Excerpts
&lt;/h2&gt;

&lt;p&gt;Jekyll uses &lt;code&gt;excerpt_separator&lt;/code&gt; in config or &lt;code&gt;&amp;lt;!--more--&amp;gt;&lt;/code&gt; in posts. Hugo works the same way with &lt;code&gt;&amp;lt;!--more--&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Post"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

This appears in the summary.

&lt;span class="c"&gt;&amp;lt;!--more--&amp;gt;&lt;/span&gt;

This is the full content.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Taxonomy Cleanup
&lt;/h2&gt;

&lt;p&gt;I took the opportunity to consolidate tags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Merged similar tags (&lt;code&gt;vscode&lt;/code&gt; → &lt;code&gt;VSCode&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Standardized capitalization&lt;/li&gt;
&lt;li&gt;Removed unused categories&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bulk Migration Script
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"The best migration is the one you automate. Don't hand-edit 60 posts when a script can do it in seconds."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For 60+ posts, I used a simple PowerShell script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"content/posts/*.md"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Raw&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Remove layout: post&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layout: post\r?\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="c"&gt;# Remove mermaid: true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mermaid: true\r?\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;Set-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FullName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;{{&amp;lt; alert "circle-info" &amp;gt;}}&lt;br&gt;
&lt;strong&gt;Note:&lt;/strong&gt; The PowerShell script above covers the basics, but you may need additional passes for things like Liquid &lt;code&gt;raw&lt;/code&gt;/&lt;code&gt;endraw&lt;/code&gt; blocks or custom Jekyll includes. Test thoroughly!&lt;br&gt;
{{&amp;lt; /alert &amp;gt;}}&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;In Part 3, I'll cover deployment with GitHub Actions and the challenges I encountered along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/content-management/organization/" rel="noopener noreferrer"&gt;Hugo Content Organization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/content-management/shortcodes/" rel="noopener noreferrer"&gt;Hugo Shortcodes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blowfish.page/docs/shortcodes/" rel="noopener noreferrer"&gt;Blowfish Shortcodes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tooling</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>website</category>
    </item>
    <item>
      <title>Migrating from Jekyll to Hugo Part 1: Why I Made the Switch</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/migrating-from-jekyll-to-hugo-part-1-why-i-made-the-switch-3176</link>
      <guid>https://dev.to/chris_ayers/migrating-from-jekyll-to-hugo-part-1-why-i-made-the-switch-3176</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchris-ayers.com%2Fimages%2Flogos%2Fhugo-logo.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchris-ayers.com%2Fimages%2Flogos%2Fhugo-logo.svg" alt="Hugo Logo" width="27" height="7.0709979906229075"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After years of running this blog on Jekyll, I finally made the switch to Hugo. Here’s why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Made the Switch
&lt;/h2&gt;

&lt;p&gt;Static site generators have evolved a lot since Jekyll first popularized the idea of building blogs from Markdown. Jekyll served me well for a long time, but as my site grew and my workflow matured, a few pain points became impossible to ignore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build times&lt;/strong&gt; : As my site grew, Jekyll builds slowed to a crawl. Waiting 30+ seconds for a rebuild made local development feel sluggish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ruby dependencies&lt;/strong&gt; : Managing Ruby versions, gems, and Bundler across machines and CI environments was a recurring frustration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Theme flexibility&lt;/strong&gt; : I wanted a more modern design system, better dark mode support, and a theme ecosystem that felt alive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hugo promised faster builds, a single binary with no external dependencies, and a more modern templating system. Once I started experimenting, the difference was immediate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Hugo Is a Better Fit
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fast builds
&lt;/h3&gt;

&lt;p&gt;Hugo compiles entire sites in milliseconds, and the dev server hot reload feels instant. That alone dramatically improves the writing and editing experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  No dependency headaches
&lt;/h3&gt;

&lt;p&gt;Hugo is just one binary. No Ruby, no Bundler, no gem conflicts, no version juggling. It works the same on every machine and every CI pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  A more powerful templating system
&lt;/h3&gt;

&lt;p&gt;Hugo’s Go-based templating is far more flexible than Liquid. It supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rich built-in functions&lt;/li&gt;
&lt;li&gt;Complex content structures&lt;/li&gt;
&lt;li&gt;Custom taxonomies&lt;/li&gt;
&lt;li&gt;Shortcodes&lt;/li&gt;
&lt;li&gt;Image processing&lt;/li&gt;
&lt;li&gt;Multilingual content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many things that require plugins in Jekyll are built directly into Hugo.&lt;/p&gt;

&lt;h3&gt;
  
  
  A modern ecosystem
&lt;/h3&gt;

&lt;p&gt;Hugo’s theme community is active, innovative, and built around modern tooling. This is where Blowfish really shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Theme
&lt;/h2&gt;

&lt;p&gt;I settled on the &lt;a href="https://blowfish.page/" rel="noopener noreferrer"&gt;Blowfish&lt;/a&gt; theme for several reasons. Honestly, it is one of the best examples of what Hugo makes possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7un8h9saj95qbkz2jwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb7un8h9saj95qbkz2jwx.png" alt="Blowfish illustration" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean, modern design
&lt;/h3&gt;

&lt;p&gt;Blowfish looks great out of the box, with thoughtful typography, spacing, and layout options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dark mode support
&lt;/h3&gt;

&lt;p&gt;Automatic dark and light mode, user-selectable themes, and customizable color palettes are all built in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature-rich components
&lt;/h3&gt;

&lt;p&gt;Blowfish includes components like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Callouts&lt;/li&gt;
&lt;li&gt;Cards&lt;/li&gt;
&lt;li&gt;Tabs&lt;/li&gt;
&lt;li&gt;Accordions&lt;/li&gt;
&lt;li&gt;Grids&lt;/li&gt;
&lt;li&gt;Footnotes&lt;/li&gt;
&lt;li&gt;Copy-to-clipboard code blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These let you build richer content without custom HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-in image optimization
&lt;/h3&gt;

&lt;p&gt;Thanks to Hugo’s image pipeline, Blowfish can automatically resize, crop, optimize, lazy-load, and serve responsive images. This is something Jekyll cannot match without external tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Taxonomies and structure
&lt;/h3&gt;

&lt;p&gt;Categories, tags, sections, menus, and breadcrumbs work cleanly and consistently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Active development and documentation
&lt;/h3&gt;

&lt;p&gt;Blowfish is well maintained, well documented, and constantly improving.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tailwind CSS for customization
&lt;/h3&gt;

&lt;p&gt;If you want to tweak the design, Tailwind makes it straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jekyll vs. Hugo (with Blowfish): A Practical Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Area&lt;/th&gt;
&lt;th&gt;Jekyll&lt;/th&gt;
&lt;th&gt;Hugo (with Blowfish)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build Speed&lt;/td&gt;
&lt;td&gt;Slow on larger sites; rebuilds often 20-60 seconds&lt;/td&gt;
&lt;td&gt;Extremely fast; rebuilds typically under 1 second&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Requires Ruby, Bundler, gems, and version management&lt;/td&gt;
&lt;td&gt;Single binary, no external dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Templating System&lt;/td&gt;
&lt;td&gt;Liquid (simple but limited)&lt;/td&gt;
&lt;td&gt;Go templates (powerful, flexible, feature-rich)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image Processing&lt;/td&gt;
&lt;td&gt;Not built in; requires external tools or plugins&lt;/td&gt;
&lt;td&gt;Native image pipeline: resize, crop, optimize, responsive images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theme Ecosystem&lt;/td&gt;
&lt;td&gt;Many themes, but many feel dated or unmaintained&lt;/td&gt;
&lt;td&gt;Modern themes with active development; Blowfish is a standout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dark Mode Support&lt;/td&gt;
&lt;td&gt;Theme-dependent; often limited&lt;/td&gt;
&lt;td&gt;Built-in automatic dark/light mode plus user theme switching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content Components&lt;/td&gt;
&lt;td&gt;Limited; requires plugins or custom HTML&lt;/td&gt;
&lt;td&gt;Blowfish includes cards, callouts, tabs, accordions, grids, and more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search&lt;/td&gt;
&lt;td&gt;Requires external JS libraries or plugins&lt;/td&gt;
&lt;td&gt;Blowfish includes fast, built-in client-side search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual Support&lt;/td&gt;
&lt;td&gt;Plugin-based, inconsistent&lt;/td&gt;
&lt;td&gt;First-class multilingual support built into Hugo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taxonomies&lt;/td&gt;
&lt;td&gt;Basic categories/tags&lt;/td&gt;
&lt;td&gt;Flexible taxonomies, sections, menus, breadcrumbs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asset Pipeline&lt;/td&gt;
&lt;td&gt;Basic; often requires plugins&lt;/td&gt;
&lt;td&gt;Built-in minification, fingerprinting, and processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customization&lt;/td&gt;
&lt;td&gt;Varies by theme; often requires manual CSS&lt;/td&gt;
&lt;td&gt;Blowfish uses Tailwind for easy, modern customization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;Good but plugin-heavy&lt;/td&gt;
&lt;td&gt;Excellent docs; Blowfish has strong theme documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Development&lt;/td&gt;
&lt;td&gt;Slower reloads; can feel laggy&lt;/td&gt;
&lt;td&gt;Instant hot reload; smooth editing experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;Slower builds; Ruby setup required&lt;/td&gt;
&lt;td&gt;Fast builds; no setup beyond Hugo binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Curve&lt;/td&gt;
&lt;td&gt;Easy to start, harder to extend&lt;/td&gt;
&lt;td&gt;Easy to start, powerful as you grow&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;After migrating, a few wins stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build time&lt;/strong&gt; : Dropped from 30+ seconds to under 1 second&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dependencies&lt;/strong&gt; : Just the Hugo binary, nothing else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better DX&lt;/strong&gt; : Hot reload is nearly instant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern design&lt;/strong&gt; : Blowfish looks great on all devices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More flexibility&lt;/strong&gt; : Shortcodes, components, and image processing make content creation easier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, the site feels faster, cleaner, and more maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;In Part 2, I’ll cover the actual migration process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Converting posts&lt;/li&gt;
&lt;li&gt;Fixing front matter&lt;/li&gt;
&lt;li&gt;Replacing Jekyll shortcodes&lt;/li&gt;
&lt;li&gt;Handling images and static assets&lt;/li&gt;
&lt;li&gt;Structuring content for Hugo’s taxonomy system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re considering making the switch yourself, the migration is easier than you might think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/documentation/" rel="noopener noreferrer"&gt;Hugo Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blowfish.page/" rel="noopener noreferrer"&gt;Blowfish Theme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/tools/migrations/#jekyll" rel="noopener noreferrer"&gt;Jekyll to Hugo Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devjournal</category>
      <category>performance</category>
      <category>webdev</category>
      <category>website</category>
    </item>
    <item>
      <title>Getting Started with the Aspire CLI - A Complete Guide</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/getting-started-with-the-aspire-cli-a-complete-guide-3b1e</link>
      <guid>https://dev.to/chris_ayers/getting-started-with-the-aspire-cli-a-complete-guide-3b1e</guid>
      <description>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://csadvent.christmas/" rel="noopener noreferrer"&gt;C# Advent Calendar 2025&lt;/a&gt;. Make sure to check out everyone else’s work when you’re done here&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Aspire CLI is a cross-platform tool for creating, managing, and running polyglot Aspire projects. This post covers the core commands you’ll use day-to-day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Aspire?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Aspire&lt;/strong&gt; is an opinionated, cloud-ready stack for building observable distributed applications. Think: a C# AppHost that models your topology, plus integrations and tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Polyglot by Design
&lt;/h3&gt;

&lt;p&gt;While Aspire started in the &lt;strong&gt;.NET&lt;/strong&gt; ecosystem, &lt;strong&gt;Aspire 13.0 made Python and JavaScript first-class citizens&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET projects&lt;/strong&gt; - ASP.NET Core APIs, Blazor apps, Worker Services, Azure Functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python applications&lt;/strong&gt; - Flask, FastAPI, Django, or any Python script&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript/Node.js apps&lt;/strong&gt; - Express, Next.js, or any Node.js application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers&lt;/strong&gt; - Any Docker image you need&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AppHost (orchestrator) is written in C#, but it can coordinate services written in any language.&lt;/p&gt;

&lt;p&gt;That said, &lt;strong&gt;.NET remains the sweet spot&lt;/strong&gt; for Aspire because you get the richest integration and tooling.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem Aspire Solves
&lt;/h3&gt;

&lt;p&gt;Coordinating multiple services usually means lots of config, hardcoded URLs, and fragile startup order. Aspire helps by giving you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt; - Model resources and dependencies in code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations&lt;/strong&gt; - Standard components for common infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling&lt;/strong&gt; - A dashboard + CLI to run and debug the system&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dashboards and Telemetry
&lt;/h3&gt;

&lt;p&gt;Aspire includes a local &lt;strong&gt;dashboard&lt;/strong&gt; that helps you understand what’s running: resource status, endpoints, logs, traces, and metrics.&lt;/p&gt;

&lt;p&gt;Telemetry is typically emitted via &lt;strong&gt;OpenTelemetry&lt;/strong&gt; (the ServiceDefaults project wires a lot of this up for .NET services). By default this is a local developer experience. Data only goes to an external backend if you configure an exporter to send it there.&lt;/p&gt;

&lt;h3&gt;
  
  
  The AppHost: Your Application’s Control Plane
&lt;/h3&gt;

&lt;p&gt;At the heart of every Aspire application is the &lt;strong&gt;AppHost&lt;/strong&gt;. It defines resources and dependencies (and can include health checks and external endpoints).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = DistributedApplication.CreateBuilder(args);

// TODO: Add resources here

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ServiceDefaults: Shared Configuration
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;ServiceDefaults&lt;/strong&gt; project is a shared library for observability and resilience (health checks, OpenTelemetry, logging, etc.).&lt;/p&gt;

&lt;p&gt;Each service references ServiceDefaults and calls two methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();

var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures consistent observability across all services without duplicating configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aspire vs Docker Compose
&lt;/h2&gt;

&lt;p&gt;If you’re currently using Docker Compose, here’s the mental model shift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Compose:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services:
 postgres:
 image: postgres:latest

 api:
 build: ./api
 depends_on:
 - postgres

 web:
 build: ./web
 depends_on:
 - api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aspire equivalent:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = DistributedApplication.CreateBuilder(args);

var postgres = builder.AddPostgres("postgres")
 .AddDatabase("mydb");

var api = builder.AddProject&amp;lt;Projects.Api&amp;gt;("api")
 .WithReference(postgres);

var web = builder.AddProject&amp;lt;Projects.Web&amp;gt;("web")
 .WithReference(api)
 .WaitFor(api);

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why Aspire often feels better:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less config glue&lt;/strong&gt; - Model topology in code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service discovery by default&lt;/strong&gt; - Fewer hardcoded URLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in dashboard&lt;/strong&gt; - Logs, traces, and metrics in one place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrations&lt;/strong&gt; - Common infra with sensible defaults&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polyglot&lt;/strong&gt; - C#, Python, JavaScript, containers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why the Aspire CLI?
&lt;/h2&gt;

&lt;p&gt;The Aspire CLI helps you scaffold, run, and evolve an Aspire application without a pile of scripts and config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you’ll use it for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create or Aspireify a solution&lt;/li&gt;
&lt;li&gt;Run the apphost and open the dashboard&lt;/li&gt;
&lt;li&gt;Add integrations and keep packages current&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aspire new&lt;/code&gt;&lt;/strong&gt; for greenfield projects with full scaffolding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aspire init&lt;/code&gt;&lt;/strong&gt; to add Aspire to existing solutions incrementally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aspire run&lt;/code&gt;&lt;/strong&gt; for development with the built-in dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aspire add&lt;/code&gt;&lt;/strong&gt; to easily integrate databases, caches, and queues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aspire update&lt;/code&gt;&lt;/strong&gt; to keep packages current&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before installing the Aspire CLI, ensure you have the following:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;.NET 8+ SDK&lt;/td&gt;
&lt;td&gt;Required for Aspire 13.0+. Run &lt;code&gt;dotnet --info&lt;/code&gt; to verify. The CLI needs the SDK even if your apps target .NET 8 or 9.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editor/IDE&lt;/td&gt;
&lt;td&gt;Visual Studio Code with C# Dev Kit and Aspire extensions, Visual Studio 2022 17.13+, or JetBrains Rider.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;td&gt;GitHub Codespaces or Dev Containers for cloud-based development.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Installing the Aspire CLI
&lt;/h2&gt;

&lt;p&gt;The install scripts download the CLI and add it to your PATH.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Install command&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows (PowerShell)&lt;/td&gt;
&lt;td&gt;`irm &lt;a href="https://aspire.dev/install.ps1" rel="noopener noreferrer"&gt;https://aspire.dev/install.ps1&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS and Linux (Bash)&lt;/td&gt;
&lt;td&gt;{% raw %}`curl -fsSL &lt;a href="https://aspire.dev/install.sh" rel="noopener noreferrer"&gt;https://aspire.dev/install.sh&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If {% raw %}&lt;code&gt;aspire&lt;/code&gt; isn’t found, open a new terminal and try again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core CLI Commands
&lt;/h2&gt;

&lt;p&gt;The Aspire CLI provides several commands for different stages of the development lifecycle. Here’s a practical rundown of the core commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;aspire new&lt;/code&gt; - Create a New Solution
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;aspire new&lt;/code&gt; for greenfield projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Interactive mode - prompts for template, name, and output
aspire new

# Non-interactive with explicit options
aspire new aspire-starter -n AspireApp -o AspireApp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What gets created:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AspireApp/
├── AspireApp.sln
├── AspireApp.AppHost/ # Dev-time orchestrator
│ ├── AppHost.cs
│ └── AspireApp.AppHost.csproj
├── AspireApp.ServiceDefaults/ # Shared configuration
│ ├── Extensions.cs
│ └── AspireApp.ServiceDefaults.csproj
├── AspireApp.ApiService/ # Mock weather data API
│ ├── Program.cs
│ └── AspireApp.ApiService.csproj
└── AspireApp.Web/ # Blazor frontend
 ├── Program.cs
 └── AspireApp.Web.csproj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get an AppHost, ServiceDefaults, and a small starter app you can run immediately.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;aspire init&lt;/code&gt; - Add Aspire to Existing Apps
&lt;/h3&gt;

&lt;p&gt;Already have a codebase? &lt;code&gt;aspire init&lt;/code&gt; adds an AppHost so you can orchestrate what you already have (including Python and JavaScript).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Navigate to your solution directory
cd /path/to/your-solution

# Initialize Aspire support
aspire init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command runs in interactive mode and will create &lt;code&gt;apphost.cs&lt;/code&gt; plus minimal run configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After running &lt;code&gt;aspire init&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your project root gets a file-based AppHost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-saas-app/
 apphost.cs # (new) orchestration code
 apphost.run.json # (new) local run configuration
 api/
 main.py
 pyproject.toml
 frontend/
 app.py
 requirements.txt
 worker/
 worker.py
 requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The initial &lt;code&gt;apphost.cs&lt;/code&gt; is minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#:sdk Aspire.AppHost.Sdk@13.0.0

var builder = DistributedApplication.CreateBuilder(args);

// TODO: Add resources here

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example: reference a Python API from a Python worker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you’re orchestrating Python services, add the Python hosting integration to your AppHost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire add python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then wire resources in the AppHost using &lt;code&gt;AddUvicornApp&lt;/code&gt; (for FastAPI, Flask, and other ASGI apps) and &lt;code&gt;AddPythonApp&lt;/code&gt; for a worker. &lt;code&gt;WithReference(...)&lt;/code&gt; declares the dependency and Aspire injects the connection info as environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddUvicornApp("api", "./api", "main:app")
 .WithUv()
 .WithHttpHealthCheck("/health");

var worker = builder.AddPythonApp("worker", "./worker", "worker.py")
 .WithUv()
 .WithReference(api); // Worker gets API_HTTP and API_HTTPS env vars

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;aspire new&lt;/code&gt; vs &lt;code&gt;aspire init&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starting fresh with a new project&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aspire new&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scaffolds everything: solution, AppHost, ServiceDefaults, sample projects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adding Aspire to existing code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aspire init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Preserves your existing projects, adds only orchestration layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Polyglot app (C#, Python, JS)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aspire init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Works with existing multi-language repos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning Aspire&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aspire new&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get a working example immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;aspire run&lt;/code&gt; - Run the Distributed App
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;aspire run&lt;/code&gt; builds and starts your resources and opens the dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# From your solution directory
aspire run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds the AppHost&lt;/li&gt;
&lt;li&gt;Builds and starts resources&lt;/li&gt;
&lt;li&gt;Prints the dashboard URL&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dashboard: https://localhost:17068/login?t=ea559845d54cea66b837dc0ff33c3bd3
Logs: ~/.aspire/cli/logs/apphost-13024-2025-10-31-19-40-58.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A note on the dashboard and telemetry&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The dashboard is where you’ll see status, logs, traces, and metrics for the running app.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;aspire add&lt;/code&gt; - Add Integrations
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;aspire add&lt;/code&gt; adds official integration packages to your AppHost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Interactive mode - shows list of available integrations
aspire add

# Add a specific integration by name
aspire add redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding an integration, wire it in your AppHost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var builder = DistributedApplication.CreateBuilder(args);

// Add Redis resource
var cache = builder.AddRedis("cache");

// Share Redis with your API
var api = builder.AddProject&amp;lt;Projects.YourApi&amp;gt;("api")
 .WithReference(cache)
 .WithHttpHealthCheck("/health");

builder.Build().Run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the client library to your consuming project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet add YourApi package Aspire.StackExchange.Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure the client in your service (for example, &lt;code&gt;builder.AddRedisClient("cache")&lt;/code&gt;).&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;code&gt;aspire update&lt;/code&gt; - Update Packages and Templates
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;aspire update&lt;/code&gt; command keeps your Aspire projects current by detecting and updating outdated packages and templates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to update the &lt;strong&gt;CLI itself&lt;/strong&gt; (instead of just your solution packages), use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aspire update --self
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Workflow
&lt;/h2&gt;

&lt;p&gt;Here’s a typical workflow for a new project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. Create a new solution
aspire new

# 2. Run it
aspire run

# 3. Add an integration
aspire add redis

# 4. Update packages (and optionally the CLI)
aspire update
aspire update --self
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/get-started/install-cli/" rel="noopener noreferrer"&gt;Install CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/reference/cli/overview/" rel="noopener noreferrer"&gt;CLI Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/get-started/add-aspire-existing-app/" rel="noopener noreferrer"&gt;Add Aspire to Existing App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/dashboard/overview/" rel="noopener noreferrer"&gt;Aspire Dashboard Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/integrations/gallery/" rel="noopener noreferrer"&gt;Integrations Gallery&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/codebytes/blog-samples/tree/main/aspire-cli" rel="noopener noreferrer"&gt;Blog Post Samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;There is too much to cover here, so check out the follow-up posts on deployment and AI-assisted development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/aspire-cli-part-2/"&gt;Part 2 - Deployment and Pipelines&lt;/a&gt; — &lt;code&gt;aspire publish&lt;/code&gt;, &lt;code&gt;aspire deploy&lt;/code&gt;, and CI/CD with GitHub Actions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/posts/aspire-cli-part-3-mcp/"&gt;Part 3 - MCP for AI Coding Agents&lt;/a&gt; — Connect AI assistants to your running Aspire applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until next time, happy Aspiring!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>cli</category>
      <category>dotnet</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building a Flexible AI Provider Strategy in .NET Aspire</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sun, 06 Jul 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/building-a-flexible-ai-provider-strategy-in-net-aspire-1i0p</link>
      <guid>https://dev.to/chris_ayers/building-a-flexible-ai-provider-strategy-in-net-aspire-1i0p</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny6djn8dxvbk403z4gaa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fny6djn8dxvbk403z4gaa.png" alt=".NET Aspire Logo" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How I architected a single codebase to seamlessly switch between Azure OpenAI, GitHub Models, Ollama, and Foundry Local without touching the API service&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When building my latest .NET Aspire application, I faced a common challenge: how do you develop and test with different AI providers without constantly rewriting your API service? The answer turned out to be surprisingly elegant - a configuration-driven approach that lets you switch between four different AI providers with zero code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: A Wide Variety of AI Choices
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;Modern AI development presents us with a wide variety of options. You might want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Develop locally&lt;/strong&gt; with Ollama for offline work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prototype quickly&lt;/strong&gt; with Microsoft’s Foundry Local&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test cheaply&lt;/strong&gt; with GitHub Models’ free tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to production&lt;/strong&gt; with Azure OpenAI’s enterprise features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The traditional approach would be to write separate implementations for each provider, but that leads to code duplication and maintenance headaches. Instead, I built a system that treats AI providers as interchangeable services.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Configuration-Driven Architecture
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;The key insight was to abstract the AI provider selection entirely out of the API service. Here’s how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "AI": {
 "Provider": "foundrylocal",
 "DeploymentName": "chat",
 "Model": "phi-3.5-mini"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it. Change a few lines in your configuration, or swap configurations/environments and your entire application switches AI providers. The result is a seamless transition with no code changes, recompilation, or deployment complexities.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Pillars of AI Provider Flexibility
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Best Use Case&lt;/th&gt;
&lt;th&gt;Free Tier&lt;/th&gt;
&lt;th&gt;Hardware Needs&lt;/th&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Azure OpenAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Production workloads&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;N/A (Cloud)&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GitHub Models&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Prototyping &amp;amp; Testing&lt;/td&gt;
&lt;td&gt;Yes (Generous)&lt;/td&gt;
&lt;td&gt;N/A (Cloud)&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Offline &amp;amp; Local Dev&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Local CPU/GPU&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Foundry Local&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Offline High-Perf Local Dev&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Local CPU/GPU&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Azure OpenAI - The Enterprise Choice
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlz03kpnp5oe5u76762r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlz03kpnp5oe5u76762r.png" alt="OpenAI Logo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Azure OpenAI is my go-to for production workloads. It’s reliable, scalable, and integrates seamlessly with Azure’s ecosystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "AI": {
 "Provider": "azureopenai",
 "Model": "gpt-4o"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why I choose Azure OpenAI for production:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise SLAs and support&lt;/li&gt;
&lt;li&gt;Built-in compliance and security features&lt;/li&gt;
&lt;li&gt;Seamless integration with Azure services&lt;/li&gt;
&lt;li&gt;Predictable pricing and billing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. GitHub Models - The Developer’s Friend
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiq6txb3vj3mppemdm9bt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiq6txb3vj3mppemdm9bt.png" alt="GitHub Logo" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/marketplace/models" rel="noopener noreferrer"&gt;GitHub Models&lt;/a&gt; surprised me with its generous free tier and extensive model catalog. Based on community feedback and my own testing, it’s proven reliable for development and staging environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "AI": {
 "Provider": "githubmodels",
 "Model": "gpt-4o-mini"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Perfect for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Early development and prototyping&lt;/li&gt;
&lt;li&gt;Testing different models without cost commitment&lt;/li&gt;
&lt;li&gt;Open source projects with budget constraints&lt;/li&gt;
&lt;li&gt;Experimenting with cutting-edge models&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Ollama - The Privacy Champion
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xghps60aph60z34a96b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xghps60aph60z34a96b.png" alt="Ollama Logo" width="800" height="1131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For local development and sensitive workloads, Ollama can’t be beat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "AI": {
 "Provider": "ollama",
 "Model": "llama3.2"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When I reach for Ollama:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Working on airplanes or in poor connectivity&lt;/li&gt;
&lt;li&gt;Handling sensitive data that can’t leave the premises&lt;/li&gt;
&lt;li&gt;Developing features without API costs&lt;/li&gt;
&lt;li&gt;Testing with models that aren’t available elsewhere&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mac Performance Note:&lt;/strong&gt; If you’re on Apple Silicon, avoid running Ollama in Docker containers. Docker on Mac cannot access the Metal GPU due to Apple’s virtualization framework limitations. Instead, install Ollama natively on macOS to take advantage of Metal GPU acceleration. This gives you significantly better performance compared to CPU-only Docker containers. The CommunityToolkit’s Ollama Integration defaults to docker, though you can use a connection string to point it to a native Ollama installation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. Foundry Local - The Microsoft Stack Choice
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchris-ayers.com%2Fimages%2Fai_studio_icon_color.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchris-ayers.com%2Fimages%2Fai_studio_icon_color.svg" alt="Foundry Local Logo" width="18" height="18"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Microsoft’s &lt;a href="https://github.com/microsoft/Foundry-Local" rel="noopener noreferrer"&gt;Foundry Local&lt;/a&gt; offers the best of both worlds - local deployment with enterprise-grade models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
 "AI": {
 "Provider": "foundrylocal",
 "Model": "phi-3.5-mini"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Foundry Local shines when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local deployment is required but you want recent models&lt;/li&gt;
&lt;li&gt;You’re already invested in the Microsoft AI ecosystem&lt;/li&gt;
&lt;li&gt;You want to leverage hardware acceleration on your machine&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;My Personal Favorite:&lt;/strong&gt; After testing all four providers extensively, Foundry Local running on-device has become my go-to choice for local AI development. Unlike Ollama in Docker, Foundry Local takes full advantage of your hardware - whether that’s Apple Silicon’s Metal GPU, NVIDIA cards, or CPU optimization. The performance is consistently excellent, and it handles the complexity of hardware acceleration automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Architecture Behind the Magic
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;The beauty of this approach lies in its simplicity. The API service never knows which AI provider it’s using-it just calls the abstracted AI service interface. This is all made possible by the &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; library, which provides the core &lt;code&gt;IChatClient&lt;/code&gt; abstraction. The complete source code for this project is available on my &lt;a href="https://github.com/chris-ayers/aspire-ai-provider-strategy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
%%{init: {'theme': 'default'}}%%
graph TD
 subgraph title[Configuration-Driven AI Provider Architecture]
 A[appsettings.json] --&amp;gt; B{AI Provider Config};
 B --&amp;gt; C{AddAIServices};
 C --&amp;gt; D{IChatClient};
 subgraph API Service
 D --&amp;gt; E[Your Service Code];
 end
 subgraph Provider Implementations
 C --&amp;gt; F[AzureOpenAIClient];
 C --&amp;gt; G[OllamaApiClient];
 C --&amp;gt; H[GitHubModels Client];
 C --&amp;gt; I[FoundryLocal Client];
 end
 end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s how I structured it.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Secret Sauce: Configuration-Driven Service Registration
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;The magic happens in the &lt;code&gt;AIServiceExtensions.cs&lt;/code&gt; file. A single call in &lt;code&gt;Program.cs&lt;/code&gt; kicks everything off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In your Program.cs
var builder = WebApplication.CreateBuilder(args);

// This one line does it all
builder.AddAIServices();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;AddAIServices&lt;/code&gt; extension method reads the configuration and registers the correct &lt;code&gt;IChatClient&lt;/code&gt; implementation. At its heart is a simple &lt;code&gt;switch&lt;/code&gt; statement that delegates to a specific registration method for each provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static IHostApplicationBuilder AddAIServices(this IHostApplicationBuilder builder)
{
 var aiSettings = AIConfiguration.GetSettings(builder.Configuration);
 builder.Services.AddSingleton(aiSettings);

 switch (aiSettings.Provider)
 {
 case AIProvider.Ollama:
 builder.AddOllamaAIServices(aiSettings);
 break;
 case AIProvider.AzureOpenAI:
 builder.AddAzureOpenAIServices(aiSettings);
 break;
 case AIProvider.GitHubModels:
 builder.AddGitHubModelsAIServices(aiSettings);
 break;
 case AIProvider.FoundryLocal:
 builder.AddFoundryLocalAIServices(aiSettings);
 break;
 default:
 throw new InvalidOperationException($"Unsupported AI provider: {aiSettings.Provider}");
 }
 return builder;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provider-Specific Implementations
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;Each provider has a slightly different setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure OpenAI and Ollama&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For Azure OpenAI and Ollama, the setup is straightforward thanks to .NET Aspire’s built-in support. A single line is enough to register the client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private static void AddAzureOpenAIServices(this IHostApplicationBuilder builder, AISettings aiSettings)
{
 builder.AddAzureOpenAIClient("ai-service")
 .AddChatClient(aiSettings.DeploymentName);
}

private static void AddOllamaAIServices(this IHostApplicationBuilder builder, AISettings aiSettings)
{
 builder.AddOllamaApiClient(aiSettings.DeploymentName)
 .AddChatClient();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub Models and Foundry Local&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub Models and Foundry Local require a bit more manual configuration. They both use the standard &lt;code&gt;OpenAIClient&lt;/code&gt; but pointed at different endpoints.&lt;/p&gt;

&lt;p&gt;For GitHub Models, we create an &lt;code&gt;OpenAIClient&lt;/code&gt; pointing to the &lt;code&gt;models.inference.ai.azure.com&lt;/code&gt; endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private static void AddGitHubModelsAIServices(this IHostApplicationBuilder builder, AISettings aiSettings)
{
 var githubToken = builder.Configuration["GITHUB_TOKEN"] ??
 builder.Configuration["ConnectionStrings:GitHubModels"];

 builder.Services.AddSingleton&amp;lt;IChatClient&amp;gt;(serviceProvider =&amp;gt;
 {
 var openAIClient = new OpenAIClient(
 new System.ClientModel.ApiKeyCredential(githubToken),
 new OpenAIClientOptions { Endpoint = new Uri("https://models.inference.ai.azure.com") }
 );
 return openAIClient.GetChatClient(aiSettings.Model).AsIChatClient();
 });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Foundry Local, the code first starts the local model server using &lt;code&gt;FoundryLocalManager&lt;/code&gt; and then creates an &lt;code&gt;OpenAIClient&lt;/code&gt; that points to the local endpoint it provides.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private static void AddFoundryLocalAIServices(this IHostApplicationBuilder builder, AIConfiguration.AISettings aiSettings)
{
 builder.Services.AddSingleton&amp;lt;IChatClient&amp;gt;(serviceProvider =&amp;gt;
 {
 var manager = FoundryLocalManager.StartModelAsync(aliasOrModelId: aiSettings.Model).GetAwaiter().GetResult();
 var modelInfo = manager.GetModelInfoAsync(aliasOrModelId: aiSettings.Model).GetAwaiter().GetResult();

 var openAIClient = new OpenAIClient(
 new System.ClientModel.ApiKeyCredential(manager.ApiKey),
 new OpenAIClientOptions { Endpoint = manager.Endpoint }
 );
 return openAIClient.GetChatClient(modelInfo?.ModelId ?? aiSettings.Model).AsIChatClient();
 });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your API service code remains blissfully unaware of this complexity. It simply requests an &lt;code&gt;IChatClient&lt;/code&gt; and starts making calls, confident that the correct provider is handling the request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Switching Techniques
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;h3&gt;
  
  
  Technique 1: Environment-Based Switching
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;My favorite approach is to use environment-specific configuration files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// appsettings.Development.json
{
 "AI": {
 "Provider": "foundrylocal",
 "Model": "phi-3.5-mini"
 }
}

// appsettings.Production.json
{
 "AI": {
 "Provider": "azureopenai",
 "Model": "gpt-4o"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means developers get fast, local AI by default, while production gets the reliability of Azure OpenAI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technique 2: Feature Flag-Style Switching
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;Want to test a new provider with just a subset of users? Environment variables make this trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Test GitHub Models for 10% of traffic
export AI__Provider="githubmodels"
export AI__Model="gpt-4o-mini"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Technique 3: Command-Line Override
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;Perfect for debugging or one-off tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet run --project src/BuildWithAspire.AppHost -- --AI:Provider=ollama --AI:Model=llama3.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Building for Resilience: Error Handling and Health Checks
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;A flexible provider strategy also requires robust error handling. What happens if your primary provider goes down or a local model crashes?&lt;/p&gt;

&lt;p&gt;I recommend implementing a few key patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Health Checks&lt;/strong&gt; : Use .NET Aspire’s health check features to monitor the status of your AI provider endpoints. This allows you to detect failures quickly and potentially route traffic away from an unhealthy provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry Policies&lt;/strong&gt; : Implement a retry policy (e.g., with Polly) to handle transient network issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallback Strategy&lt;/strong&gt; : For critical applications, consider a fallback mechanism. If a request to your primary provider fails, you could automatically retry with a secondary provider. This can be implemented within a custom &lt;code&gt;IChatClient&lt;/code&gt; wrapper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These strategies ensure your application remains resilient, even when individual components fail.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned from Production
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;h3&gt;
  
  
  What Works Well
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Models exceeded my expectations.&lt;/strong&gt; The free tier is generous, and the model selection is impressive. I’ve been using it for all my development work, and it’s become my go-to for prototyping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama is fantastic for air-gapped environments.&lt;/strong&gt; When I need to demo on a flight or work with sensitive data, Ollama ensures I’m never blocked by connectivity issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foundry Local surprised me with its performance.&lt;/strong&gt; While I haven’t run formal benchmarks, my experience shows that first-token response times are significantly faster with Foundry Local on my machine compared to cloud-based providers. It’s become my default choice for local AI development.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Caught Me Off Guard
&lt;/h3&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token usage varies dramatically between providers.&lt;/strong&gt; The same conversation might cost 10x more on one provider than another. I learned to monitor token usage carefully, especially when switching between providers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model behavior isn’t always consistent.&lt;/strong&gt; GPT-4o on Azure OpenAI behaves slightly differently than on GitHub Models. It’s not a problem, but it’s worth testing your specific use cases when switching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local providers need warm-up time.&lt;/strong&gt; Ollama and Foundry Local can take a few seconds to respond to the first request after being idle. I added a simple warm-up call during application startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mac users, beware the Docker trap.&lt;/strong&gt; I learned this the hard way: running Ollama in Docker on Apple Silicon is painfully slow because Docker can’t access the Metal GPU. Apple’s virtualization framework blocks GPU exposure to containers, so you get CPU-only performance. The solution? Run Ollama natively or use Foundry Local on macOS for Metal acceleration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Cost-Optimization Strategy
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;Here’s how I use different providers throughout my development lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Development:&lt;/strong&gt; Foundry Local or Ollama (free, fast iteration)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; GitHub Models (free tier covers most testing needs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staging:&lt;/strong&gt; GitHub Models or Azure OpenAI (depending on expected load)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production:&lt;/strong&gt; Azure OpenAI (enterprise features, SLAs)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This approach has cut my AI development costs by roughly 80% while maintaining flexibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  The One-Line Provider Switch
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;The most satisfying part of this architecture is how easy it is to switch providers. Whether you’re responding to a service outage, testing a new model, or optimizing costs, it’s always just a simple configuration change or command line override.&lt;/p&gt;

&lt;p&gt;No code changes. No redeployment. Just a quick update and restart.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Versioning
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;The AI landscape is evolving rapidly. For this project, I used &lt;strong&gt;.NET 9&lt;/strong&gt; with &lt;strong&gt;.NET Aspire 9.3&lt;/strong&gt;. If you’re implementing a similar solution, be sure to check for the latest versions and any potential breaking changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;I’m excited about the future of AI provider flexibility. Some ideas I’m exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intelligent routing&lt;/strong&gt; based on request type or user tier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost-based switching&lt;/strong&gt; that automatically chooses the most economical provider&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B testing&lt;/strong&gt; different models for the same conversation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid approaches&lt;/strong&gt; that use multiple providers for different parts of the same workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The configuration-driven approach makes all of these possibilities simple to implement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;#&lt;/p&gt;

&lt;p&gt;Building AI applications doesn’t have to lock you into a single provider. With the right architecture, you can have the best of all worlds: local development, cost-effective testing, and enterprise-grade production deployment.&lt;/p&gt;

&lt;p&gt;The key is to think of AI providers as interchangeable services from day one. Your future self will thank you when you need to switch providers for cost, performance, or compliance reasons.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you built something similar? I’d love to hear about your experiences with multi-provider AI architectures. Drop me a line on &lt;a href="https://bsky.app/profile/chris-ayers.com" rel="noopener noreferrer"&gt;Blue Sky&lt;/a&gt; or &lt;a href="https://linkedin.com/in/chris-ayers" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>azure</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Headless CMS on GitHub Pages</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Thu, 26 Jun 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/headless-cms-on-github-pages-37jo</link>
      <guid>https://dev.to/chris_ayers/headless-cms-on-github-pages-37jo</guid>
      <description>&lt;p&gt;Headless CMS on GitHub Pages&lt;/p&gt;

&lt;p&gt;I’ve been running my site on GitHub Pages - no server, just git and GitHub Actions. But I still want a CMS that works on mobile, without dragging around a laptop or doing a Git clone. Enter &lt;a href="https://github.com/sveltia/sveltia-cms" rel="noopener noreferrer"&gt;Sveltia CMS&lt;/a&gt; + &lt;a href="https://github.com/sveltia/sveltia-cms-auth" rel="noopener noreferrer"&gt;Sveltia CMS Auth&lt;/a&gt; on Cloudflare Workers. Here’s how to glue it together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Setup?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No paid services like Netlify.&lt;/li&gt;
&lt;li&gt;I edit blog posts from my phone-even waiting in line for coffee.&lt;/li&gt;
&lt;li&gt;No git install, no desktop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A solution exists: &lt;a href="https://github.com/sveltia/sveltia-cms-auth" rel="noopener noreferrer"&gt;Sveltia’s Cloudflare Workers-based authenticator&lt;/a&gt; makes GitHub-backed CMS possible on &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Step 1: Add Sveltia CMS to Your Site
&lt;/h2&gt;

&lt;p&gt;In your /admin/index.html, include Sveltia, a lightweight, modern CMS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;

&amp;lt;head&amp;gt;
    &amp;lt;meta charset="utf-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;meta name="robots" content="noindex" /&amp;gt;
    &amp;lt;title&amp;gt;Content Manager&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
    &amp;lt;script src="https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No heavy JS, no React-just &lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt;. Touch support, dark mode, fast. It handles GitHub directly via your browser or the auth worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔐 Step 2: Deploy the Sveltia-CMS-Auth Worker
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Fork or import &lt;a href="https://github.com/sveltia/sveltia-cms-auth" rel="noopener noreferrer"&gt;sveltia-cms-auth&lt;/a&gt; into &lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Deploy it; grab the Worker URL.&lt;/li&gt;
&lt;li&gt;Register a &lt;a href="https://github.com/settings/applications/new" rel="noopener noreferrer"&gt;GitHub OAuth App&lt;/a&gt;, using the Worker URL plus /callback for the redirect.&lt;/li&gt;
&lt;li&gt;Add client credentials and your domain to Workers environment variables. Deploy again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now your site has its own auth client-it communicates with GitHub when you click Login.&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠 Step 3: Wire It All Up in Your Config
&lt;/h2&gt;

&lt;p&gt;In your Sveltia admin/config.yml, under backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend:
  name: github
  repo: &amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt; # Path to your GitHub repository
  # optional, defaults to master
  branch: main
  base_url: https://&amp;lt;WORKER&amp;gt;.workers.dev
# This line should *not* be indented
media_folder: "assets/uploads" # Media files will be stored in the repo under images/uploads

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That base_url points Sveltia’s OAuth to your Worker. Push the change, and your /admin/ loads Sveltia. Clicking “Login” redirects to GitHub via your Worker, and you’re ready to edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  📱 Step 4: Edit From Your Phone
&lt;/h2&gt;

&lt;p&gt;Visit yourgithubpages.com/admin/ on your mobile device. The UI is responsive and built for touch. I’ve easily edited blog posts without needing VS Code or Terminal. Sveltia handles commit and push transparently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Why not Decap CMS or Netlify CMS?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: They typically rely on external OAuth providers. &lt;a href="https://github.com/sveltia/sveltia-cms" rel="noopener noreferrer"&gt;Sveltia&lt;/a&gt; + Cloudflare Workers solves that neatly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Does it require a PC?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: Only initial setup needs a desktop. Afterward, your CMS runs entirely in-browser, even mobile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Self-hosted?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
A: &lt;a href="https://docs.github.com/en/pages" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt; hosts the static site. The CMS runs client-side, and auth is on &lt;a href="https://developers.cloudflare.com/workers/platform/pricing/" rel="noopener noreferrer"&gt;Cloudflare’s free tier&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Goal&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub-only hosting&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt; (no server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modern, touch-ready UI&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/sveltia/sveltia-cms" rel="noopener noreferrer"&gt;Sveltia CMS&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth via GitHub&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/sveltia/sveltia-cms-auth" rel="noopener noreferrer"&gt;Sveltia-CMS-Auth Worker&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero laptop edits&lt;/td&gt;
&lt;td&gt;Fully mobile browser-compatible&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This combo gives me a mobile-friendly CMS experience without extra hassle. If you’re editing your &lt;a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll" rel="noopener noreferrer"&gt;Jekyll-based GitHub Pages blog&lt;/a&gt; without a PC, this setup hits the mark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/pages" rel="noopener noreferrer"&gt;GitHub Pages Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/docs/" rel="noopener noreferrer"&gt;Jekyll Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/workers/" rel="noopener noreferrer"&gt;Cloudflare Workers Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site" rel="noopener noreferrer"&gt;Setting up Custom Domains with GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/dns/manage-dns-records/" rel="noopener noreferrer"&gt;Managing Multiple Domains with Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sveltia/sveltia-cms" rel="noopener noreferrer"&gt;Sveltia CMS GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sveltia/sveltia-cms-auth" rel="noopener noreferrer"&gt;Sveltia CMS Auth GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/2022/12/20/migrating-from-wordpress-to-github-pages"&gt;My Previous Post on Migrating to GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/2022/12/27/multiple-domains-on-github-pages"&gt;My Guide to Multiple Domains on GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>tools</category>
      <category>githubpages</category>
      <category>sveltiacms</category>
    </item>
    <item>
      <title>Understanding AI and Prompting Techniques Part 1</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sun, 25 May 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/understanding-ai-and-prompting-techniques-part-1-4a60</link>
      <guid>https://dev.to/chris_ayers/understanding-ai-and-prompting-techniques-part-1-4a60</guid>
      <description>&lt;h2&gt;
  
  
  Understanding AI and Prompting Techniques Part 1
&lt;/h2&gt;

&lt;p&gt;Artificial Intelligence (AI) has rapidly evolved from science fiction to an integral part of our daily lives, transforming how we interact with technology and reshaping industries worldwide. Whether you’re using ChatGPT to draft emails, asking Copilot to write code, or generating images with DALL-E, understanding how to effectively communicate with AI systems has become an essential skill.&lt;/p&gt;

&lt;p&gt;In this first part of our series, we’ll explore foundational concepts of AI, machine learning, deep learning, and generative AI, then dive deep into the art of prompting—the key to unlocking AI’s full potential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;What is AI?&lt;/li&gt;
&lt;li&gt;Key AI Terms&lt;/li&gt;
&lt;li&gt;AI Models&lt;/li&gt;
&lt;li&gt;Tokens: The Language of AI&lt;/li&gt;
&lt;li&gt;Prompting&lt;/li&gt;
&lt;li&gt;What’s Next?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is AI?
&lt;/h2&gt;

&lt;p&gt;Artificial Intelligence is a branch of computer science dedicated to creating intelligent machines capable of performing tasks that typically require human intelligence. These tasks include problem-solving, decision-making, language understanding, and perception.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evolution of AI
&lt;/h3&gt;

&lt;p&gt;AI has evolved through distinct phases, each building upon the previous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1950s - Artificial Intelligence&lt;/strong&gt; : The foundational era where pioneers like Alan Turing and John McCarthy laid the groundwork for machines that could simulate human reasoning and problem-solving.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1980s-1990s - Expert Systems&lt;/strong&gt; : Rule-based systems that captured human expertise in specific domains, marking the first practical AI applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1997 - Machine Learning Breakthrough&lt;/strong&gt; : IBM’s Deep Blue defeated world chess champion Garry Kasparov, showcasing the power of algorithms that could learn and improve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2012 - Deep Learning Revolution&lt;/strong&gt; : The ImageNet competition demonstrated that neural networks with multiple layers could dramatically outperform traditional methods in image recognition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2017 - Transformer Architecture&lt;/strong&gt; : The introduction of the “Attention Is All You Need” paper revolutionized natural language processing, leading to modern AI models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2022-2024 - Generative AI Explosion&lt;/strong&gt; : Models like ChatGPT, GPT-4, and Claude brought AI to mainstream audiences, enabling creation of text, images, code, and more from simple prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key AI Terms
&lt;/h2&gt;

&lt;p&gt;To effectively engage with AI, it’s essential to understand some fundamental terms.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Models
&lt;/h2&gt;

&lt;p&gt;AI models are the engines that power modern artificial intelligence. A model is a system trained on large datasets to recognize patterns, make predictions, or generate new content. The quality and capabilities of an AI system depend heavily on the underlying model.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Models Work
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Training&lt;/strong&gt; : Models are trained using vast amounts of data and advanced algorithms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning Patterns&lt;/strong&gt; : Through training, models learn to identify trends, relationships, and structures in data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Making Predictions&lt;/strong&gt; : Once trained, models can process new data to make predictions, classify information, or generate content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Popular AI Models (2025)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Company/Organization&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Specialization&lt;/th&gt;
&lt;th&gt;Key Features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;GPT-4o, GPT-4o mini, o1&lt;/td&gt;
&lt;td&gt;Language, reasoning&lt;/td&gt;
&lt;td&gt;Advanced reasoning, multimodal capabilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Phi-4, Copilot&lt;/td&gt;
&lt;td&gt;Code, productivity&lt;/td&gt;
&lt;td&gt;Optimized for efficiency, integrated tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Claude 3.5 Sonnet, Claude 3.5 Haiku&lt;/td&gt;
&lt;td&gt;Conversational AI&lt;/td&gt;
&lt;td&gt;Constitutional AI, safety-focused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta&lt;/td&gt;
&lt;td&gt;Llama 3.1, Llama 3.2&lt;/td&gt;
&lt;td&gt;Open-source language&lt;/td&gt;
&lt;td&gt;Available for research and commercial use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Gemini Pro, Gemini Ultra&lt;/td&gt;
&lt;td&gt;Multimodal AI&lt;/td&gt;
&lt;td&gt;Strong integration with Google services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek&lt;/td&gt;
&lt;td&gt;DeepSeek-R1&lt;/td&gt;
&lt;td&gt;Reasoning&lt;/td&gt;
&lt;td&gt;Specialized in logical reasoning tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These models differ significantly in their architecture, training data, and intended use cases. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPT-4o&lt;/strong&gt; excels at complex reasoning and multimodal tasks (text, images, audio)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude 3.5 Sonnet&lt;/strong&gt; is known for its helpful, harmless, and honest responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phi-4&lt;/strong&gt; is optimized for efficiency while maintaining strong performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Llama 3.1&lt;/strong&gt; offers powerful open-source alternatives for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“The choice of model determines the capabilities and limitations of your AI-powered application.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Tokens: The Language of AI
&lt;/h2&gt;

&lt;p&gt;Tokens are the fundamental units that AI models use to process and understand language. Think of tokens as the “words” in AI’s vocabulary, though they don’t always correspond directly to human words.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Tokenization
&lt;/h3&gt;

&lt;p&gt;When you submit text to an AI model, it first breaks your input into tokens through a process called tokenization. Here’s what you need to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Size&lt;/strong&gt; : One token roughly equals 4 characters of English text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversion&lt;/strong&gt; : 100 tokens ≈ 75 English words&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variability&lt;/strong&gt; : Common words might be single tokens, while rare words or technical terms might be split into multiple tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt; : Different languages have different tokenization patterns (e.g., Chinese characters often require more tokens per word)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Tokens Matter
&lt;/h3&gt;

&lt;p&gt;Understanding tokens helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optimize prompts&lt;/strong&gt; : Stay within model token limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage costs&lt;/strong&gt; : Many AI services charge per token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve efficiency&lt;/strong&gt; : Craft concise prompts that convey maximum information&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Token Limits in Practice
&lt;/h3&gt;

&lt;p&gt;Most AI models have token limits for both input and output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GPT-4o&lt;/strong&gt; : Up to 128,000 input tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude 3.5 Sonnet&lt;/strong&gt; : Up to 200,000 input tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini Pro&lt;/strong&gt; : Up to 1,000,000 input tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1v0f4mx6ym25lxe2vhsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1v0f4mx6ym25lxe2vhsd.png" alt="Tokenization example" width="800" height="391"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip&lt;/strong&gt; : Use online token counters to estimate your prompt length before submitting to AI models, especially for complex tasks or long documents.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Prompting
&lt;/h2&gt;

&lt;p&gt;Prompting is the art of communicating with AI models to achieve useful and relevant responses. The effectiveness of a prompt depends on clarity, context, and the role you assign to the model. Well-crafted prompts can dramatically improve the quality and relevance of AI outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Prompting Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clear prompts reduce ambiguity and help the model understand your intent.&lt;/li&gt;
&lt;li&gt;Providing context ensures the AI tailors its response to your needs.&lt;/li&gt;
&lt;li&gt;Assigning roles guides the model’s tone, expertise, and style.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Components of a Good Prompt
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instruction&lt;/td&gt;
&lt;td&gt;What you want the AI to do&lt;/td&gt;
&lt;td&gt;Summarize this article in 3 sentences.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;Background or specifics&lt;/td&gt;
&lt;td&gt;The article is about renewable energy trends in 2025.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Role&lt;/td&gt;
&lt;td&gt;Persona or expertise&lt;/td&gt;
&lt;td&gt;You are an energy policy analyst.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format&lt;/td&gt;
&lt;td&gt;Desired output style&lt;/td&gt;
&lt;td&gt;Respond in bullet points.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constraints&lt;/td&gt;
&lt;td&gt;Limits or requirements&lt;/td&gt;
&lt;td&gt;Use no more than 100 words.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Practical Prompting Tips
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Be specific&lt;/strong&gt; : Vague prompts lead to vague answers. Specify what you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add context&lt;/strong&gt; : Include relevant details, such as audience, purpose, or scenario.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set a role&lt;/strong&gt; : Tell the AI who it should act as for more targeted responses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request a format&lt;/strong&gt; : Ask for lists, tables, or summaries if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterate&lt;/strong&gt; : Refine your prompt if the output is not what you expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prompting Examples
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Without context:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Write an email inviting people to a meeting.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;With context and role:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“You are a project manager at Contoso. Write a formal email inviting the Azure Reliability team to a kickoff meeting next Thursday at 2 PM ET to discuss outage analysis improvements using AI.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second prompt produces a much more targeted and useful response because it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Role clarity&lt;/strong&gt; : Project manager perspective&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specific context&lt;/strong&gt; : Company name, team, meeting purpose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear details&lt;/strong&gt; : Date, time, topic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tone guidance&lt;/strong&gt; : Formal communication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Power of Context and Roles
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Why Context Matters
&lt;/h4&gt;

&lt;p&gt;Context provides the background information that transforms generic AI responses into tailored, relevant outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Audience awareness&lt;/strong&gt; : Specify who will read/use the output (executives, developers, students)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purpose clarity&lt;/strong&gt; : Define what you want to achieve (inform, persuade, educate, troubleshoot)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints&lt;/strong&gt; : Include relevant limitations (time, budget, technical requirements)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain specifics&lt;/strong&gt; : Provide industry or technical context when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Effective Role Assignment
&lt;/h4&gt;

&lt;p&gt;Assigning roles gives the AI a persona and expertise focus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Professional roles&lt;/strong&gt; : “You are a cybersecurity expert,” “Act as a technical architect,” “Respond as a UX designer”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communication styles&lt;/strong&gt; : “Explain like I’m 5,” “Use formal business language,” “Write in a conversational tone”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perspective shifts&lt;/strong&gt; : “From a customer’s viewpoint,” “Consider the developer experience,” “Think like a project manager”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example of role impact:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Generic prompt:&lt;/em&gt; “How do I optimize this SQL query?”&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Role-specific prompt:&lt;/em&gt; “You are a senior database administrator. Review this SQL query for a high-traffic e-commerce site and suggest optimizations that prioritize read performance while maintaining data consistency.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Prompting Techniques
&lt;/h3&gt;

&lt;p&gt;Beyond the basics, here are some powerful techniques for getting better results:&lt;/p&gt;

&lt;h4&gt;
  
  
  Chain of Thought Prompting
&lt;/h4&gt;

&lt;p&gt;Ask the AI to “think step by step” or “show your reasoning”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze this code for security vulnerabilities. Think step by step:
1. First, identify potential input validation issues
2. Then, check for authentication and authorization flaws
3. Finally, look for data exposure risks

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Few-Shot Learning
&lt;/h4&gt;

&lt;p&gt;Provide examples of the desired output format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Convert these technical terms to plain English:

API → Application Programming Interface (a way for software to communicate)
SDK → Software Development Kit (tools for building apps)
Docker → [Your task: explain this term]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Prompt Chaining
&lt;/h4&gt;

&lt;p&gt;Break complex tasks into smaller, sequential prompts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;“Summarize this 50-page document into key themes”&lt;/li&gt;
&lt;li&gt;“For each theme, identify 3 actionable recommendations”&lt;/li&gt;
&lt;li&gt;“Create a presentation outline based on these recommendations”&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Understanding AI Model Limitations
&lt;/h3&gt;

&lt;p&gt;While AI models are powerful, it’s crucial to understand their limitations:&lt;/p&gt;

&lt;h4&gt;
  
  
  Statelessness and Memory
&lt;/h4&gt;

&lt;p&gt;Most AI models are stateless—they don’t remember previous interactions unless explicitly provided with conversation history. Each request is independent, which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No persistent memory&lt;/strong&gt; : The model won’t remember what you discussed earlier unless you include it in your current prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context windows&lt;/strong&gt; : Models can only “see” a limited amount of text at once (their context window)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application responsibility&lt;/strong&gt; : Chat applications maintain conversation history and send it with each new request&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Other Key Limitations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge cutoffs&lt;/strong&gt; : Models are trained on data up to a specific date and don’t know about events after that&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hallucinations&lt;/strong&gt; : Models may generate plausible-sounding but incorrect information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bias&lt;/strong&gt; : Training data biases can influence model outputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reasoning limitations&lt;/strong&gt; : While improving, models can struggle with complex logical reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Best Practice&lt;/strong&gt; : Always verify important information from AI models, especially for critical decisions or factual claims.&lt;/p&gt;

&lt;p&gt;“Effective prompting combines clear instructions, relevant context, and well-defined roles to guide AI models toward your desired outcome.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;In this series, we’ll dive deeper into advanced prompt engineering techniques, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt patterns and templates&lt;/strong&gt; for common use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-modal prompting&lt;/strong&gt; with images, audio, and code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt optimization strategies&lt;/strong&gt; for better performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-world case studies&lt;/strong&gt; from software development, content creation, and data analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety and ethical considerations&lt;/strong&gt; in AI prompting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether you’re a developer looking to integrate AI into your applications, a content creator exploring AI-assisted workflows, or simply curious about maximizing your productivity with AI tools, the next part will provide practical techniques you can apply immediately.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;“Mastering AI prompting is like learning a new language—the better you communicate, the more powerful the results.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Stay tuned for Part 2, where we’ll put these concepts into practice with hands-on examples and advanced strategies!&lt;/p&gt;

</description>
      <category>technology</category>
      <category>ai</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>Aspiring .NET &amp; Resilience @ NDC Oslo 2025</title>
      <dc:creator>Chris Ayers</dc:creator>
      <pubDate>Sat, 24 May 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/chris_ayers/aspiring-net-resilience-ndc-oslo-2025-a8h</link>
      <guid>https://dev.to/chris_ayers/aspiring-net-resilience-ndc-oslo-2025-a8h</guid>
      <description>&lt;p&gt;As I’m flying from Oslo to Amsterdam, I’m still buzzing with energy and inspiration from NDC Oslo 2025. It was an incredible week of learning, sharing, and connecting with some of the brightest minds in technology. From thought-provoking keynotes that challenged our assumptions to the epic &lt;a href="https://www.youtube.com/@TheLinebreakers" rel="noopener noreferrer"&gt;The Linebreakers&lt;/a&gt; concert at Brewgata, every moment reinforced why each NDC conference but NDC Oslo remains one of the premier developer conferences in the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Journey of Learning and Sharing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://ndcoslo.com" rel="noopener noreferrer"&gt;NDC Oslo 2025&lt;/a&gt; brought together over 2,000 developers, architects, and technology leaders from across the globe. The conference’s unique blend of deep technical content and community spirit created an atmosphere where learning happened not just in sessions, but in every conversation.&lt;/p&gt;

&lt;p&gt;As a speaker at this year’s event, I had the privilege of presenting two sessions that reflect the current evolution of our industry:&lt;/p&gt;

&lt;h3&gt;
  
  
  Aspiring .NET with Azure OpenAI and Ollama
&lt;/h3&gt;

&lt;p&gt;During the Community Days event, I showcased practical AI integration patterns for .NET developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Building semantic search capabilities&lt;/strong&gt; using Azure OpenAI embeddings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running local LLMs with Ollama&lt;/strong&gt; for development and testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementing responsible AI practices&lt;/strong&gt; including content filtering and rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating conversational interfaces&lt;/strong&gt; that enhance rather than replace existing applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The recently updated samples for .NET Aspire 9.3 sparked discussions about the future of AI in enterprise applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://ndcoslo.com/agenda/resilient-by-design-0nqr/03e2y9s6amr" rel="noopener noreferrer"&gt;Resilient by Design&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In this session, I explored how to build systems that don’t just survive failure-they thrive despite it. We dove into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chaos engineering principles&lt;/strong&gt; and how to implement them safely in production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breaker patterns&lt;/strong&gt; with real-world examples from Azure deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful degradation strategies&lt;/strong&gt; that maintain user experience during partial outages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability practices&lt;/strong&gt; that make resilience measurable and improvable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engaged room had questions demonstrated just how critical resilience has become in our cloud-native world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engaging with Diverse Perspectives
&lt;/h2&gt;

&lt;p&gt;What makes NDC Oslo special is its commitment to showcasing the full spectrum of modern technology. This year’s program delivered exceptional content across multiple tracks:&lt;/p&gt;

&lt;h3&gt;
  
  
  AI and Machine Learning
&lt;/h3&gt;

&lt;p&gt;Beyond the hype, speakers demonstrated practical AI implementations that solve real business problems. Sessions ranged from building RAG (Retrieval-Augmented Generation) systems to implementing ethical AI governance frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Architecture
&lt;/h3&gt;

&lt;p&gt;The cloud track featured battle-tested patterns for building distributed systems at scale. Highlights included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event-driven architectures using Azure Service Bus and Event Grid&lt;/li&gt;
&lt;li&gt;Multi-region deployment strategies with zero-downtime deployments&lt;/li&gt;
&lt;li&gt;Cost optimization techniques that saved companies millions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Experience
&lt;/h3&gt;

&lt;p&gt;A refreshing focus on making developers’ lives better, with sessions covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modern debugging techniques for distributed systems&lt;/li&gt;
&lt;li&gt;AI-powered code review tools&lt;/li&gt;
&lt;li&gt;Creating effective development environments with DevContainers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance and Optimization
&lt;/h3&gt;

&lt;p&gt;Deep technical dives into making applications faster and more efficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;.NET performance improvements and how to leverage them&lt;/li&gt;
&lt;li&gt;Database query optimization strategies&lt;/li&gt;
&lt;li&gt;Frontend performance patterns for modern SPAs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security and Best Practices
&lt;/h3&gt;

&lt;p&gt;Critical discussions on protecting modern applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero-trust architecture implementation&lt;/li&gt;
&lt;li&gt;Supply chain security for containerized applications&lt;/li&gt;
&lt;li&gt;OWASP Top 10 for cloud-native applications&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Psychology and Soft Skills
&lt;/h3&gt;

&lt;p&gt;Perhaps the most impactful track, addressing the human side of technology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building psychologically safe teams&lt;/li&gt;
&lt;li&gt;Managing technical debt without burnout&lt;/li&gt;
&lt;li&gt;Effective communication strategies for technical leaders&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conference Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Keynotes That Inspired
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ndcoslo.com/agenda/keynote-ctrl-shift-build-pause/7afc8e95b18d" rel="noopener noreferrer"&gt;Laila Bougria’s “CTRL+SHIFT+(BUILD) PAUSE”&lt;/a&gt;&lt;/strong&gt; was a masterclass in presentation excellence. Her exploration of using AI in our daily workflow and the importance of not giving way too much control resonated deeply. The way she wove personal stories with technical insights created a narrative that was both educational and emotionally compelling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ndcoslo.com/agenda/keynote-the-unbearable-weight-of-architecture-0xq3/06tisj5yxke" rel="noopener noreferrer"&gt;David Whitney’s “The Unbearable Weight of Architecture”&lt;/a&gt;&lt;/strong&gt; challenged us to think beyond technical perfection. His emphasis on pragmatic architecture decisions and the importance of designing with intentionality struck a chord with the audience. His discussion of how wrong decisions cast long shadows over systems-dooming maintainers to endless toil-was both sobering and enlightening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ndcoslo.com/agenda/reasons-to-be-cheerful-0-1-2/001a544d1822" rel="noopener noreferrer"&gt;Rendle’s closing keynote “Reasons To Be Cheerful: 0, 1, 2…”&lt;/a&gt;&lt;/strong&gt; sent us home with renewed optimism. His journey through technology’s evolution reminded us how far we’ve come and painted an exciting picture of where we’re heading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond the Sessions
&lt;/h3&gt;

&lt;p&gt;The true magic of NDC Oslo happened between the scheduled talks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coffee Conversations&lt;/strong&gt; : Where a casual chat about microservices turned into a deep dive on event sourcing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightning Talks&lt;/strong&gt; : 10-minute bursts of inspiration that often packed more value than hour-long sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workshop Deep Dives&lt;/strong&gt; : Hands-on learning that transformed theory into practice&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Legendary NDC Party
&lt;/h3&gt;

&lt;p&gt;The conference’s social highlight deserves special mention. The evening began with fascinating presentations on space technology (who knew satellite communication could be so entertaining?), followed by a nostalgic journey through the Demo Scene that had everyone reminiscing about their first coding experiences.&lt;/p&gt;

&lt;p&gt;The Phil Nash Karaoke session was legendary-complete with a full chorus of developers providing hyena sound effects for “Be Prepared” from The Lion King. It perfectly captured the conference’s spirit: serious about technology, but never taking ourselves too seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;Reflecting on NDC Oslo 2025, several themes emerged that will shape my work in the coming year:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI Integration is Now Table Stakes&lt;/strong&gt; The question is no longer whether to integrate AI, but how to do it responsibly and effectively. Every application can benefit from intelligent features, but success requires thoughtful implementation and clear value propositions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resilience Through Simplicity&lt;/strong&gt; The most resilient systems aren’t necessarily the most complex. Sessions repeatedly emphasized that simple, well-understood patterns often outperform clever solutions when things go wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Community Amplifies Individual Impact&lt;/strong&gt; The connections made at NDC Oslo will generate more value than any single session. The mix of experienced architects sharing war stories with eager newcomers created a learning environment that benefits everyone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Practical Beats Theoretical&lt;/strong&gt; The most successful sessions focused on “here’s how we actually did it” rather than “here’s how it should work in theory.” Real-world case studies and live coding demonstrations drove the most engagement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Human Element Remains Central&lt;/strong&gt; Despite all our technological advances, the sessions on team dynamics, communication, and mental health drew the largest crowds. Technology is ultimately about people, and NDC Oslo never forgot that.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;The conversations started at NDC Oslo will continue through blog posts, open-source contributions, and community meetups.&lt;/p&gt;

&lt;p&gt;For those considering attending future NDC events, I cannot recommend them highly enough. Whether you’re a seasoned architect or just starting your development journey, you’ll find sessions, workshops, and conversations that challenge and inspire you.&lt;/p&gt;

&lt;p&gt;The technology landscape continues to evolve at breakneck speed, but events like NDC Oslo remind us that we’re not navigating these changes alone. We’re part of a global community of builders, thinkers, and problem-solvers, all working to create a better technological future.&lt;/p&gt;

&lt;p&gt;Thank you, NDC Oslo, for another unforgettable experience. See you next year!&lt;/p&gt;

</description>
      <category>speaking</category>
      <category>azure</category>
      <category>ai</category>
      <category>resilience</category>
    </item>
  </channel>
</rss>
