<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>CodeBytes - The Curious Engineer</title><link>https://chris-ayers.com/</link><description>Recent content on CodeBytes - The Curious Engineer</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><copyright>© 2026 Chris Ayers</copyright><lastBuildDate>Thu, 26 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://chris-ayers.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Agent Skills, Plugins and Marketplace: The Complete Guide</title><link>https://chris-ayers.com/posts/agent-skills-plugins-marketplace/</link><pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/agent-skills-plugins-marketplace/</guid><description>&lt;p&gt;Extending GitHub Copilot with reusable, shareable AI capabilities&lt;/p&gt;</description><content:encoded>&lt;p&gt;Extending GitHub Copilot with reusable, shareable AI capabilities&lt;/p&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;GitHub Copilot&amp;rsquo;s extensibility story has evolved rapidly. From custom instructions to MCP servers, the platform has steadily added ways to customize how the AI assistant works. But until recently, &lt;strong&gt;sharing&lt;/strong&gt; those customizations across projects and teams remained painful - submodules, manual file copying, and constant drift.&lt;/p&gt;
&lt;p&gt;That changes with three interconnected features: &lt;strong&gt;Agent Skills&lt;/strong&gt;, &lt;strong&gt;Plugins&lt;/strong&gt;, and &lt;strong&gt;Marketplaces&lt;/strong&gt;. Together, they form a complete ecosystem for creating, packaging, distributing, and discovering AI-powered developer tooling.&lt;/p&gt;
&lt;p&gt;This post covers what each piece does, how they fit together, and how to build your own.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Agent Skills: Teaching Copilot New Tricks
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.blog/changelog/2025-12-18-github-copilot-now-supports-agent-skills/" target="_blank" rel="noreferrer"&gt;Agent Skills&lt;/a&gt; were introduced in December 2025. They let you teach Copilot how to perform specialized tasks in a specific, repeatable way.&lt;/p&gt;

&lt;h3 class="relative group"&gt;What Is a Skill?
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;A skill is a &lt;strong&gt;folder&lt;/strong&gt; containing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;SKILL.md&lt;/code&gt; file with instructions and metadata&lt;/li&gt;
&lt;li&gt;Optional supporting scripts, templates, and reference files&lt;/li&gt;
&lt;li&gt;Tool and model configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When Copilot determines a skill is relevant to your prompt, it &lt;strong&gt;automatically loads&lt;/strong&gt; the instructions and follows them.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Skill Structure
 
 
 
 
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.github/skills/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; csv-analysis/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SKILL.md # Instructions (required)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; scripts/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; analyze.py # Supporting scripts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; templates/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; report.md # Output templates&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Writing a SKILL.md
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;SKILL.md&lt;/code&gt; file is the entry point. It uses YAML frontmatter for metadata and markdown for instructions:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;name: csv-analysis
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;description: Analyze CSV files and generate statistical reports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tools:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; powershell
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; view
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; create
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Instructions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;When asked to analyze a CSV file:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;1.&lt;/span&gt; Read the file and identify columns
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;2.&lt;/span&gt; Compute summary statistics per column
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;3.&lt;/span&gt; Detect missing values and outliers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. Generate a markdown report with tables and findings&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Where Skills Are Discovered
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Skills follow an &lt;strong&gt;open standard&lt;/strong&gt; (&lt;a href="https://agentskills.io" target="_blank" rel="noreferrer"&gt;agentskills.io&lt;/a&gt;) shared across leading AI coding tools. Each tool looks in slightly different locations:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Location&lt;/th&gt;
 &lt;th&gt;Tool&lt;/th&gt;
 &lt;th&gt;Scope&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.github/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Copilot CLI, VS Code&lt;/td&gt;
 &lt;td&gt;Repository&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.claude/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Claude Code, Copilot&lt;/td&gt;
 &lt;td&gt;Repository&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.agents/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Codex CLI&lt;/td&gt;
 &lt;td&gt;Repository&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;.gemini/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Gemini CLI&lt;/td&gt;
 &lt;td&gt;Repository&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;~/.copilot/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Copilot CLI&lt;/td&gt;
 &lt;td&gt;User-level&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;~/.claude/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Claude Code&lt;/td&gt;
 &lt;td&gt;User-level&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;~/.agents/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Codex CLI&lt;/td&gt;
 &lt;td&gt;User-level&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;~/.gemini/skills/&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Gemini CLI&lt;/td&gt;
 &lt;td&gt;User-level&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Installed plugins&lt;/td&gt;
 &lt;td&gt;All tools&lt;/td&gt;
 &lt;td&gt;Global&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;SKILL.md&lt;/code&gt; format is identical across all tools - write once, discovered everywhere. If you&amp;rsquo;ve set up skills for Claude Code in &lt;code&gt;.claude/skills/&lt;/code&gt;, Copilot picks them up automatically. Skills also work with the &lt;strong&gt;Copilot Coding Agent&lt;/strong&gt; on GitHub.com.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Key Design Principle: On-Demand Loading
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Skills load &lt;strong&gt;only when relevant&lt;/strong&gt;. You can define dozens of skills in a project, and Copilot will only load the ones that match the current task. This keeps context windows focused and avoids unnecessary token usage.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Skill Loading Flow: User Prompt to Copilot evaluation, loading relevant skills and executing with specified tools"
 width="901"
 height="171"
 src="/images/diagrams/skill-loading-flow.drawio_hu_b18498c455322e1a.png"
 srcset="/images/diagrams/skill-loading-flow.drawio_hu_b18498c455322e1a.png 800w,/images/diagrams/skill-loading-flow.drawio_hu_1247a3bd2ded3c17.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/skill-loading-flow.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Plugins: Package Management for Copilot
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;While skills solve the &amp;ldquo;how do I teach Copilot something new?&amp;rdquo; problem, &lt;strong&gt;plugins&lt;/strong&gt; solve the &amp;ldquo;how do I share it?&amp;rdquo; problem.&lt;/p&gt;

&lt;h3 class="relative group"&gt;What Is a Plugin?
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;A plugin is a &lt;strong&gt;distributable package&lt;/strong&gt; that bundles Copilot customizations into a single installable unit. Think of it as npm for your Copilot configurations.&lt;/p&gt;
&lt;p&gt;A plugin can contain any combination of:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Component&lt;/th&gt;
 &lt;th&gt;File Pattern&lt;/th&gt;
 &lt;th&gt;Purpose&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Custom Agents&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;agents/*.agent.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Specialized AI assistants with defined personas&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Skills&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;skills/*/SKILL.md&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Discrete callable capabilities&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Hooks&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;hooks.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Event handlers for agent lifecycle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;MCP Servers&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.mcp.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Model Context Protocol integrations&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;LSP Servers&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;lsp.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Language Server Protocol integrations&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;Creating a Plugin
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Every plugin needs a &lt;code&gt;plugin.json&lt;/code&gt; manifest in a &lt;code&gt;.github/&lt;/code&gt; subdirectory:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;my-plugin/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .github/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; plugin.json # Required manifest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; agents/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-agent.agent.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; skills/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-skill/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SKILL.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hooks.json&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The manifest declares what the plugin contains. Here&amp;rsquo;s the real &lt;code&gt;plugin.json&lt;/code&gt; from the &lt;a href="https://github.com/codebytes/agent-skills" target="_blank" rel="noreferrer"&gt;agent-skills&lt;/a&gt; demo repo:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;document-tools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Document processing toolkit with CSV analysis, report generation, and data profiling capabilities&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;author&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Chris Ayers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;noreply@chris-ayers.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;license&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;MIT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;keywords&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;csv&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;analysis&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;reporting&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;agents&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;./agents/data-analyst.agent.md&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;skills&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;./skills/csv-analysis/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Important details:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; field should only contain letters, numbers, and dashes&lt;/li&gt;
&lt;li&gt;File paths are relative to the &lt;strong&gt;plugin root&lt;/strong&gt;, not the &lt;code&gt;.github/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;When layering a plugin onto an existing repo, paths may need &lt;code&gt;../../&lt;/code&gt; navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Why Plugins Over Manual Config?
 
 
 
 
&lt;/h3&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Feature&lt;/th&gt;
 &lt;th&gt;Manual Configuration&lt;/th&gt;
 &lt;th&gt;Plugins&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Scope&lt;/td&gt;
 &lt;td&gt;Single repository&lt;/td&gt;
 &lt;td&gt;Any project&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Sharing&lt;/td&gt;
 &lt;td&gt;Copy/paste files&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;copilot plugin install&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Versioning&lt;/td&gt;
 &lt;td&gt;Git history&lt;/td&gt;
 &lt;td&gt;Marketplace versions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Discovery&lt;/td&gt;
 &lt;td&gt;Searching repositories&lt;/td&gt;
 &lt;td&gt;Marketplace browsing&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Updates&lt;/td&gt;
 &lt;td&gt;Manual tracking&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;copilot plugin update&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 class="relative group"&gt;Marketplaces: App Stores for Plugins
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;A &lt;strong&gt;marketplace&lt;/strong&gt; is a Git repository that serves as a registry for available plugins. It&amp;rsquo;s a lightweight app store - a &lt;code&gt;marketplace.json&lt;/code&gt; file that lists plugins with metadata.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Default Marketplaces
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Both VS Code and Copilot CLI ship with two marketplaces pre-configured:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Marketplace&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;a href="https://github.com/github/copilot-plugins" target="_blank" rel="noreferrer"&gt;copilot-plugins&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Official GitHub plugins&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;a href="https://github.com/github/awesome-copilot" target="_blank" rel="noreferrer"&gt;awesome-copilot&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;Community-contributed plugins&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;Creating a Marketplace
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;.github/plugin/marketplace.json&lt;/code&gt; in a Git repository:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;my-team-plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;owner&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;My Team&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;team@example.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Internal team plugin registry&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dev-toolkit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Full-stack development tools&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./plugins/dev-toolkit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Push to GitHub and you have a working marketplace.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Marketplace Structure: Repository containing marketplace.json linking to plugins with their components"
 width="881"
 height="326"
 src="/images/diagrams/marketplace-structure.drawio_hu_288c516997ebf4cf.png"
 srcset="/images/diagrams/marketplace-structure.drawio_hu_288c516997ebf4cf.png 800w,/images/diagrams/marketplace-structure.drawio_hu_40b87075fd44b2d6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/marketplace-structure.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Versioning with External Sources
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Plugins can reference external repositories with specific tags:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;external-tool&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;source&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://github.com/org/tool-plugin&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ref&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;v2.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This lets your marketplace curate plugins from multiple repositories, each pinned to a specific version.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Cross-Tool Compatibility
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;One of the most powerful aspects of the emerging plugin ecosystem is &lt;strong&gt;cross-tool portability&lt;/strong&gt;. The SKILL.md format follows an open standard shared across multiple AI coding tools.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Compatibility Matrix
 
 
 
 
&lt;/h3&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Capability&lt;/th&gt;
 &lt;th&gt;Copilot CLI&lt;/th&gt;
 &lt;th&gt;VS Code&lt;/th&gt;
 &lt;th&gt;Claude Code&lt;/th&gt;
 &lt;th&gt;Codex CLI&lt;/th&gt;
 &lt;th&gt;Gemini CLI&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Skills (SKILL.md)&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Custom Agents&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Partial&lt;/td&gt;
 &lt;td&gt;Partial&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Plugin Manifest&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.github/plugin.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.github/plugin.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;N/A&lt;/td&gt;
 &lt;td&gt;Extensions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Marketplace&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes &lt;code&gt;marketplace.json&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Yes Same&lt;/td&gt;
 &lt;td&gt;Yes Same concept&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;$skill-installer&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gemini extensions&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Hooks&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;MCP Servers&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;LSP Servers&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;Yes&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;td&gt;No&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;Making Plugins Work Everywhere
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;To maximize compatibility, include &lt;strong&gt;both&lt;/strong&gt; plugin manifests:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;my-plugin/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .github/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; plugin.json # Copilot CLI + VS Code
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .claude-plugin/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; plugin.json # Claude Code
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; agents/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-agent.agent.md # Shared across tools
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; skills/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-skill/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SKILL.md # Universal format
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hooks.json # Copilot + Claude&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;For &lt;strong&gt;Codex CLI&lt;/strong&gt; and &lt;strong&gt;Gemini CLI&lt;/strong&gt; users, also provide skills at the repo root:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;my-repo/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .agents/skills/ # Codex CLI discovery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-skill/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SKILL.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .gemini/skills/ # Gemini CLI discovery
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-skill/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SKILL.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; plugins/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; my-plugin/ # Full plugin for Copilot + Claude
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .github/plugin/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; marketplace.json # Marketplace registry&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Key Takeaway
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Skills are the most portable&lt;/strong&gt; - the SKILL.md format works identically across all five tools. &lt;strong&gt;Plugins&lt;/strong&gt; require dual manifests (&lt;code&gt;.github/&lt;/code&gt; + &lt;code&gt;.claude-plugin/&lt;/code&gt;) for full Copilot and Claude support. &lt;strong&gt;Codex&lt;/strong&gt; uses &lt;code&gt;.agents/skills/&lt;/code&gt; and community registries, while &lt;strong&gt;Gemini&lt;/strong&gt; uses &lt;code&gt;.gemini/skills/&lt;/code&gt; and its own extensions system.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Installing and Managing Plugins
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Copilot CLI
 
 
 
 
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Browse default marketplaces&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin marketplace list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin marketplace browse awesome-copilot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Add a custom marketplace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin marketplace add my-org/internal-plugins
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install from a marketplace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin install dev-toolkit@awesome-copilot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install directly from a repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin install user/repo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin install user/repo:plugins/subfolder
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Manage installed plugins&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin update my-plugin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin uninstall my-plugin&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;VS Code
 
 
 
 
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Set &lt;code&gt;chat.plugins.enabled&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in settings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add marketplaces to &lt;code&gt;chat.plugins.marketplaces&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;chat.plugins.marketplaces&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-org/my-plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Browse with &lt;code&gt;@agentPlugins&lt;/code&gt; in the Extensions view&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Or use Command Palette then &lt;strong&gt;Chat: Plugins&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Marketplace settings must be at the &lt;strong&gt;user level&lt;/strong&gt; - workspace settings won&amp;rsquo;t work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 class="relative group"&gt;Runtime Behavior
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;After installation, components integrate automatically:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Plugin Runtime Behavior: Install flows through registry to agents, skills, hooks, and MCP servers feeding into Copilot session"
 width="781"
 height="241"
 src="/images/diagrams/runtime-behavior.drawio_hu_306590865efaddf6.png"
 srcset="/images/diagrams/runtime-behavior.drawio_hu_306590865efaddf6.png 800w,/images/diagrams/runtime-behavior.drawio_hu_524f440bb6e51831.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/runtime-behavior.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt; appear in your agent selection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt; load when relevant to your task&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hooks&lt;/strong&gt; fire at lifecycle events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP servers&lt;/strong&gt; extend the available tool set&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No additional configuration needed.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Where Plugins Are Stored
 
 
 
 
&lt;/h3&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Source&lt;/th&gt;
 &lt;th&gt;Location&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Marketplace plugins&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.copilot/installed-plugins/MARKETPLACE/PLUGIN/&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Direct installs&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/.copilot/installed-plugins/_direct/PLUGIN/&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VS Code (macOS)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;~/Library/Application Support/Code/agentPlugins/&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VS Code (Windows)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;%APPDATA%/Code/agentPlugins/&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Knowing where plugins land is useful for debugging — if a plugin isn&amp;rsquo;t loading, check these paths to verify installation succeeded.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Building a Plugin: Step by Step
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/codebytes/agent-skills" target="_blank" rel="noreferrer"&gt;agent-skills&lt;/a&gt; repo demonstrates a complete working example. Here&amp;rsquo;s the workflow:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Create the structure&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p my-plugin/.github
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p my-plugin/agents
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p my-plugin/skills/csv-analysis&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;2. Write the plugin manifest&lt;/strong&gt; (&lt;code&gt;my-plugin/.github/plugin.json&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;Note: paths are relative to the &lt;strong&gt;plugin root&lt;/strong&gt;, not the &lt;code&gt;.github/&lt;/code&gt; directory, so they start with &lt;code&gt;../&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Define an agent&lt;/strong&gt; — a persona with specific tools and a workflow (see &lt;code&gt;agents/data-analyst.agent.md&lt;/code&gt; in the demo repo for a full example).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Define a skill&lt;/strong&gt; — a focused, on-demand capability with a &lt;code&gt;SKILL.md&lt;/code&gt; frontmatter declaring its &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and required &lt;code&gt;tools&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Add a &lt;code&gt;hooks.json&lt;/code&gt;&lt;/strong&gt; to fire notifications or setup steps at lifecycle events:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;hooks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;subagentStart&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;echo &amp;#39;Document tools plugin loaded — data analysis capabilities active&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Notify when document tools plugin activates&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;6. Publish as a marketplace&lt;/strong&gt; — push the repo to GitHub, then anyone can register it:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin marketplace add codebytes/agent-skills
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;copilot plugin install document-tools@agent-skills&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;s it. The agent and skills are now available across every project.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Security Considerations
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The plugin system operates within Copilot&amp;rsquo;s standard permission framework:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Security Permissions Flow: Plugin tool calls checked for permissions, with skipPermission bypass or user approval paths"
 width="771"
 height="226"
 src="/images/diagrams/security-permissions.drawio_hu_42386148fcedb8f8.png"
 srcset="/images/diagrams/security-permissions.drawio_hu_42386148fcedb8f8.png 800w,/images/diagrams/security-permissions.drawio_hu_fe7604a531b02893.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/security-permissions.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Folder trust&lt;/strong&gt; - Repository-level hooks only load after user confirms trust&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool permissions&lt;/strong&gt; - Standard approval prompts for plugin tools&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;skipPermission&lt;/strong&gt; - Authors can mark known-safe operations to skip prompts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP allowlists&lt;/strong&gt; - Restrict servers with &lt;code&gt;MCP_ALLOWLIST&lt;/code&gt; feature flags&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review before install&lt;/strong&gt; - Always inspect unfamiliar plugins before installing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Best Practices
 
 
 
 
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start with existing plugins&lt;/strong&gt; - Browse &lt;code&gt;awesome-copilot&lt;/code&gt; and &lt;code&gt;copilot-plugins&lt;/code&gt; before building your own&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep plugins focused&lt;/strong&gt; - &amp;ldquo;Rails development&amp;rdquo; is better than &amp;ldquo;everything&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test with CLI first&lt;/strong&gt; - Better error messages than VS Code for debugging&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version with tags&lt;/strong&gt; - Pin external sources to specific refs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use for team standards&lt;/strong&gt; - Publish internal plugins for consistent tooling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update regularly&lt;/strong&gt; - &lt;code&gt;copilot plugin update&lt;/code&gt; for latest improvements&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review what you install&lt;/strong&gt; - Plugins run code on your machine&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Known Limitations (Preview)
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;As of early 2026, these features are still in preview:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VS Code settings scope&lt;/strong&gt;: Marketplaces must be user-level, not workspace&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dev containers&lt;/strong&gt;: Plugin installation may not work inside containers&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Silent failures&lt;/strong&gt;: Manifest errors cause feeds to silently not appear&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aggressive caching&lt;/strong&gt;: VS Code caches feed details; may need reload or alternate URL format&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch installs&lt;/strong&gt;: CLI doesn&amp;rsquo;t yet support installing from specific branches directly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use VS Code Insiders for the latest fixes, and validate with Copilot CLI before VS Code.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Big Picture
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Agent Skills Architecture: Marketplace, Plugins, and Components"
 width="802"
 height="352"
 src="/images/diagrams/agent-skills-architecture.drawio_hu_c86ae8d5bbf6f42.png"
 srcset="/images/diagrams/agent-skills-architecture.drawio_hu_c86ae8d5bbf6f42.png 800w,/images/diagrams/agent-skills-architecture.drawio_hu_aeddd93894c132c4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/agent-skills-architecture.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The ecosystem follows a clear layering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt; teach Copilot specific tasks&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt; provide specialized personas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugins&lt;/strong&gt; package everything together&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marketplaces&lt;/strong&gt; make it all discoverable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/codebytes/agent-skills" target="_blank" rel="noreferrer"&gt;Demo Repo: codebytes/agent-skills&lt;/a&gt; — Complete working plugin + marketplace example&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/concepts/agents/copilot-cli/about-cli-plugins" target="_blank" rel="noreferrer"&gt;GitHub Docs: About CLI Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://awesome-copilot.github.com/learning-hub/installing-and-using-plugins/" target="_blank" rel="noreferrer"&gt;awesome-copilot: Installing and Using Plugins&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/changelog/2025-12-18-github-copilot-now-supports-agent-skills/" target="_blank" rel="noreferrer"&gt;GitHub Changelog: Agent Skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/github/copilot-plugins" target="_blank" rel="noreferrer"&gt;copilot-plugins Registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/github/awesome-copilot" target="_blank" rel="noreferrer"&gt;awesome-copilot Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Microsoft Certification Renewals - March 2026</title><link>https://chris-ayers.com/posts/certification-renewals-march-2026/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/certification-renewals-march-2026/</guid><description>&lt;p&gt;I just renewed six Microsoft certifications, keeping my skills current across Azure architecture, security, networking, and AI.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I just renewed six Microsoft certifications, keeping my skills current across Azure architecture, security, networking, and AI.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why Renewals Matter
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I started pursuing certifications early in my career as a consultant at a professional services company. They encouraged it, and it helped with competencies and career growth. When you&amp;rsquo;re helping customers adopt a technology, having the certification carries authority - it shows you&amp;rsquo;ve put in the work to understand the platform deeply.&lt;/p&gt;
&lt;p&gt;Beyond the professional benefits, certifications force you to learn. The renewal process keeps you up to date with the latest features and changes, and it pushes you into areas of a technology that you might not use day to day. You end up with a much more complete understanding of the platform, not just the parts you happen to work with.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Staying Current
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Microsoft role-based and specialty certifications require periodic renewal to ensure credential holders stay up to date with the latest technologies. Rather than retaking full proctored exams, Microsoft offers free online renewal assessments through &lt;a href="https://learn.microsoft.com" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;. These assessments cover the latest updates and features added since the last renewal cycle.&lt;/p&gt;
&lt;p&gt;On March 2, 2026, I completed renewal assessments for all six of my expiring certifications in a single session.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Certifications Renewed
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Expert Level
 
 
 
 
&lt;/h3&gt;


 
 &lt;div class="width-patch"&gt;&lt;/div&gt;
&lt;div id="gallery-873f2716c7d1d7eb254c6286919b8190" class="gallery"&gt;
 
 &lt;img src="/images/badges/microsoft-certified-cybersecurity-architect-expert.png" class="grid-w33" alt="Cybersecurity Architect Expert" /&gt;
 &lt;img src="/images/badges/microsoft-certified-azure-solutions-architect-expert.png" class="grid-w33" alt="Azure Solutions Architect Expert" /&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Certified: Cybersecurity Architect Expert&lt;/strong&gt; - Designing and evolving cybersecurity strategy for an organization, including zero-trust architecture, governance, risk, and compliance (GRC), and security operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Certified: Azure Solutions Architect Expert&lt;/strong&gt; - Designing and implementing solutions on Azure, including compute, network, storage, monitoring, and security&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Associate Level
 
 
 
 
&lt;/h3&gt;


 
 
 
 &lt;div class="width-patch"&gt;&lt;/div&gt;
&lt;div id="gallery-7e103a4f1b18036b95f28c561a9e9b26" class="gallery"&gt;
 
 &lt;img src="/images/badges/microsoft-certified-security-operations-analyst-associate.png" class="grid-w25" alt="Security Operations Analyst Associate" /&gt;
 &lt;img src="/images/badges/microsoft-certified-azure-ai-engineer-associate.png" class="grid-w25" alt="Azure AI Engineer Associate" /&gt;
 &lt;img src="/images/badges/microsoft-certified-azure-network-engineer-associate.png" class="grid-w25" alt="Azure Network Engineer Associate" /&gt;
 &lt;img src="/images/badges/microsoft-certified-azure-security-engineer-associate.png" class="grid-w25" alt="Azure Security Engineer Associate" /&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Certified: Security Operations Analyst Associate&lt;/strong&gt; - Investigating, searching for, and mitigating threats using Microsoft Sentinel, Microsoft Defender for Cloud, and Microsoft Defender XDR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Certified: Azure AI Engineer Associate&lt;/strong&gt; - Designing and implementing Azure AI solutions using Azure AI services, Azure AI Search, and Azure OpenAI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Certified: Azure Network Engineer Associate&lt;/strong&gt; - Designing, implementing, and maintaining Azure networking solutions including hybrid networking, connectivity, routing, and security&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Certified: Azure Security Engineer Associate&lt;/strong&gt; - Implementing security controls, managing identity and access, and protecting data, applications, and networks in Azure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For anyone considering Microsoft certifications, the renewal process is straightforward and free. You can complete the assessments at your own pace on &lt;a href="https://learn.microsoft.com/en-us/credentials/" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, and they cover the latest updates since your last renewal cycle.&lt;/p&gt;</content:encoded></item><item><title>MITRE ATT&amp;CK for Developers: Beyond OWASP</title><link>https://chris-ayers.com/posts/mitre-attack-framework/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/mitre-attack-framework/</guid><description>&lt;p&gt;Most developers know the OWASP Top 10, but fewer know MITRE ATT&amp;amp;CK. OWASP tells you what can break. ATT&amp;amp;CK tells you how attackers actually operate. Together, they give you a complete picture of application security.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I&amp;rsquo;ll be presenting this topic at &lt;a href="https://ndcsecurity.com/" target="_blank" rel="noreferrer"&gt;NDC Security 2026&lt;/a&gt; in Oslo, March 2-5. If you&amp;rsquo;re attending, come check out my talk — &lt;strong&gt;MITRE ATT&amp;amp;CK for Developers&lt;/strong&gt; — on Wednesday, March 4 at 10:20.&lt;/p&gt;
&lt;/blockquote&gt;</description><content:encoded>&lt;p&gt;Most developers know the OWASP Top 10, but fewer know MITRE ATT&amp;amp;CK. OWASP tells you what can break. ATT&amp;amp;CK tells you how attackers actually operate. Together, they give you a complete picture of application security.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;I&amp;rsquo;ll be presenting this topic at &lt;a href="https://ndcsecurity.com/" target="_blank" rel="noreferrer"&gt;NDC Security 2026&lt;/a&gt; in Oslo, March 2-5. If you&amp;rsquo;re attending, come check out my talk — &lt;strong&gt;MITRE ATT&amp;amp;CK for Developers&lt;/strong&gt; — on Wednesday, March 4 at 10:20.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;The Security Challenge
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Modern applications have a massive attack surface — APIs, microservices, cloud resources — every one a potential entry point. Attackers chain multiple techniques across stages, adapting as they go. Traditional defenses focus on single points of failure, but sophisticated attacks slip through.&lt;/p&gt;
&lt;p&gt;The uncomfortable truth: attackers iterate faster than our defenses.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What is OWASP
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://owasp.org/" target="_blank" rel="noreferrer"&gt;OWASP&lt;/a&gt; is a community-driven organization that publishes security standards for web applications. The &lt;strong&gt;OWASP Top 10&lt;/strong&gt; catalogs the most critical risks:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;Category&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;A01&lt;/td&gt;
 &lt;td&gt;Broken Access Control&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A02&lt;/td&gt;
 &lt;td&gt;Cryptographic Failures&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A03&lt;/td&gt;
 &lt;td&gt;Injection&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A04&lt;/td&gt;
 &lt;td&gt;Insecure Design&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A05&lt;/td&gt;
 &lt;td&gt;Security Misconfiguration&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A06&lt;/td&gt;
 &lt;td&gt;Vulnerable and Outdated Components&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A07&lt;/td&gt;
 &lt;td&gt;Identification and Authentication Failures&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A08&lt;/td&gt;
 &lt;td&gt;Software and Data Integrity Failures&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A09&lt;/td&gt;
 &lt;td&gt;Security Logging and Monitoring Failures&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A10&lt;/td&gt;
 &lt;td&gt;Server-Side Request Forgery (SSRF)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;OWASP&amp;rsquo;s strength is &lt;strong&gt;prevention&lt;/strong&gt; — it tells you what vulnerabilities exist and how to fix them.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What is MITRE
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.mitre.org/" target="_blank" rel="noreferrer"&gt;MITRE&lt;/a&gt; is a not-for-profit, vendor-neutral organization founded in 1958. They maintain an ecosystem of interlocking cybersecurity frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CVE&lt;/strong&gt; - catalogs specific vulnerabilities in software&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CWE&lt;/strong&gt; - classifies underlying weakness types in code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CAPEC&lt;/strong&gt; - documents attack patterns&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ATT&amp;amp;CK&lt;/strong&gt; - maps real-world adversary behavior&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D3FEND&lt;/strong&gt; - catalogs defensive countermeasures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ATLAS&lt;/strong&gt; - covers AI and machine learning threats&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="MITRE Cybersecurity Ecosystem"
 width="714"
 height="269"
 src="/images/mitre-ecosystem.drawio_hu_38ab4dabd56800e5.png"
 srcset="/images/mitre-ecosystem.drawio_hu_38ab4dabd56800e5.png 800w,/images/mitre-ecosystem.drawio_hu_7b6d8eb886b6b8f8.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/mitre-ecosystem.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;What is MITRE ATT&amp;amp;CK
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://attack.mitre.org/" target="_blank" rel="noreferrer"&gt;MITRE ATT&amp;amp;CK&lt;/a&gt; (Adversarial Tactics, Techniques, and Common Knowledge) is a knowledge base of adversary behavior based on real-world cyber attacks. Born from a 2013 experiment at Fort Meade, it catalogs what attackers actually do across the full attack lifecycle.&lt;/p&gt;
&lt;p&gt;The Enterprise Matrix has &lt;strong&gt;14 tactics&lt;/strong&gt;, over &lt;strong&gt;200 techniques&lt;/strong&gt;, and &lt;strong&gt;400+ sub-techniques&lt;/strong&gt;.&lt;/p&gt;

&lt;h3 class="relative group"&gt;How It Is Organized
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tactics&lt;/strong&gt; - the &amp;ldquo;why&amp;rdquo; (the adversary&amp;rsquo;s goal)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Techniques&lt;/strong&gt; - the &amp;ldquo;how&amp;rdquo; (the method used)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sub-techniques&lt;/strong&gt; - specific implementations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Procedures&lt;/strong&gt; - real-world examples from threat actors&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;The 14 Tactics
 
 
 
 
&lt;/h3&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Phase&lt;/th&gt;
 &lt;th&gt;Tactics&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Pre-Attack&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Reconnaissance, Resource Development&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Get In&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Initial Access, Execution, Persistence, Privilege Escalation&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Stay In&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Defense Evasion, Credential Access, Discovery, Lateral Movement&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Act&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Collection, Command and Control, Exfiltration, Impact&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="The 14 ATT&amp;amp;CK Tactics"
 width="622"
 height="482"
 src="/images/14-attack-tactics.drawio_hu_a3791e270d6ca9f1.png"
 srcset="/images/14-attack-tactics.drawio_hu_a3791e270d6ca9f1.png 800w,/images/14-attack-tactics.drawio_hu_eac5821788cd1c2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/14-attack-tactics.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;OWASP vs ATT&amp;amp;CK: Why You Need Both
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;These frameworks are &lt;strong&gt;complementary, not competitive&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;OWASP&lt;/th&gt;
 &lt;th&gt;MITRE ATT&amp;amp;CK&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Focus&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Vulnerabilities&lt;/td&gt;
 &lt;td&gt;Adversary behavior&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Perspective&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;What breaks&lt;/td&gt;
 &lt;td&gt;What attackers do&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Approach&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Prevention-first&lt;/td&gt;
 &lt;td&gt;Detection-oriented&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Application layer&lt;/td&gt;
 &lt;td&gt;Full attack lifecycle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="OWASP and ATT&amp;amp;CK Integration"
 width="697"
 height="356"
 src="/images/owasp-attack-integration.drawio_hu_9e042e836bb5201.png"
 srcset="/images/owasp-attack-integration.drawio_hu_9e042e836bb5201.png 800w,/images/owasp-attack-integration.drawio_hu_7755c61971858fd5.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/owasp-attack-integration.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;OWASP prevents vulnerabilities. ATT&amp;amp;CK detects adversary behavior.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;A single OWASP vulnerability can enable multiple ATT&amp;amp;CK techniques:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;OWASP Category&lt;/th&gt;
 &lt;th&gt;ATT&amp;amp;CK Techniques&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Broken Access Control&lt;/td&gt;
 &lt;td&gt;T1078 (Valid Accounts), T1098 (Account Manipulation), T1068 (Privilege Escalation)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Injection&lt;/td&gt;
 &lt;td&gt;T1190 (Exploit Public-Facing App), T1059 (Command Injection)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Security Misconfiguration&lt;/td&gt;
 &lt;td&gt;T1552 (Unsecured Credentials), T1082 (System Info Discovery)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cryptographic Failures&lt;/td&gt;
 &lt;td&gt;T1555 (Credentials from Password Stores), T1565 (Data Manipulation)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Authentication Failures&lt;/td&gt;
 &lt;td&gt;T1087 (Account Discovery), T1110 (Brute Force)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 class="relative group"&gt;Why Developers Should Care
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;You already use MITRE data.&lt;/strong&gt; Tools like Dependabot, Snyk, and Trivy report CVEs and CWEs under the hood. Understanding the ecosystem helps you make better decisions about what to fix and why.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shared vocabulary.&lt;/strong&gt; When your SOC team says &amp;ldquo;we detected T1190,&amp;rdquo; you&amp;rsquo;ll know they mean exploitation of a public-facing application and can reason about which services might be affected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your code is the detection layer.&lt;/strong&gt; The logs and telemetry you emit feed directly into ATT&amp;amp;CK-based detection systems. Better observability means faster threat detection.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Attack Chains Are Real
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Attackers chain multiple techniques together. A supply chain attack might unfold like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Attacker publishes a typosquatted npm package&lt;/li&gt;
&lt;li&gt;Post-install script executes a payload&lt;/li&gt;
&lt;li&gt;Payload harvests credentials from environment variables&lt;/li&gt;
&lt;li&gt;Stolen credentials provide production access&lt;/li&gt;
&lt;li&gt;Attacker creates a backdoor IAM user for persistence&lt;/li&gt;
&lt;li&gt;Customer data is exfiltrated&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Supply Chain Attack Chain"
 width="908"
 height="63"
 src="/images/attack-chain-supply.drawio_hu_b4f31ebcbf590de9.png"
 srcset="/images/attack-chain-supply.drawio_hu_b4f31ebcbf590de9.png 800w,/images/attack-chain-supply.drawio_hu_695e60a4d09681c4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/attack-chain-supply.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Six techniques, six tactics, one continuous attack. Defend only at the perimeter and you miss five of six detection opportunities. Real attacks also loop and backtrack — ATT&amp;amp;CK captures this reality with a matrix rather than a linear chain.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What You Can Do Today
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Start with three high-impact techniques:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;T1078 - Valid Accounts&lt;/strong&gt; — monitor authentication patterns for credential stuffing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1539 - Steal Web Session Cookie&lt;/strong&gt; — secure session management with fingerprinting and short expiration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;T1213 - Data from Information Repositories&lt;/strong&gt; — behavioral analytics to detect unusual data access&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then build from there:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Map features to techniques&lt;/strong&gt; — identify which ATT&amp;amp;CK techniques each feature could enable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tag logs with technique IDs&lt;/strong&gt; — so your SIEM can correlate events&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the ATT&amp;amp;CK Navigator&lt;/strong&gt; — visualize coverage and gaps&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Include ATT&amp;amp;CK in threat modeling&lt;/strong&gt; — consider how attackers would chain techniques&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review code for both&lt;/strong&gt; — OWASP vulnerabilities AND technique resistance&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Defense in Depth Architecture"
 width="547"
 height="313"
 src="/images/defense-in-depth.drawio_hu_1389b53fe73328ef.png"
 srcset="/images/defense-in-depth.drawio_hu_1389b53fe73328ef.png 800w,/images/defense-in-depth.drawio_hu_9bf207b86ee2e453.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/defense-in-depth.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://attack.mitre.org/matrices/enterprise/" target="_blank" rel="noreferrer"&gt;MITRE ATT&amp;amp;CK Enterprise Matrix&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mitre-attack.github.io/attack-navigator/" target="_blank" rel="noreferrer"&gt;ATT&amp;amp;CK Navigator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-project-top-ten/" target="_blank" rel="noreferrer"&gt;OWASP Top 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-project-developer-guide/" target="_blank" rel="noreferrer"&gt;OWASP Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://d3fend.mitre.org/" target="_blank" rel="noreferrer"&gt;MITRE D3FEND&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;OWASP and ATT&amp;amp;CK are two sides of the same coin — prevent what you can, detect what you cannot. Understanding how attackers operate makes you a better developer. Start small, pick a few high-impact techniques, and build from there.&lt;/p&gt;</content:encoded></item><item><title>NDC Security 2026</title><link>https://chris-ayers.com/posts/ndc-security-2026/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/ndc-security-2026/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="NDC Security 2026 - MITRE ATT&amp;amp;CK for Developers"
 width="1500"
 height="1500"
 src="/images/ndc-security-2026-mitre-attck-for-developers_hu_3592c95e6139c249.jpg"
 srcset="/images/ndc-security-2026-mitre-attck-for-developers_hu_3592c95e6139c249.jpg 800w, /images/ndc-security-2026-mitre-attck-for-developers_hu_281f253683067ced.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/ndc-security-2026-mitre-attck-for-developers.jpg"&gt;&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m excited to be speaking at &lt;a href="https://ndcsecurity.com/" target="_blank" rel="noreferrer"&gt;NDC Security 2026&lt;/a&gt; in Oslo, March 2-5! I&amp;rsquo;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;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="NDC Security 2026 - MITRE ATT&amp;amp;CK for Developers"
 width="1500"
 height="1500"
 src="/images/ndc-security-2026-mitre-attck-for-developers_hu_3592c95e6139c249.jpg"
 srcset="/images/ndc-security-2026-mitre-attck-for-developers_hu_3592c95e6139c249.jpg 800w, /images/ndc-security-2026-mitre-attck-for-developers_hu_281f253683067ced.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/ndc-security-2026-mitre-attck-for-developers.jpg"&gt;&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m excited to be speaking at &lt;a href="https://ndcsecurity.com/" target="_blank" rel="noreferrer"&gt;NDC Security 2026&lt;/a&gt; in Oslo, March 2-5! I&amp;rsquo;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 class="relative group"&gt;About NDC Security
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ndcsecurity.com/" target="_blank" rel="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 class="relative group"&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&amp;rsquo;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="/posts/mitre-attack-framework/" &gt;MITRE ATT&amp;amp;CK for Developers: Beyond OWASP&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conference Highlights
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Notable Sessions
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;A few talks I&amp;rsquo;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&amp;rsquo;ve Never Seen&lt;/strong&gt; — Scott Helme&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;See You There
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re attending NDC Security, come say hello! You can check out the full agenda and grab tickets at &lt;a href="https://ndcsecurity.com/" target="_blank" rel="noreferrer"&gt;ndcsecurity.com&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Promoted to Principal Software Engineer at Microsoft</title><link>https://chris-ayers.com/posts/promoted-to-principal-swe/</link><pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/promoted-to-principal-swe/</guid><description>&lt;p&gt;I&amp;rsquo;m thrilled to share that I&amp;rsquo;ve been promoted to &lt;strong&gt;Principal Software Engineer&lt;/strong&gt; at Microsoft!&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;m thrilled to share that I&amp;rsquo;ve been promoted to &lt;strong&gt;Principal Software Engineer&lt;/strong&gt; at Microsoft!&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Journey
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s been an incredible four and a half years at Microsoft, and this milestone still feels a bit surreal. When I &lt;a href="/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&amp;rsquo;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="/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&amp;rsquo;s reliability at scale. It was a big change, but it reinforced something I&amp;rsquo;ve always believed - growth comes from stepping into the unfamiliar.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Looking Forward
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;As a Principal Software Engineer, I&amp;rsquo;m continuing to focus on Azure reliability - building tools and systems that help keep the platform running smoothly for millions of users worldwide. I&amp;rsquo;m also doubling down on the things I&amp;rsquo;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&amp;rsquo;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 class="relative group"&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&amp;rsquo;ve been working - constantly sharing knowledge, educating the team, and helping set technical direction. Whether it&amp;rsquo;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&amp;rsquo;s what got me here, and it&amp;rsquo;s what I&amp;rsquo;ll keep doing.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Gratitude
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;None of this happens alone. I&amp;rsquo;m grateful for the incredible teammates and mentors I&amp;rsquo;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&amp;rsquo;re on your own career journey, here&amp;rsquo;s what I&amp;rsquo;d say: don&amp;rsquo;t be afraid to take the leap into unfamiliar territory. The skills you&amp;rsquo;ve built are more transferable than you think, and the best growth happens outside your comfort zone.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s to the next chapter. Let&amp;rsquo;s build something great.&lt;/p&gt;</content:encoded></item><item><title>Aspire CLI Part 3 - MCP for AI Coding Agents</title><link>https://chris-ayers.com/posts/aspire-cli-part-3-mcp/</link><pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/aspire-cli-part-3-mcp/</guid><description>&lt;p&gt;In &lt;a href="/posts/aspire-cli-getting-started/" &gt;Part 1&lt;/a&gt;, we covered creating and running Aspire apps. In &lt;a href="/posts/aspire-cli-part-2/" &gt;Part 2&lt;/a&gt;, we explored deployment and CI/CD. Now let&amp;rsquo;s look at one of Aspire&amp;rsquo;s most exciting features: MCP (Model Context Protocol) support for AI coding agents.&lt;/p&gt;</description><content:encoded>&lt;p&gt;In &lt;a href="/posts/aspire-cli-getting-started/" &gt;Part 1&lt;/a&gt;, we covered creating and running Aspire apps. In &lt;a href="/posts/aspire-cli-part-2/" &gt;Part 2&lt;/a&gt;, we explored deployment and CI/CD. Now let&amp;rsquo;s look at one of Aspire&amp;rsquo;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 class="relative group"&gt;What is Aspire MCP?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The Aspire MCP server is a local &lt;a href="https://modelcontextprotocol.io/" target="_blank" rel="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" target="_blank" rel="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 class="relative group"&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 class="relative group"&gt;Getting Started
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; your-aspire-project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire mcp init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;servers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;aspire&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stdio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aspire&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mcp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If you don&amp;rsquo;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 class="relative group"&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;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;p&gt;This approach supports additional assistants including Visual Studio, Cursor, and OpenAI Codex. Consult your tool&amp;rsquo;s MCP documentation for configuration details.&lt;/p&gt;

&lt;h2 class="relative group"&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 class="relative group"&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 class="relative group"&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 class="relative group"&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 class="relative group"&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 class="relative group"&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;&amp;ldquo;Are all my resources running?&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Show me the last 50 log entries from the API service&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Analyze HTTP request performance for the webfrontend&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;What traces show errors in the last 5 minutes?&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Restart unhealthy resources&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;What Aspire integrations are available for Redis?&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Excluding Resources from MCP
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;You may have resources with sensitive data that shouldn&amp;rsquo;t be exposed to AI assistants. Annotate them in your AppHost:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiservice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExcludeFromMcp&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// This resource hidden from MCP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;web&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiservice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;A Practical Example
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Consider a polyglot application using the Aspire CLI with AI-assisted development. Here&amp;rsquo;s a sample AppHost from the &lt;a href="https://github.com/davidfowl/aspire-13-samples" target="_blank" rel="noreferrer"&gt;Aspire 13 samples&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hosting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hosting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Python&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hosting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docker&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="p"&gt;-*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppHost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDockerComposeEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;openai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddUvicornApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ai-agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;./agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;main:app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;&amp;ldquo;Is the ai-agent resource healthy?&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Show me any errors in the agent logs&amp;rdquo;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;What OpenTelemetry traces have the longest duration?&amp;rdquo;&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 class="relative group"&gt;Supported AI Assistants
 
 
 
 
&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Assistant&lt;/th&gt;
 &lt;th style="text-align: center"&gt;&lt;code&gt;aspire mcp init&lt;/code&gt;&lt;/th&gt;
 &lt;th style="text-align: center"&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 style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://code.visualstudio.com/docs/copilot/customization/mcp-servers" target="_blank" rel="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 style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td style="text-align: center"&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" target="_blank" rel="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 style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.claude.com/en/docs/claude-code/mcp" target="_blank" rel="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 style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://opencode.ai" target="_blank" rel="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 style="text-align: center"&gt;—&lt;/td&gt;
 &lt;td style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://learn.microsoft.com/visualstudio/ide/mcp-servers" target="_blank" rel="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 style="text-align: center"&gt;—&lt;/td&gt;
 &lt;td style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://cursor.com/docs/context/mcp#installing-mcp-servers" target="_blank" rel="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 style="text-align: center"&gt;—&lt;/td&gt;
 &lt;td style="text-align: center"&gt;✅&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://developers.openai.com/codex/mcp/" target="_blank" rel="noreferrer"&gt;MCP setup&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 class="relative group"&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&amp;rsquo;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" target="_blank" rel="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&amp;rsquo;re using &lt;code&gt;aspire mcp init&lt;/code&gt; (STDIO transport), there&amp;rsquo;s no API key to manage — authentication is handled by the subprocess communication.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Troubleshooting
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Self-Signed Certificate Issues
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Some AI assistants don&amp;rsquo;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&amp;rsquo;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-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;profiles&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;environmentVariables&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ASPIRE_DASHBOARD_MCP_ENDPOINT_URL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://localhost:16036&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;ASPIRE_ALLOW_UNSECURED_TRANSPORT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&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 class="relative group"&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;&amp;ldquo;Why is the checkout service slow?&amp;rdquo;&lt;/strong&gt; - The AI can analyze traces and identify the bottleneck&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Debug this 500 error&amp;rdquo;&lt;/strong&gt; - The AI accesses structured logs to find the root cause&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Is my database connection healthy?&amp;rdquo;&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 class="relative group"&gt;Learn More
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/get-started/configure-mcp/" target="_blank" rel="noreferrer"&gt;Configure MCP Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/dashboard/mcp-server/" target="_blank" rel="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/" target="_blank" rel="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/" target="_blank" rel="noreferrer"&gt;aspire mcp start Command Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" target="_blank" rel="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&amp;#43;label%3Amcp" target="_blank" rel="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" target="_blank" rel="noreferrer"&gt;Blog Post MCP Sample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&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&amp;rsquo;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 class="relative group"&gt;Related Posts
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/posts/aspire-cli-getting-started/" &gt;Getting Started with the Aspire CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/posts/aspire-cli-part-2/" &gt;Aspire CLI Part 2 - Deployment and Pipelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Aspire CLI Part 2 - Deployment and Pipelines</title><link>https://chris-ayers.com/posts/aspire-cli-part-2/</link><pubDate>Sat, 21 Feb 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/aspire-cli-part-2/</guid><description>&lt;p&gt;In &lt;a href="/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&amp;rsquo;s dive into deployment and CI/CD pipelines.&lt;/p&gt;</description><content:encoded>&lt;p&gt;In &lt;a href="/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&amp;rsquo;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 class="relative group"&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 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire publish -o ./artifacts&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The output depends on which hosting integration packages you&amp;rsquo;ve added:&lt;/p&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;p&gt;If no integration supports publishing, &lt;code&gt;aspire publish&lt;/code&gt; will tell you:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;No resources in the distributed application model support publishing.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;docker.io/library/postgres:17.2&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${PG_PASSWORD}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8000:5432&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${API_IMAGE}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ConnectionStrings__db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Host=pg;Port=5432;Username=postgres;Password=${PG_PASSWORD};Database=db&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&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" target="_blank" rel="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" target="_blank" rel="noreferrer"&gt;Docker Compose sample&lt;/a&gt; for a complete working example.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hosting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Docker&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="p"&gt;-*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppHost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDockerComposeEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;dc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddPostgres&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDataVolume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPgAdmin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;db&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCSharpApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;./api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then the standard workflow is:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire run &lt;span class="c1"&gt;# Run locally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire publish -o ./artifacts &lt;span class="c1"&gt;# Generate Docker Compose files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire deploy &lt;span class="c1"&gt;# Deploy to Docker Compose&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="k"&gt;do&lt;/span&gt; docker-compose-down-dc &lt;span class="c1"&gt;# Tear down the deployment&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&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" target="_blank" rel="noreferrer"&gt;Azure Container Apps sample&lt;/a&gt; for a complete working example.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hosting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppContainers&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hosting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Storage&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppHost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAzureContainerAppEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;env&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAzureStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;storage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunAsEmulator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;blobs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddBlobContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;images&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blobs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire publish -o ./azure-artifacts &lt;span class="c1"&gt;# Generates Bicep files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire deploy &lt;span class="c1"&gt;# Deploys to Azure Container Apps&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&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" target="_blank" rel="noreferrer"&gt;Kubernetes sample&lt;/a&gt; for a complete working example.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;k8s&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddKubernetesEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithExternalHttpEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Generate and deploy:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Generate Kubernetes manifests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire publish -o ./k8s-output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Apply with kubectl or Helm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f ./k8s-output&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;k8s&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddKubernetesEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s-env&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;compose&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDockerComposeEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;docker-env&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frontend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;frontend&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithComputeEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k8s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;backend&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithComputeEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Without this, Aspire throws an ambiguous environment exception at publish time.&lt;/p&gt;

&lt;h2 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire deploy&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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" target="_blank" rel="noreferrer"&gt;.NET Blog&lt;/a&gt; and the &lt;a href="https://aspire.dev/deployment/overview/" target="_blank" rel="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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire run &lt;span class="c1"&gt;# Run locally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire deploy &lt;span class="c1"&gt;# Deploy to Docker Compose&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="k"&gt;do&lt;/span&gt; docker-compose-down-dc &lt;span class="c1"&gt;# Teardown&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;Azure__SubscriptionId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your-subscription-id&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;Azure__Location&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;azure-region&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;Azure__ResourceGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;resource-group-name&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# List available pipeline steps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="k"&gt;do&lt;/span&gt; diagnostics
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tear down a Docker Compose deployment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="k"&gt;do&lt;/span&gt; docker-compose-down-dc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The naming convention is: docker-compose-down-{environment-name}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&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" target="_blank" rel="noreferrer"&gt;exec sample&lt;/a&gt; for a complete working example with Postgres and Redis.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Enable the exec feature&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire config &lt;span class="nb"&gt;set&lt;/span&gt; features.execCommandEnabled true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run EF Core migrations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="nb"&gt;exec&lt;/span&gt; --resource mydb -- dotnet ef database update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Open an interactive shell in a container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="nb"&gt;exec&lt;/span&gt; --resource redis -- redis-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Start a dependency and then run against it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="nb"&gt;exec&lt;/span&gt; --start-resource mydb -- dotnet ef migrations add Init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&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/" target="_blank" rel="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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Initialize azd in your project directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;azd init
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Provision infrastructure and deploy in one command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;azd up&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;During &lt;code&gt;azd init&lt;/code&gt;, you&amp;rsquo;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 class="relative group"&gt;GitHub Actions
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Using azd (Recommended for Azure)
 
 
 
 
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy Aspire App&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup .NET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-dotnet@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dotnet-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;10.0.x&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install azd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Azure/setup-azd@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Log in to Azure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azure/login@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.AZURE_CREDENTIALS }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Provision and Deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azd up --no-prompt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AZURE_ENV_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ vars.AZURE_ENV_NAME }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AZURE_LOCATION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ vars.AZURE_LOCATION }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;AZURE_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ vars.AZURE_SUBSCRIPTION_ID }}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Using Aspire CLI
 
 
 
 
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy with Aspire CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup .NET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-dotnet@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dotnet-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;10.0.x&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Aspire CLI&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;curl -fsSL https://aspire.dev/install.sh | bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Publish artifacts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;aspire publish -o ./artifacts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;working-directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./src/MyApp.AppHost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy with Docker Compose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; cd ./artifacts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; docker compose up --build -d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire &lt;span class="k"&gt;do&lt;/span&gt; publish-manifest --output-path ./diagnostics&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This produces a manifest snapshot for inspecting resource graphs and troubleshooting, but it&amp;rsquo;s not the primary deployment path.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Learn More
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/deployment/overview/" target="_blank" rel="noreferrer"&gt;Publishing and Deployment Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/reference/cli/overview/" target="_blank" rel="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/" target="_blank" rel="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/" target="_blank" rel="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" target="_blank" rel="noreferrer"&gt;Aspire Samples (davidfowl)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dotnet/aspire-samples" target="_blank" rel="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" target="_blank" rel="noreferrer"&gt;Blog Post Samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&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&amp;rsquo;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="/posts/aspire-cli-part-3-mcp/" &gt;Part 3&lt;/a&gt;, we explore one of Aspire&amp;rsquo;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 class="relative group"&gt;Related Posts
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/posts/aspire-cli-getting-started/" &gt;Getting Started with the Aspire CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/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;</content:encoded></item><item><title>Migrating from Jekyll to Hugo Part 3: Deployment and Lessons Learned</title><link>https://chris-ayers.com/posts/migrating-from-jekyll-to-hugo-part-3-deployment-and-lessons-learned/</link><pubDate>Sun, 25 Jan 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/migrating-from-jekyll-to-hugo-part-3-deployment-and-lessons-learned/</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;</description><content:encoded>&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 class="relative group"&gt;GitHub Actions Workflow
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the workflow I use to deploy Hugo to GitHub Pages:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy Hugo site to Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pages&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.154.5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@8e8c483db84b4bee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v6.0.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;submodules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;recursive&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peaceiris/actions-hugo@75d2e847&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v3.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hugo-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.HUGO_VERSION }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/configure-pages@983d7736&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v5.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Cache Hugo resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@8b402f58&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v5.0.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ${{ runner.temp }}/hugo_cache
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; resources/_gen&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hugo-${{ runner.os }}-${{ hashFiles(&amp;#39;content/**&amp;#39;, &amp;#39;config/**&amp;#39;, &amp;#39;assets/**&amp;#39;) }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restore-keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; hugo-${{ runner.os }}-&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build with Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HUGO_CACHEDIR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ runner.temp }}/hugo_cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HUGO_ENVIRONMENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;production&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TZ&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;America/New_York&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; hugo \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --gc \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --minify \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --baseURL &amp;#34;${{ steps.pages.outputs.base_url }}/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Upload artifact&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/upload-pages-artifact@7b1f4a76&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v4.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./public&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;needs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;github-pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ steps.deployment.outputs.page_url }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy to GitHub Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/deploy-pages@d6db9016&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v4.0.5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&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" target="_blank" rel="noreferrer"&gt;Super-Linter&lt;/a&gt; workflow that runs on every PR:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Super-Linter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.workflow }}-${{ github.ref }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Lint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout code&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@8e8c483db84b4bee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v6.0.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Super-linter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;super-linter/super-linter@d5b0a2ab&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v8.3.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;VALIDATE_ALL_CODEBASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;false&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Auto-fix formatting on PR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIX_CSS_PRETTIER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIX_HTML_PRETTIER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIX_JSON_PRETTIER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIX_MARKDOWN_PRETTIER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FIX_YAML_PRETTIER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Disable linters that don&amp;#39;t apply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;VALIDATE_JSCPD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;false&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;VALIDATE_PYTHON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;false&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Use repo markdownlint config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MARKDOWN_CONFIG_FILE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.markdownlint.yml&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Don&amp;#39;t lint the theme submodule&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FILTER_REGEX_EXCLUDE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;themes/.*&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;rsquo;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 class="relative group"&gt;Challenges and Solutions
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# In front matter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/category/development/my-old-post-url/&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;Challenge 2: Theme Version Compatibility
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;I hit this warning early on:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WARN Module &amp;#34;blowfish&amp;#34; is not compatible with this Hugo version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Pin both Hugo and theme versions:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# In GitHub Actions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HUGO_VERSION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.154.5&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Pin theme to specific tag&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; themes/blowfish
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout v2.97.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Challenge 3: Date Formatting
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Hugo uses Go&amp;rsquo;s reference time format. This tripped me up — Go doesn&amp;rsquo;t use &lt;code&gt;YYYY-MM-DD&lt;/code&gt; style format strings. Instead, it uses a specific reference time:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Go reference time: Mon Jan 2 15:04:05 MST 2006&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So in Hugo templates, you format dates like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;{{/* Long date */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Date.Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;January 2, 2006&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;{{/* Output: January 25, 2026 */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;{{/* ISO date */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Date.Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;2006-01-02&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;{{/* Output: 2026-01-25 */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;{{/* With time */}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Date.Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Jan 2, 2006 3:04 PM&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;{{/* Output: Jan 25, 2026 12:00 AM */}}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;Challenge 4: Custom Layouts
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Some Jekyll layouts needed recreation. Hugo&amp;rsquo;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 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;config/_default/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── hugo.toml # Core site settings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── languages.en.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── markup.toml # Goldmark, syntax highlighting
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── menus.en.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── module.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── params.toml # Theme parameters&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;Challenge 6: RSS Feed URLs
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Jekyll&amp;rsquo;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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# hugo.toml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;home&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HTML&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RSS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;FEED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;JSON&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Legacy feed.xml for backward compatibility with Jekyll&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;outputFormats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FEED&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mediaType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/rss+xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;baseName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;feed&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;rsquo;s &lt;code&gt;rss.xml&lt;/code&gt; into my layouts:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp themes/blowfish/layouts/_default/rss.xml layouts/_default/feed.xml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;rsquo;s default feed works too.&lt;/p&gt;

&lt;h2 class="relative group"&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&amp;rsquo;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 class="relative group"&gt;Before and After
 
 
 
 
&lt;/h2&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;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The migration took a weekend of focused work, but it was absolutely worth it. Hugo&amp;rsquo;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&amp;rsquo;re considering the switch, I hope this series helps. Feel free to reach out with questions!&lt;/p&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/documentation/" target="_blank" rel="noreferrer"&gt;Hugo Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blowfish.page/" target="_blank" rel="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/" target="_blank" rel="noreferrer"&gt;Hugo on GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/tools/migrations/#jekyll" target="_blank" rel="noreferrer"&gt;Jekyll to Hugo Migration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Migrating from Jekyll to Hugo Part 2: Content Migration</title><link>https://chris-ayers.com/posts/migrating-from-jekyll-to-hugo-part-2-content-migration/</link><pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/migrating-from-jekyll-to-hugo-part-2-content-migration/</guid><description>&lt;p&gt;In Part 1, I covered why I switched from Jekyll to Hugo. Now let&amp;rsquo;s dive into the actual content migration.&lt;/p&gt;</description><content:encoded>&lt;p&gt;In Part 1, I covered why I switched from Jekyll to Hugo. Now let&amp;rsquo;s dive into the actual content migration.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Front Matter Conversion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Most Jekyll posts work with minimal changes, but there are key differences:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Jekyll&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;post&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;My Post&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ld"&gt;2024-01-15&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;development&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;docker, containers]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;My Post&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ld"&gt;2024-01-15&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;Development&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;Containers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"&gt;&lt;path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &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;/span&gt;
&lt;/div&gt;


&lt;h2 class="relative group"&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 class="relative group"&gt;Images and Figures
 
 
 
 
&lt;/h3&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Jekyll --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% include figure.html src=&amp;#34;/images/photo.jpg&amp;#34; caption=&amp;#34;My caption&amp;#34; %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Hugo --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; figure src=&amp;#34;/images/photo.jpg&amp;#34; caption=&amp;#34;My caption&amp;#34; &amp;gt;}}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s the Hugo figure shortcode in action:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 src="/images/logos/hugo-logo.svg"
 alt="Hugo Logo"
 /&gt;
 
 &lt;figcaption&gt;The Hugo logo rendered with the figure shortcode&lt;/figcaption&gt;
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Mermaid Diagrams
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;This was a bigger change. Jekyll with the mermaid plugin uses fenced code blocks:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Jekyll --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```mermaid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;graph TD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A --&amp;gt; B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;```&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Hugo with Blowfish requires the mermaid shortcode:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Hugo --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; mermaid &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;graph TD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A --&amp;gt; B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; /mermaid &amp;gt;}}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I wrote a quick script to find and convert these across all posts. Here&amp;rsquo;s what a real mermaid diagram looks like after migration:&lt;/p&gt;
&lt;pre class="not-prose mermaid"&gt;
flowchart LR
 A["Jekyll Post\n(.md + Liquid)"] --&gt;|migrate| B["Hugo Post\n(.md + Shortcodes)"]
 B --&gt; C{"hugo build"}
 C --&gt; D["Static HTML"]
 C --&gt; E["Processed Images"]
 C --&gt; F["Minified CSS/JS"]
&lt;/pre&gt;


&lt;h3 class="relative group"&gt;Code Blocks
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Standard fenced code blocks work the same, but Hugo adds features:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Hugo with line numbers --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; highlight go &amp;#34;linenos=table,hl_lines=3&amp;#34; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;func main() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fmt.Println(&amp;#34;Hello&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fmt.Println(&amp;#34;Highlighted!&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{&amp;lt; /highlight &amp;gt;}}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s what that looks like rendered with Hugo&amp;rsquo;s syntax highlighting and line numbers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="hl"&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Hello&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line hl"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Highlighted!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2 class="relative group"&gt;Static Assets
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Jekyll and Hugo organize assets differently:&lt;/p&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;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;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &gt;&lt;strong&gt;Watch out:&lt;/strong&gt; Hugo&amp;rsquo;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;/span&gt;
&lt;/div&gt;


&lt;h2 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;title: &amp;#34;My Post&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This appears in the summary.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!--more--&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This is the full content.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&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 class="relative group"&gt;Bulk Migration Script
 
 
 
 
&lt;/h2&gt;
&lt;blockquote&gt;&lt;p&gt;&amp;ldquo;The best migration is the one you automate. Don&amp;rsquo;t hand-edit 60 posts when a script can do it in seconds.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;For 60+ posts, I used a simple PowerShell script:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;Get-ChildItem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;content/posts/*.md&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;ForEach-Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Get-Content&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;FullName&lt;/span&gt; &lt;span class="n"&gt;-Raw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;# Remove layout: post&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;-replace&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;layout: post\r?\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;# Remove mermaid: true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt; &lt;span class="o"&gt;-replace&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;mermaid: true\r?\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Set-Content&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;FullName&lt;/span&gt; &lt;span class="nv"&gt;$content&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

 



&lt;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 128c17.67 0 32 14.33 32 32c0 17.67-14.33 32-32 32S224 177.7 224 160C224 142.3 238.3 128 256 128zM296 384h-80C202.8 384 192 373.3 192 360s10.75-24 24-24h16v-64H224c-13.25 0-24-10.75-24-24S210.8 224 224 224h32c13.25 0 24 10.75 24 24v88h16c13.25 0 24 10.75 24 24S309.3 384 296 384z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &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; blocks or custom Jekyll includes. Test thoroughly!&lt;/span&gt;
&lt;/div&gt;


&lt;h2 class="relative group"&gt;What&amp;rsquo;s Next
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In Part 3, I&amp;rsquo;ll cover deployment with GitHub Actions and the challenges I encountered along the way.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/content-management/organization/" target="_blank" rel="noreferrer"&gt;Hugo Content Organization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/content-management/shortcodes/" target="_blank" rel="noreferrer"&gt;Hugo Shortcodes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blowfish.page/docs/shortcodes/" target="_blank" rel="noreferrer"&gt;Blowfish Shortcodes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Migrating from Jekyll to Hugo Part 1: Why I Made the Switch</title><link>https://chris-ayers.com/posts/migrating-from-jekyll-to-hugo-part-1-why-i-made-the-switch/</link><pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/migrating-from-jekyll-to-hugo-part-1-why-i-made-the-switch/</guid><description>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 src="/images/logos/hugo-logo.svg"
 alt="Hugo Logo"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;After years of running this blog on Jekyll, I finally made the switch to Hugo. Here&amp;rsquo;s why.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 src="/images/logos/hugo-logo.svg"
 alt="Hugo Logo"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;After years of running this blog on Jekyll, I finally made the switch to Hugo. Here&amp;rsquo;s why.&lt;/p&gt;

&lt;h2 class="relative group"&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 class="relative group"&gt;Why Hugo Is a Better Fit
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&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 class="relative group"&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 class="relative group"&gt;A more powerful templating system
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Hugo&amp;rsquo;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 class="relative group"&gt;A modern ecosystem
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Hugo&amp;rsquo;s theme community is active, innovative, and built around modern tooling. This is where Blowfish really shines.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Choosing a Theme
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I settled on the &lt;a href="https://blowfish.page/" target="_blank" rel="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;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Blowfish illustration"
 width="288"
 height="288"
 src="/images/logos/blowfish-logo_hu_6b7cb84e1afd72e1.png"
 srcset="/images/logos/blowfish-logo_hu_6b7cb84e1afd72e1.png 800w,/images/logos/blowfish-logo_hu_94f0dd74ffd10ee7.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/blowfish-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&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 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Built-in image optimization
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Thanks to Hugo&amp;rsquo;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 class="relative group"&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 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Jekyll vs. Hugo (with Blowfish): A Practical Comparison
 
 
 
 
&lt;/h2&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;h2 class="relative group"&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 class="relative group"&gt;What&amp;rsquo;s Next
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In Part 2, I&amp;rsquo;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&amp;rsquo;s taxonomy system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;re considering making the switch yourself, the migration is easier than you might think.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/documentation/" target="_blank" rel="noreferrer"&gt;Hugo Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blowfish.page/" target="_blank" rel="noreferrer"&gt;Blowfish Theme&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/tools/migrations/#jekyll" target="_blank" rel="noreferrer"&gt;Jekyll to Hugo Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Getting Started with the Aspire CLI - A Complete Guide</title><link>https://chris-ayers.com/posts/aspire-cli-getting-started/</link><pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/aspire-cli-getting-started/</guid><description>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://csadvent.christmas/" target="_blank" rel="noreferrer"&gt;C# Advent Calendar 2025&lt;/a&gt;. Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;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&amp;rsquo;ll use day-to-day.&lt;/p&gt;</description><content:encoded>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://csadvent.christmas/" target="_blank" rel="noreferrer"&gt;C# Advent Calendar 2025&lt;/a&gt;. Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;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&amp;rsquo;ll use day-to-day.&lt;/p&gt;

&lt;h2 class="relative group"&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 class="relative group"&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;hr&gt;

&lt;h3 class="relative group"&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 class="relative group"&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 class="relative group"&gt;The AppHost: Your Application&amp;rsquo;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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// TODO: Add resources here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddServiceDefaults&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapDefaultEndpoints&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This ensures consistent observability across all services without duplicating configuration.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Aspire vs Docker Compose
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re currently using Docker Compose, here&amp;rsquo;s the mental model shift.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker Compose:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./api&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./web&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;api&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Aspire equivalent:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddPostgres&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mydb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;web&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&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 class="relative group"&gt;Prerequisites
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Before installing the Aspire CLI, ensure you have the following:&lt;/p&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;h2 class="relative group"&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;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;&lt;code&gt;irm https://aspire.dev/install.ps1 | iex&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;macOS and Linux (Bash)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;curl -fsSL https://aspire.dev/install.sh | bash&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If &lt;code&gt;aspire&lt;/code&gt; isn&amp;rsquo;t found, open a new terminal and try again.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Core CLI Commands
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The Aspire CLI provides several commands for different stages of the development lifecycle. Here&amp;rsquo;s a practical rundown of the core commands.&lt;/p&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Interactive mode - prompts for template, name, and output&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire new
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Non-interactive with explicit options&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire new aspire-starter -n AspireApp -o AspireApp&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;What gets created:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AspireApp/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── AspireApp.sln
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── AspireApp.AppHost/ # Dev-time orchestrator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── AppHost.cs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── AspireApp.AppHost.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── AspireApp.ServiceDefaults/ # Shared configuration
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── Extensions.cs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── AspireApp.ServiceDefaults.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── AspireApp.ApiService/ # Mock weather data API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── Program.cs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── AspireApp.ApiService.csproj
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── AspireApp.Web/ # Blazor frontend
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── Program.cs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── AspireApp.Web.csproj&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;hr&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Navigate to your solution directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your-solution
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Initialize Aspire support&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire init&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;my-saas-app/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; apphost.cs # (new) orchestration code
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; apphost.run.json # (new) local run configuration
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; api/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; main.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pyproject.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; frontend/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; app.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; worker/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; worker.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; requirements.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The initial &lt;code&gt;apphost.cs&lt;/code&gt; is minimal:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sdk&lt;/span&gt; &lt;span class="n"&gt;Aspire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AppHost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sdk&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="m"&gt;13.0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// TODO: Add resources here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire add python&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddUvicornApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;./api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;main:app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddPythonApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;worker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;./worker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;worker.py&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithUv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Worker gets API_HTTP and API_HTTPS env vars&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;&lt;code&gt;aspire new&lt;/code&gt; vs &lt;code&gt;aspire init&lt;/code&gt;
 
 
 
 
&lt;/h3&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;hr&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# From your solution directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire run&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Dashboard: https://localhost:17068/login?t=ea559845d54cea66b837dc0ff33c3bd3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Logs: ~/.aspire/cli/logs/apphost-13024-2025-10-31-19-40-58.log&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;hr&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Interactive mode - shows list of available integrations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire add
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Add a specific integration by name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire add redis&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After adding an integration, wire it in your AppHost:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DistributedApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Add Redis resource&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddRedis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;cache&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Share Redis with your API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddProject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YourApi&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithHttpHealthCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then add the client library to your consuming project:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet add YourApi package Aspire.StackExchange.Redis&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then configure the client in your service (for example, &lt;code&gt;builder.AddRedisClient(&amp;quot;cache&amp;quot;)&lt;/code&gt;).&lt;/p&gt;
&lt;hr&gt;

&lt;h3 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire update&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire update --self&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Practical Workflow
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a typical workflow for a new project:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. Create a new solution&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire new
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. Run it&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire run
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3. Add an integration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire add redis
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 4. Update packages (and optionally the CLI)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aspire update --self&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Learn More
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/get-started/install-cli/" target="_blank" rel="noreferrer"&gt;Install CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/reference/cli/overview/" target="_blank" rel="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/" target="_blank" rel="noreferrer"&gt;Add Aspire to Existing App&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/dashboard/overview/" target="_blank" rel="noreferrer"&gt;Aspire Dashboard Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aspire.dev/integrations/gallery/" target="_blank" rel="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" target="_blank" rel="noreferrer"&gt;Blog Post Samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&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="/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="/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;</content:encoded></item><item><title>Building a Flexible AI Provider Strategy in .NET Aspire</title><link>https://chris-ayers.com/posts/building-a-flexible-ai-provider-strategy-in-net-aspire/</link><pubDate>Sun, 06 Jul 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/building-a-flexible-ai-provider-strategy-in-net-aspire/</guid><description>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=".NET Aspire Logo"
 width="533"
 height="533"
 src="/images/logos/dotnet-aspire-logo_hu_9f12311144931b16.png"
 srcset="/images/logos/dotnet-aspire-logo_hu_9f12311144931b16.png 800w,/images/logos/dotnet-aspire-logo_hu_b2e506e6ad47b7a2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dotnet-aspire-logo.png"
 /&gt;
 
 
 &lt;/figure&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;</description><content:encoded>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=".NET Aspire Logo"
 width="533"
 height="533"
 src="/images/logos/dotnet-aspire-logo_hu_9f12311144931b16.png"
 srcset="/images/logos/dotnet-aspire-logo_hu_9f12311144931b16.png 800w,/images/logos/dotnet-aspire-logo_hu_b2e506e6ad47b7a2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dotnet-aspire-logo.png"
 /&gt;
 
 
 &lt;/figure&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 class="relative group"&gt;The Problem: A Wide Variety of AI Choices
 
 
 
 
&lt;/h2&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&amp;rsquo;s Foundry Local&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test cheaply&lt;/strong&gt; with GitHub Models&amp;rsquo; free tier&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy to production&lt;/strong&gt; with Azure OpenAI&amp;rsquo;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 class="relative group"&gt;The Solution: Configuration-Driven Architecture
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The key insight was to abstract the AI provider selection entirely out of the API service. Here&amp;rsquo;s how it works:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;foundrylocal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;DeploymentName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;chat&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;phi-3.5-mini&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That&amp;rsquo;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 class="relative group"&gt;The Four Pillars of AI Provider Flexibility
 
 
 
 
&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left"&gt;Provider&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Best Use Case&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Free Tier&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Hardware Needs&lt;/th&gt;
 &lt;th style="text-align: left"&gt;Deployment&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;Azure OpenAI&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Production workloads&lt;/td&gt;
 &lt;td style="text-align: left"&gt;No&lt;/td&gt;
 &lt;td style="text-align: left"&gt;N/A (Cloud)&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Cloud&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;GitHub Models&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Prototyping &amp;amp; Testing&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Yes (Generous)&lt;/td&gt;
 &lt;td style="text-align: left"&gt;N/A (Cloud)&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Cloud&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;Ollama&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Offline &amp;amp; Local Dev&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Yes&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Local CPU/GPU&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Local&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left"&gt;&lt;strong&gt;Foundry Local&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Offline High-Perf Local Dev&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Yes&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Local CPU/GPU&lt;/td&gt;
 &lt;td style="text-align: left"&gt;Local&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 class="relative group"&gt;1. Azure OpenAI - The Enterprise Choice
 
 
 
 
&lt;/h3&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="OpenAI Logo"
 width="1260"
 height="708"
 src="/images/logos/azure-openai-logo_hu_7502dd0895b39747.png"
 srcset="/images/logos/azure-openai-logo_hu_7502dd0895b39747.png 800w,/images/logos/azure-openai-logo_hu_eb33600d838f18aa.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/azure-openai-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Azure OpenAI is my go-to for production workloads. It&amp;rsquo;s reliable, scalable, and integrates seamlessly with Azure&amp;rsquo;s ecosystem:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azureopenai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpt-4o&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;2. GitHub Models - The Developer&amp;rsquo;s Friend
 
 
 
 
&lt;/h3&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="GitHub Logo"
 width="560"
 height="560"
 src="/images/logos/github-logo-inverted_hu_2b28320e2fe9e866.png"
 srcset="/images/logos/github-logo-inverted_hu_2b28320e2fe9e866.png 800w,/images/logos/github-logo-inverted_hu_2d7770ef09d272c7.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/github-logo-inverted.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;&lt;a href="https://github.com/marketplace/models" target="_blank" rel="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&amp;rsquo;s proven reliable for development and staging environments.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;githubmodels&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpt-4o-mini&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;3. Ollama - The Privacy Champion
 
 
 
 
&lt;/h3&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Ollama Logo"
 width="181"
 height="256"
 src="/images/ollama_hu_c0401f39acc3409e.png"
 srcset="/images/ollama_hu_c0401f39acc3409e.png 800w,/images/ollama_hu_1b173c7f06981914.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/ollama.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;For local development and sensitive workloads, Ollama can&amp;rsquo;t be beat:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ollama&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;llama3.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;rsquo;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&amp;rsquo;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&amp;rsquo;re on Apple Silicon, avoid running Ollama in Docker containers. Docker on Mac cannot access the Metal GPU due to Apple&amp;rsquo;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&amp;rsquo;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 class="relative group"&gt;4. Foundry Local - The Microsoft Stack Choice
 
 
 
 
&lt;/h3&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 src="/images/ai_studio_icon_color.svg"
 alt="Foundry Local Logo"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Microsoft&amp;rsquo;s &lt;a href="https://github.com/microsoft/Foundry-Local" target="_blank" rel="noreferrer"&gt;Foundry Local&lt;/a&gt; offers the best of both worlds - local deployment with enterprise-grade models:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;foundrylocal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;phi-3.5-mini&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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&amp;rsquo;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&amp;rsquo;s Apple Silicon&amp;rsquo;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 class="relative group"&gt;The Architecture Behind the Magic
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The beauty of this approach lies in its simplicity. The API service never knows which AI provider it&amp;rsquo;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" target="_blank" rel="noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="not-prose mermaid"&gt;
%%{init: {'theme': 'default'}}%%
graph TD
 subgraph title[Configuration-Driven AI Provider Architecture]
 A[appsettings.json] --&gt; B{AI Provider Config};
 B --&gt; C{AddAIServices};
 C --&gt; D{IChatClient};
 subgraph API Service
 D --&gt; E[Your Service Code];
 end
 subgraph Provider Implementations
 C --&gt; F[AzureOpenAIClient];
 C --&gt; G[OllamaApiClient];
 C --&gt; H[GitHubModels Client];
 C --&gt; I[FoundryLocal Client];
 end
 end
&lt;/pre&gt;

&lt;p&gt;Here&amp;rsquo;s how I structured it.&lt;/p&gt;
&lt;hr&gt;

&lt;h3 class="relative group"&gt;The Secret Sauce: Configuration-Driven Service Registration
 
 
 
 
&lt;/h3&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// In your Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// This one line does it all&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAIServices&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;AddAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AIConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetSettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;AIProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ollama&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddOllamaAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;AIProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAzureOpenAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;AIProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GitHubModels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddGitHubModelsAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;AIProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FoundryLocal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFoundryLocalAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$&amp;#34;Unsupported AI provider: {aiSettings.Provider}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Provider-Specific Implementations
 
 
 
 
&lt;/h3&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&amp;rsquo;s built-in support. A single line is enough to register the client:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AddAzureOpenAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AISettings&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAzureOpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ai-service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeploymentName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AddOllamaAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AISettings&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddOllamaApiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeploymentName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddChatClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AddGitHubModelsAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AISettings&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;githubToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;GITHUB_TOKEN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ConnectionStrings:GitHubModels&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IChatClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;openAIClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;githubToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIClientOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://models.inference.ai.azure.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;openAIClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;AsIChatClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;AddFoundryLocalAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IHostApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AIConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AISettings&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IChatClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;serviceProvider&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FoundryLocalManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartModelAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aliasOrModelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;GetAwaiter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;GetResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;modelInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetModelInfoAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aliasOrModelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;GetAwaiter&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;GetResult&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;openAIClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIClientOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;openAIClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modelInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;ModelId&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;aiSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;AsIChatClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;Real-World Switching Techniques
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Technique 1: Environment-Based Switching
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;My favorite approach is to use environment-specific configuration files:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// appsettings.Development.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;foundrylocal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;phi-3.5-mini&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// appsettings.Production.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;AI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Provider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azureopenai&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Model&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpt-4o&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&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 class="relative group"&gt;Technique 2: Feature Flag-Style Switching
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Want to test a new provider with just a subset of users? Environment variables make this trivial:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Test GitHub Models for 10% of traffic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AI__Provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;githubmodels&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;AI__Model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;gpt-4o-mini&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Technique 3: Command-Line Override
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Perfect for debugging or one-off tests:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet run --project src/BuildWithAspire.AppHost -- --AI:Provider&lt;span class="o"&gt;=&lt;/span&gt;ollama --AI:Model&lt;span class="o"&gt;=&lt;/span&gt;llama3.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;hr&gt;

&lt;h2 class="relative group"&gt;Building for Resilience: Error Handling and Health Checks
 
 
 
 
&lt;/h2&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&amp;rsquo;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;hr&gt;

&lt;h2 class="relative group"&gt;Lessons Learned from Production
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;What Works Well
 
 
 
 
&lt;/h3&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&amp;rsquo;ve been using it for all my development work, and it&amp;rsquo;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&amp;rsquo;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&amp;rsquo;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&amp;rsquo;s become my default choice for local AI development.&lt;/p&gt;

&lt;h3 class="relative group"&gt;What Caught Me Off Guard
 
 
 
 
&lt;/h3&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&amp;rsquo;t always consistent.&lt;/strong&gt; GPT-4o on Azure OpenAI behaves slightly differently than on GitHub Models. It&amp;rsquo;s not a problem, but it&amp;rsquo;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&amp;rsquo;t access the Metal GPU. Apple&amp;rsquo;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;hr&gt;

&lt;h2 class="relative group"&gt;The Cost-Optimization Strategy
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;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;hr&gt;

&lt;h2 class="relative group"&gt;The One-Line Provider Switch
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The most satisfying part of this architecture is how easy it is to switch providers. Whether you&amp;rsquo;re responding to a service outage, testing a new model, or optimizing costs, it&amp;rsquo;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;hr&gt;

&lt;h2 class="relative group"&gt;A Note on Versioning
 
 
 
 
&lt;/h2&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&amp;rsquo;re implementing a similar solution, be sure to check for the latest versions and any potential breaking changes.&lt;/p&gt;
&lt;hr&gt;

&lt;h2 class="relative group"&gt;What&amp;rsquo;s Next?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m excited about the future of AI provider flexibility. Some ideas I&amp;rsquo;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;hr&gt;

&lt;h2 class="relative group"&gt;Final Thoughts
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Building AI applications doesn&amp;rsquo;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&amp;rsquo;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" target="_blank" rel="noreferrer"&gt;Blue Sky&lt;/a&gt; or &lt;a href="https://linkedin.com/in/chris-ayers" target="_blank" rel="noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</content:encoded></item><item><title>Headless CMS Without a PC on GitHub Pages</title><link>https://chris-ayers.com/posts/mobile-cms-on-github-pages/</link><pubDate>Thu, 26 Jun 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/mobile-cms-on-github-pages/</guid><description>&lt;p&gt;Headless CMS Without a PC on GitHub Pages&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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" target="_blank" rel="noreferrer"&gt;Sveltia CMS&lt;/a&gt; + &lt;a href="https://github.com/sveltia/sveltia-cms-auth" target="_blank" rel="noreferrer"&gt;Sveltia CMS Auth&lt;/a&gt; on Cloudflare Workers. Here&amp;rsquo;s how to glue it together.&lt;/p&gt;</description><content:encoded>&lt;p&gt;Headless CMS Without a PC on GitHub Pages&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;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" target="_blank" rel="noreferrer"&gt;Sveltia CMS&lt;/a&gt; + &lt;a href="https://github.com/sveltia/sveltia-cms-auth" target="_blank" rel="noreferrer"&gt;Sveltia CMS Auth&lt;/a&gt; on Cloudflare Workers. Here&amp;rsquo;s how to glue it together.&lt;/p&gt;

&lt;h2 class="relative group"&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" target="_blank" rel="noreferrer"&gt;Sveltia&amp;rsquo;s Cloudflare Workers-based authenticator&lt;/a&gt; makes GitHub-backed CMS possible on &lt;a href="https://pages.github.com/" target="_blank" rel="noreferrer"&gt;GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;robots&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;noindex&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Content Manager&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://unpkg.com/@sveltia/cms/dist/sveltia-cms.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;No heavy JS, no React-just &lt;a href="https://svelte.dev/" target="_blank" rel="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 class="relative group"&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" target="_blank" rel="noreferrer"&gt;sveltia-cms-auth&lt;/a&gt; into &lt;a href="https://workers.cloudflare.com/" target="_blank" rel="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" target="_blank" rel="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 class="relative group"&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;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;github&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;OWNER&amp;gt;/&amp;lt;REPO&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Path to your GitHub repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# optional, defaults to master&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://&amp;lt;WORKER&amp;gt;.workers.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# This line should *not* be indented&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;media_folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;assets/uploads&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Media files will be stored in the repo under images/uploads&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That base_url points Sveltia&amp;rsquo;s OAuth to your Worker. Push the change, and your /admin/ loads Sveltia. Clicking &amp;ldquo;Login&amp;rdquo; redirects to GitHub via your Worker, and you&amp;rsquo;re ready to edit.&lt;/p&gt;

&lt;h2 class="relative group"&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&amp;rsquo;ve easily edited blog posts without needing VS Code or Terminal. Sveltia handles commit and push transparently.&lt;/p&gt;

&lt;h2 class="relative group"&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;
A: They typically rely on external OAuth providers. &lt;a href="https://github.com/sveltia/sveltia-cms" target="_blank" rel="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;
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;
A: &lt;a href="https://docs.github.com/en/pages" target="_blank" rel="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/" target="_blank" rel="noreferrer"&gt;Cloudflare&amp;rsquo;s free tier&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;TL;DR
 
 
 
 
&lt;/h2&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/" target="_blank" rel="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" target="_blank" rel="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" target="_blank" rel="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;p&gt;This combo gives me a mobile-friendly CMS experience without extra hassle. If you&amp;rsquo;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" target="_blank" rel="noreferrer"&gt;Jekyll-based GitHub Pages blog&lt;/a&gt; without a PC, this setup hits the mark.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Related Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/pages" target="_blank" rel="noreferrer"&gt;GitHub Pages Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/docs/" target="_blank" rel="noreferrer"&gt;Jekyll Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/workers/" target="_blank" rel="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" target="_blank" rel="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/" target="_blank" rel="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" target="_blank" rel="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" target="_blank" rel="noreferrer"&gt;Sveltia CMS Auth GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/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="/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;</content:encoded></item><item><title>Understanding AI and Prompting Techniques Part 1</title><link>https://chris-ayers.com/posts/ai-and-prompting/</link><pubDate>Sun, 25 May 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/ai-and-prompting/</guid><description>&lt;h2 class="relative group"&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&amp;rsquo;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&amp;rsquo;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&amp;rsquo;s full potential.&lt;/p&gt;</description><content:encoded>&lt;h2 class="relative group"&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&amp;rsquo;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&amp;rsquo;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&amp;rsquo;s full potential.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Table of Contents
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/posts/ai-and-prompting/#what-is-ai" &gt;What is AI?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/posts/ai-and-prompting/#key-ai-terms" &gt;Key AI Terms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/posts/ai-and-prompting/#ai-models" &gt;AI Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/posts/ai-and-prompting/#tokens-the-language-of-ai" &gt;Tokens: The Language of AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/posts/ai-and-prompting/#prompting" &gt;Prompting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/posts/ai-and-prompting/#whats-next" &gt;What&amp;rsquo;s Next?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&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 class="relative group"&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&amp;rsquo;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 &amp;ldquo;Attention Is All You Need&amp;rdquo; 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 class="relative group"&gt;Key AI Terms
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;To effectively engage with AI, it&amp;rsquo;s essential to understand some fundamental terms.&lt;/p&gt;
&lt;hr&gt;

&lt;h2 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Popular AI Models (2025)
 
 
 
 
&lt;/h3&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;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;&amp;ldquo;The choice of model determines the capabilities and limitations of your AI-powered application.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;hr&gt;

&lt;h2 class="relative group"&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 &amp;ldquo;words&amp;rdquo; in AI&amp;rsquo;s vocabulary, though they don&amp;rsquo;t always correspond directly to human words.&lt;/p&gt;

&lt;h3 class="relative group"&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&amp;rsquo;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 class="relative group"&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 class="relative group"&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;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Tokenization example"
 width="1682"
 height="824"
 src="/images/chatgpt-tokens_hu_f0747f140730ac1d.png"
 srcset="/images/chatgpt-tokens_hu_f0747f140730ac1d.png 800w, /images/chatgpt-tokens_hu_aeed3ee4e9fbdae0.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/chatgpt-tokens.png"&gt;&lt;/figure&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;hr&gt;

&lt;h2 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Components of a Good Prompt
 
 
 
 
&lt;/h3&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;h3 class="relative group"&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 class="relative group"&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;&amp;ldquo;Write an email inviting people to a meeting.&amp;rdquo;&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;&amp;ldquo;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.&amp;rdquo;&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 class="relative group"&gt;The Power of Context and Roles
 
 
 
 
&lt;/h3&gt;

&lt;h4 class="relative group"&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 class="relative group"&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;: &amp;ldquo;You are a cybersecurity expert,&amp;rdquo; &amp;ldquo;Act as a technical architect,&amp;rdquo; &amp;ldquo;Respond as a UX designer&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Communication styles&lt;/strong&gt;: &amp;ldquo;Explain like I&amp;rsquo;m 5,&amp;rdquo; &amp;ldquo;Use formal business language,&amp;rdquo; &amp;ldquo;Write in a conversational tone&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perspective shifts&lt;/strong&gt;: &amp;ldquo;From a customer&amp;rsquo;s viewpoint,&amp;rdquo; &amp;ldquo;Consider the developer experience,&amp;rdquo; &amp;ldquo;Think like a project manager&amp;rdquo;&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; &amp;ldquo;How do I optimize this SQL query?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Role-specific prompt:&lt;/em&gt; &amp;ldquo;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.&amp;rdquo;&lt;/p&gt;

&lt;h3 class="relative group"&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 class="relative group"&gt;Chain of Thought Prompting
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Ask the AI to &amp;ldquo;think step by step&amp;rdquo; or &amp;ldquo;show your reasoning&amp;rdquo;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Analyze this code for security vulnerabilities. Think step by step:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. First, identify potential input validation issues
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. Then, check for authentication and authorization flaws
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. Finally, look for data exposure risks&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&gt;Few-Shot Learning
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Provide examples of the desired output format:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Convert these technical terms to plain English:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;API → Application Programming Interface (a way for software to communicate)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SDK → Software Development Kit (tools for building apps)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Docker → [Your task: explain this term]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 class="relative group"&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;&amp;ldquo;Summarize this 50-page document into key themes&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;For each theme, identify 3 actionable recommendations&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Create a presentation outline based on these recommendations&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 class="relative group"&gt;Understanding AI Model Limitations
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;While AI models are powerful, it&amp;rsquo;s crucial to understand their limitations:&lt;/p&gt;

&lt;h4 class="relative group"&gt;Statelessness and Memory
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Most AI models are stateless-they don&amp;rsquo;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&amp;rsquo;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 &amp;ldquo;see&amp;rdquo; 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 class="relative group"&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&amp;rsquo;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.
&amp;ldquo;Effective prompting combines clear instructions, relevant context, and well-defined roles to guide AI models toward your desired outcome.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;What&amp;rsquo;s Next?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In this series, we&amp;rsquo;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&amp;rsquo;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;&amp;ldquo;Mastering AI prompting is like learning a new language-the better you communicate, the more powerful the results.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Stay tuned for Part 2, where we&amp;rsquo;ll put these concepts into practice with hands-on examples and advanced strategies!&lt;/p&gt;</content:encoded></item><item><title>Aspiring .NET &amp; Resilience @ NDC Oslo 2025</title><link>https://chris-ayers.com/posts/ndc-oslo-2025-wrapup/</link><pubDate>Sat, 24 May 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/ndc-oslo-2025-wrapup/</guid><description>&lt;p&gt;As I&amp;rsquo;m flying from Oslo to Amsterdam, I&amp;rsquo;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" target="_blank" rel="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;</description><content:encoded>&lt;p&gt;As I&amp;rsquo;m flying from Oslo to Amsterdam, I&amp;rsquo;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" target="_blank" rel="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 class="relative group"&gt;A Journey of Learning and Sharing
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ndcoslo.com" target="_blank" rel="noreferrer"&gt;NDC Oslo 2025&lt;/a&gt; brought together over 2,000 developers, architects, and technology leaders from across the globe. The conference&amp;rsquo;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&amp;rsquo;s event, I had the privilege of presenting two sessions that reflect the current evolution of our industry:&lt;/p&gt;

&lt;h3 class="relative group"&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 class="relative group"&gt;&lt;a href="https://ndcoslo.com/agenda/resilient-by-design-0nqr/03e2y9s6amr" target="_blank" rel="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&amp;rsquo;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 class="relative group"&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&amp;rsquo;s program delivered exceptional content across multiple tracks:&lt;/p&gt;

&lt;h3 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Developer Experience
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;A refreshing focus on making developers&amp;rsquo; 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 class="relative group"&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 class="relative group"&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 class="relative group"&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 class="relative group"&gt;Conference Highlights
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&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" target="_blank" rel="noreferrer"&gt;Laila Bougria&amp;rsquo;s &amp;ldquo;CTRL+SHIFT+(BUILD) PAUSE&amp;rdquo;&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" target="_blank" rel="noreferrer"&gt;David Whitney&amp;rsquo;s &amp;ldquo;The Unbearable Weight of Architecture&amp;rdquo;&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" target="_blank" rel="noreferrer"&gt;Rendle&amp;rsquo;s closing keynote &amp;ldquo;Reasons To Be Cheerful: 0, 1, 2&amp;hellip;&amp;rdquo;&lt;/a&gt;&lt;/strong&gt; sent us home with renewed optimism. His journey through technology&amp;rsquo;s evolution reminded us how far we&amp;rsquo;ve come and painted an exciting picture of where we&amp;rsquo;re heading.&lt;/p&gt;

&lt;h3 class="relative group"&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 class="relative group"&gt;The Legendary NDC Party
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The conference&amp;rsquo;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 &amp;ldquo;Be Prepared&amp;rdquo; from The Lion King. It perfectly captured the conference&amp;rsquo;s spirit: serious about technology, but never taking ourselves too seriously.&lt;/p&gt;

&lt;h2 class="relative group"&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&amp;rsquo;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 &amp;ldquo;here&amp;rsquo;s how we actually did it&amp;rdquo; rather than &amp;ldquo;here&amp;rsquo;s how it should work in theory.&amp;rdquo; 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 class="relative group"&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&amp;rsquo;re a seasoned architect or just starting your development journey, you&amp;rsquo;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&amp;rsquo;re not navigating these changes alone. We&amp;rsquo;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;</content:encoded></item><item><title>Aspiring .NET &amp; Resilience @ Techorama Belgium 2025</title><link>https://chris-ayers.com/posts/techorama-belgium-2025/</link><pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/techorama-belgium-2025/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Techorama Belgium 2025"
 width="1140"
 height="285"
 src="/images/logos/techorama-be-2025-logo_hu_71f38ff5a1de957b.png"
 srcset="/images/logos/techorama-be-2025-logo_hu_71f38ff5a1de957b.png 800w, /images/logos/techorama-be-2025-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/techorama-be-2025-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m thrilled to announce that I&amp;rsquo;ll be delivering &lt;strong&gt;&lt;a href="https://techorama.be/speakers/speaker/chris-ayers/" target="_blank" rel="noreferrer"&gt;two sessions&lt;/a&gt;&lt;/strong&gt; at &lt;strong&gt;Techorama Belgium 2025&lt;/strong&gt;! Join me as we explore the cutting edge of .NET, AI, and cloud resilience with actionable strategies, demos, and real-world insights. &lt;a href="https://techorama.be/" target="_blank" rel="noreferrer"&gt;Techorama Belgium&lt;/a&gt; is a premier tech conference that brings together developers, architects, and IT professionals to share knowledge and network with industry leaders. This year, I&amp;rsquo;m excited to be part of the lineup!&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Techorama Belgium 2025"
 width="1140"
 height="285"
 src="/images/logos/techorama-be-2025-logo_hu_71f38ff5a1de957b.png"
 srcset="/images/logos/techorama-be-2025-logo_hu_71f38ff5a1de957b.png 800w, /images/logos/techorama-be-2025-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/techorama-be-2025-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m thrilled to announce that I&amp;rsquo;ll be delivering &lt;strong&gt;&lt;a href="https://techorama.be/speakers/speaker/chris-ayers/" target="_blank" rel="noreferrer"&gt;two sessions&lt;/a&gt;&lt;/strong&gt; at &lt;strong&gt;Techorama Belgium 2025&lt;/strong&gt;! Join me as we explore the cutting edge of .NET, AI, and cloud resilience with actionable strategies, demos, and real-world insights. &lt;a href="https://techorama.be/" target="_blank" rel="noreferrer"&gt;Techorama Belgium&lt;/a&gt; is a premier tech conference that brings together developers, architects, and IT professionals to share knowledge and network with industry leaders. This year, I&amp;rsquo;m excited to be part of the lineup!&lt;/p&gt;

&lt;h2 class="relative group"&gt;My Sessions at Techorama Belgium 2025
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Aspiring .NET with Azure OpenAI and Ollama
 
 
 
 
&lt;/h3&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt=".NET Aspire Logo"
 src="/images/logos/dotnet-aspire-logo.svg"
 &gt;&lt;/figure&gt;
&lt;p&gt;Discover how you can unlock the potential of AI in your .NET applications. In this session, you&amp;rsquo;ll learn how to integrate &lt;a href="https://azure.microsoft.com/services/cognitive-services/openai-service/" target="_blank" rel="noreferrer"&gt;Azure OpenAI&lt;/a&gt; and &lt;a href="https://ollama.ai/" target="_blank" rel="noreferrer"&gt;Ollama&lt;/a&gt; to build intelligent, modern .NET solutions. Through hands-on demos, code walkthroughs, and practical guidance, you&amp;rsquo;ll see how conversational AI, large language models, and generative capabilities can enhance your projects.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll also explore &lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview" target="_blank" rel="noreferrer"&gt;.NET Aspire&lt;/a&gt;, a modern framework designed to simplify building cloud-native applications. .NET Aspire provides streamlined APIs, built-in observability, and simplified deployment, making it easier to develop, test, and scale your applications efficiently.&lt;/p&gt;
&lt;p&gt;Additionally, you&amp;rsquo;ll learn about &lt;a href="https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai" target="_blank" rel="noreferrer"&gt;Microsoft.Extensions.AI&lt;/a&gt;, a powerful library that integrates AI services directly into your .NET applications. It provides standardized interfaces and extensions, enabling you to easily connect to AI models like Azure OpenAI and Ollama, manage AI configurations, and incorporate advanced AI capabilities without extensive changes to your existing code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key topics:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview" target="_blank" rel="noreferrer"&gt;.NET Aspire&lt;/a&gt; and the future of cloud-native .NET applications&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai" target="_blank" rel="noreferrer"&gt;Microsoft.Extensions.AI&lt;/a&gt; for seamless integration with Azure OpenAI and Ollama&lt;/li&gt;
&lt;li&gt;Building AI-powered features in your .NET applications&lt;/li&gt;
&lt;li&gt;Deploying and scaling AI solutions in the cloud&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://techorama.be/agenda/session/aspiring-net-with-azure-open-ai-and-ollama/" target="_blank" rel="noreferrer"&gt;Session details&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;h3 class="relative group"&gt;Resilient by Design
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ll also be delivering this session on Azure Resiliency at &lt;a href="/2025/05/06/ndc-oslo-2025/" &gt;NDC Oslo&lt;/a&gt;, and I&amp;rsquo;m excited to bring it to &lt;a href="https://techorama.be/" target="_blank" rel="noreferrer"&gt;Techorama Belgium&lt;/a&gt; as well!&lt;/p&gt;
&lt;p&gt;In cloud environments, failures are inevitable. This session explores how to build systems that not only survive but thrive amid disruptions. We&amp;rsquo;ll examine practical resilience patterns, architectural approaches, and Azure-native tools that help maintain reliability when components fail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key topics:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud resilience fundamentals and failure modes&lt;/li&gt;
&lt;li&gt;Multi-region architecture strategies&lt;/li&gt;
&lt;li&gt;Built-in &lt;a href="https://learn.microsoft.com/azure/well-architected/reliability/" target="_blank" rel="noreferrer"&gt;Azure resilience capabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Testing for resilience with &lt;a href="https://azure.microsoft.com/services/chaos-studio/" target="_blank" rel="noreferrer"&gt;Azure Chaos Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Implementing resilience patterns in .NET applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://techorama.be/agenda/session/resilient-by-design/" target="_blank" rel="noreferrer"&gt;Session details&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;

&lt;h2 class="relative group"&gt;Join Me in Belgium
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I look forward to meeting you and diving into the art and science of &lt;strong&gt;AI-powered&lt;/strong&gt; and &lt;strong&gt;Resilient By Design&lt;/strong&gt; solutions in Azure and .NET. Don&amp;rsquo;t miss this chance to level up your engineering strategy-see you at &lt;strong&gt;Techorama Belgium!&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>Resilient by Design @ NDC Oslo 2025</title><link>https://chris-ayers.com/posts/ndc-oslo-2025/</link><pubDate>Tue, 06 May 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/ndc-oslo-2025/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="NDC Oslo 2025"
 width="200"
 height="200"
 src="/images/logos/ndc-logo.jpg"
 srcset="/images/logos/ndc-logo.jpg 800w, /images/logos/ndc-logo.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/ndc-logo.jpg"&gt;&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m excited to preview my upcoming NDC Oslo talk, &lt;strong&gt;&amp;ldquo;Resilient by Design&amp;rdquo;&lt;/strong&gt;, where I&amp;rsquo;ll share how to architect Azure systems that not only survive failure but continue running smoothly when disruptions occur (&lt;a href="https://ndcoslo.com/agenda/resilient-by-design-0nqr/03e2y9s6amr" title="Resilient by Design | NDC Oslo 2025" target="_blank" rel="noreferrer"&gt;NDC&lt;/a&gt;). Join me on to explore core resilience principles, Azure-native tools, and proven best practices for maintaining high availability in real-world scenarios (&lt;a href="https://azure.microsoft.com/en-us/explore/reliability" title="Azure Reliability" target="_blank" rel="noreferrer"&gt;Microsoft Azure&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/" title="Reliability quick links - Microsoft Azure Well-Architected Framework" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;). Whether you&amp;rsquo;re new to Azure or looking to optimize an existing environment, this session will equip you with actionable strategies to anticipate, mitigate, and recover from failures (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/highly-available-multi-region-design" title="Recommendations for highly available multi-region design" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;).&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="NDC Oslo 2025"
 width="200"
 height="200"
 src="/images/logos/ndc-logo.jpg"
 srcset="/images/logos/ndc-logo.jpg 800w, /images/logos/ndc-logo.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/ndc-logo.jpg"&gt;&lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m excited to preview my upcoming NDC Oslo talk, &lt;strong&gt;&amp;ldquo;Resilient by Design&amp;rdquo;&lt;/strong&gt;, where I&amp;rsquo;ll share how to architect Azure systems that not only survive failure but continue running smoothly when disruptions occur (&lt;a href="https://ndcoslo.com/agenda/resilient-by-design-0nqr/03e2y9s6amr" title="Resilient by Design | NDC Oslo 2025" target="_blank" rel="noreferrer"&gt;NDC&lt;/a&gt;). Join me on to explore core resilience principles, Azure-native tools, and proven best practices for maintaining high availability in real-world scenarios (&lt;a href="https://azure.microsoft.com/en-us/explore/reliability" title="Azure Reliability" target="_blank" rel="noreferrer"&gt;Microsoft Azure&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/" title="Reliability quick links - Microsoft Azure Well-Architected Framework" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;). Whether you&amp;rsquo;re new to Azure or looking to optimize an existing environment, this session will equip you with actionable strategies to anticipate, mitigate, and recover from failures (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/highly-available-multi-region-design" title="Recommendations for highly available multi-region design" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why Resilience Matters
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In cloud-native environments, &lt;strong&gt;failure is inevitable&lt;/strong&gt;-hardware degrades, networks fluctuate, dependencies falter-and without resilience built in, even minor glitches can cascade into major outages (&lt;a href="https://azure.microsoft.com/en-us/explore/reliability" title="Azure Reliability" target="_blank" rel="noreferrer"&gt;Microsoft Azure&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;). Designing for resilience means embracing failure modes and planning recovery paths proactively rather than reacting when things break (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/" title="Reliability quick links - Microsoft Azure Well-Architected Framework" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;). By prioritizing resilience, organizations can uphold service-level objectives (SLOs) and deliver reliable experiences to users, even under adverse conditions (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/highly-available-multi-region-design" title="Recommendations for highly available multi-region design" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Core Resilience Principles
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Design for Redundancy:&lt;/strong&gt; Build duplicate components and failover paths to eliminate single points of failure, leveraging availability zones and regions as your foundation (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/" title="Reliability quick links - Microsoft Azure Well-Architected Framework" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implement Multi-Region Strategies:&lt;/strong&gt; Use active-active or active-passive architectures across regions to maintain service continuity during regional outages (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/highly-available-multi-region-design" title="Recommendations for highly available multi-region design" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leverage Failure Mode Analysis:&lt;/strong&gt; Proactively identify and prioritize potential failure scenarios to focus mitigation efforts where they matter most (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/reliability/" title="Reliability quick links - Microsoft Azure Well-Architected Framework" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan for Geo-Redundancy:&lt;/strong&gt; Configure geo-redundant storage and services (e.g., GRS/RA-GRS) to ensure critical data remains accessible if a primary region becomes unavailable (&lt;a href="https://docs.azure.cn/en-us/storage/common/geo-redundant-design" title="Use geo-redundancy to design highly available applications" target="_blank" rel="noreferrer"&gt;Azure documentation&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Azure Resilience Toolkit
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Azure Chaos Studio:&lt;/strong&gt; Inject controlled faults to validate and harden your applications against real-world issues before they impact customers (&lt;a href="https://azure.microsoft.com/en-us/products/chaos-studio" title="Azure Chaos Studio - Chaos engineering experimentation" target="_blank" rel="noreferrer"&gt;Azure Chaos Studio&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traffic Management &amp;amp; Load Balancing:&lt;/strong&gt; Use Azure Traffic Manager and Front Door to intelligently route traffic and failover quickly under load spikes or regional failures (&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/web-apps/app-service/architectures/multi-region" title="Highly available multi-region web app - Azure Architecture Center" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;App Service Reliable Web App Pattern:&lt;/strong&gt; Implement retry, circuit breaker, and cache-aside patterns to improve application reliability and performance efficiency (&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/web-apps/app-service/architectures/multi-region" title="Highly available multi-region web app - Azure Architecture Center" target="_blank" rel="noreferrer"&gt;Microsoft Learn&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Well-Architected Framework:&lt;/strong&gt; Apply the Reliability pillar&amp;rsquo;s design principles and assessment checklists from Microsoft&amp;rsquo;s Well-Architected Framework to ensure consistent resilience across workloads (&lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Real-World Strategies
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll cover &lt;strong&gt;real-world strategies&lt;/strong&gt; for maintaining uptime under pressure, such as automated failover drills, disaster recovery runbooks, and self-healing infrastructure practices (&lt;a href="https://ndcoslo.com/agenda/resilient-by-design-0nqr/03e2y9s6amr" title="Resilient by Design | NDC Oslo 2025" target="_blank" rel="noreferrer"&gt;NDC&lt;/a&gt;). You&amp;rsquo;ll see examples of how teams integrate chaos experiments into CI/CD pipelines and leverage telemetry-driven insights to continuously refine their resilience posture (&lt;a href="https://azure.microsoft.com/en-us/products/chaos-studio" title="Azure Chaos Studio - Chaos engineering experimentation" target="_blank" rel="noreferrer"&gt;Azure Chaos Studio&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" title="Azure Well-Architected Framework - Learn Microsoft" target="_blank" rel="noreferrer"&gt;Azure Well-Architected&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Join Me in Oslo
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I look forward to meeting you and diving into the art and science of building &lt;strong&gt;resilient-by-design&lt;/strong&gt; solutions in Azure. Don&amp;rsquo;t miss this chance to level up your resilience strategy-see you at &lt;strong&gt;NDC Oslo!&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>Stir Trek 2025 and Multiple Dev Containers</title><link>https://chris-ayers.com/posts/stir-trek-and-multiple-dev-containers/</link><pubDate>Sun, 04 May 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/stir-trek-and-multiple-dev-containers/</guid><description>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Stir Trek 2025"
 width="520"
 height="315"
 src="/images/logos/stirtrek-logo_hu_fee8ba143242909e.png"
 srcset="/images/logos/stirtrek-logo_hu_fee8ba143242909e.png 800w,/images/logos/stirtrek-logo_hu_66e466ca87ee59f2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/stirtrek-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This month at Stir Trek 2025, I presented on Dev Containers and GitHub Codespaces, demonstrating how these tools streamline both local and cloud-based development workflows. The session covered the essentials of creating portable development environments, customizing containers with features and extensions, and launching Codespaces directly from your repository. A lively Q&amp;amp;A followed, with attendees asking about strategies for running and working with multiple containers. Below, I&amp;rsquo;ve distilled those discussions and provided a deeper dive into shared container configurations across multiple projects-including folder structures, Docker Compose setups, VS Code workflows, and advanced tips you can apply in your own work.&lt;/p&gt;</description><content:encoded>
&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Stir Trek 2025"
 width="520"
 height="315"
 src="/images/logos/stirtrek-logo_hu_fee8ba143242909e.png"
 srcset="/images/logos/stirtrek-logo_hu_fee8ba143242909e.png 800w,/images/logos/stirtrek-logo_hu_66e466ca87ee59f2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/stirtrek-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This month at Stir Trek 2025, I presented on Dev Containers and GitHub Codespaces, demonstrating how these tools streamline both local and cloud-based development workflows. The session covered the essentials of creating portable development environments, customizing containers with features and extensions, and launching Codespaces directly from your repository. A lively Q&amp;amp;A followed, with attendees asking about strategies for running and working with multiple containers. Below, I&amp;rsquo;ve distilled those discussions and provided a deeper dive into shared container configurations across multiple projects-including folder structures, Docker Compose setups, VS Code workflows, and advanced tips you can apply in your own work.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Recap: Dev Containers and Codespaces
 
 
 
 
&lt;/h2&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Dev Containers"
 width="133"
 height="133"
 src="/images/logos/dev-containers-logo.png"
 srcset="/images/logos/dev-containers-logo.png 800w, /images/logos/dev-containers-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dev-containers-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;Dev Containers are Docker-based environments enriched with development-specific tooling, settings, and startup tasks as defined in a &lt;code&gt;devcontainer.json&lt;/code&gt; file. They enable you to use a container as a full-featured development environment-isolating dependencies, standardizing tool versions, and enabling reproducible setups locally or remotely (&lt;a href="https://devcontainers.github.io/" title="Development containers" target="_blank" rel="noreferrer"&gt;Dev Containers&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;GitHub Codespaces builds on Dev Containers by providing cloud-hosted environments that spin up in seconds with configurable CPU, memory, and storage. Codespaces leverages the same open specification as Dev Containers, making your &lt;code&gt;devcontainer.json&lt;/code&gt; a first-class citizen whether you connect via VS Code, IntelliJ, or directly in the browser (&lt;a href="https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers" title="Introduction to dev containers - Codespaces - GitHub Docs" target="_blank" rel="noreferrer"&gt;GitHub Docs&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Q&amp;amp;A: Running Multiple Containers
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Can I connect to multiple containers at once?
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;By default, &lt;strong&gt;VS Code allows only one container per window&lt;/strong&gt;, but you can open additional windows and attach each to a different container to work on multiple services in parallel (&lt;a href="https://code.visualstudio.com/remote/advancedcontainers/connect-multiple-containers" title="Connect to multiple containers - Visual Studio Code" target="_blank" rel="noreferrer"&gt;Visual Studio Code&lt;/a&gt;).&lt;/p&gt;

&lt;h3 class="relative group"&gt;What about using a single window for multiple containers?
 
 
 
 
&lt;/h3&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Docker Compose"
 width="200"
 height="219"
 src="/images/logos/docker-compose-logo.png"
 srcset="/images/logos/docker-compose-logo.png 800w, /images/logos/docker-compose-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/docker-compose-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;If you use &lt;strong&gt;Docker Compose&lt;/strong&gt;, define multiple services in your &lt;code&gt;docker-compose.yml&lt;/code&gt; and create separate &lt;code&gt;devcontainer.json&lt;/code&gt; configurations for each service-each referencing the common Compose file. VS Code will then list each configuration in its Dev Container picker, letting you reopen the current window to connect to a different service without duplicating your Compose setup (&lt;a href="https://devcontainers.github.io/guide/dockerfile" title="Using Images, Dockerfiles, and Docker Compose" target="_blank" rel="noreferrer"&gt;Dev Containers&lt;/a&gt;).&lt;/p&gt;

&lt;h3 class="relative group"&gt;How do I configure separate containers for multiple projects?
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;To maintain isolation and clarity, place each container configuration in its own subdirectory under &lt;code&gt;.devcontainer&lt;/code&gt;, following the pattern &lt;code&gt;.devcontainer/&amp;lt;project&amp;gt;/devcontainer.json&lt;/code&gt;. Tools supporting the spec recognize this layout and list all found configurations in the Codespaces or VS Code Dev Container dropdown (&lt;a href="https://containers.dev/implementors/spec/" title="Development Container Specification" target="_blank" rel="noreferrer"&gt;containers.dev&lt;/a&gt;, &lt;a href="https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers" title="Introduction to dev containers - Codespaces - GitHub Docs" target="_blank" rel="noreferrer"&gt;GitHub Docs&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Shared &lt;code&gt;.devcontainer&lt;/code&gt; Folder Structure
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Centralize your container configurations in a single root directory:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dev-container/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ .devcontainer/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ .env
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ docker-compose.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ project-a-node-js/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └─ devcontainer.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ project-b-node-js/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └─ devcontainer.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├─ project-c-python/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └─ devcontainer.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └─ project-d-go-lang/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └─ devcontainer.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ project-a-node-js/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ project-b-node-js/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ project-c-python/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─ project-d-go-lang/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This layout lets all projects share a &lt;strong&gt;single&lt;/strong&gt; Compose definition and environment variables-reducing duplication and easing updates (&lt;a href="https://containers.dev/implementors/spec/" title="Development Container Specification" target="_blank" rel="noreferrer"&gt;containers.dev&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Common Docker Compose File
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;.devcontainer/docker-compose.yml&lt;/code&gt;, define every project service alongside shared dependencies:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-a-node-js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mcr.microsoft.com/devcontainers/base:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;..:/workspaces:cached&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8001:8000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sleep infinity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-b-node-js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mcr.microsoft.com/devcontainers/base:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;..:/workspaces:cached&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8002:8000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-c-python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mcr.microsoft.com/devcontainers/base:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;..:/workspaces:cached&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8003:8000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-d-go-lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mcr.microsoft.com/devcontainers/base:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;..:/workspaces:cached&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;8004:8000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres-data:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres-data:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Each service listens on port 8000 internally and is mapped to a unique host port (8001-8004) to prevent collisions (&lt;a href="https://code.visualstudio.com/docs/containers/docker-compose" title="Use Docker Compose - Visual Studio Code" target="_blank" rel="noreferrer"&gt;Visual Studio Code&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Workspace Mounts &amp;amp; Folder Mapping
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Mounting the root-level folder into &lt;code&gt;/workspaces&lt;/code&gt; in each container gives uniform access to all projects. In each &lt;code&gt;devcontainer.json&lt;/code&gt;, point &lt;code&gt;workspaceFolder&lt;/code&gt; at the specific subdirectory:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;workspaceFolder&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/workspaces/project-b-node-js&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This ensures your editor is scoped appropriately when connected (&lt;a href="https://devcontainers.github.io/guide/dockerfile" title="Using Images, Dockerfiles, and Docker Compose" target="_blank" rel="noreferrer"&gt;Dev Containers&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Per-Project &lt;code&gt;devcontainer.json&lt;/code&gt;
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Each project&amp;rsquo;s configuration references the shared Compose file and specifies its service:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsonc" data-lang="jsonc"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Project B Dev Container&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;dockerComposeFile&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;../docker-compose.yml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;project-b-node-js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;workspaceFolder&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/workspaces/project-b-node-js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;shutdownAction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;none&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code&gt;&amp;quot;shutdownAction&amp;quot;: &amp;quot;none&amp;quot;&lt;/code&gt; keeps all containers running when you close one window, so you don&amp;rsquo;t inadvertently tear down shared services (&lt;a href="https://devcontainers.github.io/implementors/json_reference/" title="Dev Container metadata reference" target="_blank" rel="noreferrer"&gt;Dev Containers&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Building &amp;amp; Switching Between Containers
 
 
 
 
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Open the root folder&lt;/strong&gt; (&lt;code&gt;dev-container/&lt;/code&gt;) in VS Code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reopen in Container:&lt;/strong&gt; Run &lt;strong&gt;Dev Containers: Reopen in Container&lt;/strong&gt; and select your desired project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Switch Container:&lt;/strong&gt; Later, use &lt;strong&gt;Dev Containers: Switch Container&lt;/strong&gt; to hop to another project without restarting the Docker stack (&lt;a href="https://code.visualstudio.com/remote/advancedcontainers/connect-multiple-containers" title="Connect to multiple containers - Visual Studio Code" target="_blank" rel="noreferrer"&gt;Visual Studio Code&lt;/a&gt;, &lt;a href="https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers" title="Introduction to dev containers - Codespaces - GitHub Docs" target="_blank" rel="noreferrer"&gt;GitHub Docs&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Advanced Multi-Project Strategies
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Environment-Specific Overrides
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Layer additional Compose files for environment-specific tweaks:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker-compose -f docker-compose.yml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -f docker-compose.override.yml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -f docker-compose.dev.yml up -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Overrides can redefine images, ports, mounts, or feature flags per environment (&lt;a href="https://code.visualstudio.com/docs/containers/docker-compose" title="Use Docker Compose - Visual Studio Code" target="_blank" rel="noreferrer"&gt;Visual Studio Code&lt;/a&gt;).&lt;/p&gt;

&lt;h3 class="relative group"&gt;Isolated Networks &amp;amp; Namespaces
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Define separate Docker networks to segment traffic:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dev-a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dev-b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-a-node-js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;dev-a]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;project-b-node-js&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;dev-b]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;dev-a, dev-b]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This prevents unintended inter-service communication between project environments (&lt;a href="https://code.visualstudio.com/docs/containers/docker-compose" title="Use Docker Compose - Visual Studio Code" target="_blank" rel="noreferrer"&gt;Visual Studio Code&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;GitHub Codespaces Integration
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Codespaces recognizes the same &lt;code&gt;.devcontainer&lt;/code&gt; layout:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration Dropdown:&lt;/strong&gt; Multiple &lt;code&gt;devcontainer.json&lt;/code&gt; files under &lt;code&gt;.devcontainer/&lt;/code&gt; are automatically listed when creating a Codespace (&lt;a href="https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers" title="Introduction to dev containers - Codespaces - GitHub Docs" target="_blank" rel="noreferrer"&gt;GitHub Docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port Forwarding:&lt;/strong&gt; Host-mapped ports (8001-8004) surface as forwarded ports in the Codespaces UI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pre-builds &amp;amp; Secrets:&lt;/strong&gt; Enable pre-builds in &lt;code&gt;devcontainer.json&lt;/code&gt; and leverage repository or organization secrets instead of a local &lt;code&gt;.env&lt;/code&gt; file (&lt;a href="https://github.blog/news-insights/product-news/codespaces-multi-repository-monorepo-scenarios/" title="Codespaces for multi-repository and monorepo scenarios" target="_blank" rel="noreferrer"&gt;The GitHub Blog&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Lifecycle Customization
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Use Dev Container lifecycle hooks per project to automate setup:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-jsonc" data-lang="jsonc"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;postCreateCommand&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cd /workspaces/project-a-node-js &amp;amp;&amp;amp; npm ci&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;postStartCommand&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;npm run migrate&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;initializeCommand&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;git submodule update --init&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;These commands ensure each container is fully prepared for development immediately (&lt;a href="https://devcontainers.github.io/implementors/json_reference/" title="Dev Container metadata reference" target="_blank" rel="noreferrer"&gt;Dev Containers&lt;/a&gt;).&lt;/p&gt;

&lt;h2 class="relative group"&gt;Troubleshooting Tips
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stuck at &amp;ldquo;Rebuilding container&amp;hellip;&amp;rdquo;&lt;/strong&gt;: Clear the Docker build cache or raise VS Code&amp;rsquo;s Docker logging level.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ports not forwarding:&lt;/strong&gt; Verify &lt;code&gt;forwardPorts&lt;/code&gt; in &lt;code&gt;devcontainer.json&lt;/code&gt; or check Codespaces port settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume performance issues:&lt;/strong&gt; On macOS/Windows, consider isolating cache directories (e.g., &lt;code&gt;node_modules&lt;/code&gt;) in named volumes to speed up I/O (&lt;a href="https://some-natalie.dev/blog/multiservice-devcontainers/" title="Securing Devcontainers (part 2) - multi-service applications with ..." target="_blank" rel="noreferrer"&gt;Some Natalie&amp;rsquo;s corner of the internet&lt;/a&gt;, &lt;a href="https://blog.pamelafox.org/2024/11/making-dev-container-with-multiple-data.html" title="Making a dev container with multiple data services - pamela fox&amp;#39;s blog" target="_blank" rel="noreferrer"&gt;pamela fox&amp;rsquo;s blog&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;By centralizing Dev Container configurations and sharing a unified &lt;code&gt;docker-compose.yml&lt;/code&gt;, you eliminate duplication, streamline dependency management, and enable seamless switching between multiple projects-both locally and in GitHub Codespaces. This pattern scales from a handful of services to extensive microservice landscapes, delivering consistent, reproducible developer environments across your entire workspace.&lt;/p&gt;</content:encoded></item><item><title>Copilot or Rubber Ducky?</title><link>https://chris-ayers.com/posts/copilot-or-rubber-ducky/</link><pubDate>Thu, 24 Apr 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/copilot-or-rubber-ducky/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Rubber Duck"
 width="2419"
 height="3226"
 src="/images/rubberduck_hu_3901fba2e6e5fb21.jpg"
 srcset="/images/rubberduck_hu_3901fba2e6e5fb21.jpg 800w, /images/rubberduck_hu_4cc08f1c89d13cc4.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/rubberduck.jpg"&gt;&lt;/figure&gt;

&lt;h1 class="relative group"&gt;Rubber Duck Debugging in the Age of AI
 
 
 
 
&lt;/h1&gt;
&lt;p&gt;Ever found yourself explaining your code to a little rubber duck perched on your desk? If so, you&amp;rsquo;re in good company. This quirky practice, known as &lt;strong&gt;rubber duck debugging&lt;/strong&gt;, has helped countless developers articulate problems and discover solutions. The idea is simple: by forcing yourself to explain your code, line by line, to an inanimate duck, you often stumble upon the bug or insight you needed. It&amp;rsquo;s like having a silent pair programmer who patiently listens as you work through the logic. But what if your rubber duck could &lt;strong&gt;talk back&lt;/strong&gt; and offer suggestions? Modern AI coding assistants (like GitHub Copilot) are becoming the new interactive rubber ducks, and that means a lot for debugging, brainstorming ideas, and reviewing code.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Rubber Duck"
 width="2419"
 height="3226"
 src="/images/rubberduck_hu_3901fba2e6e5fb21.jpg"
 srcset="/images/rubberduck_hu_3901fba2e6e5fb21.jpg 800w, /images/rubberduck_hu_4cc08f1c89d13cc4.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/rubberduck.jpg"&gt;&lt;/figure&gt;

&lt;h1 class="relative group"&gt;Rubber Duck Debugging in the Age of AI
 
 
 
 
&lt;/h1&gt;
&lt;p&gt;Ever found yourself explaining your code to a little rubber duck perched on your desk? If so, you&amp;rsquo;re in good company. This quirky practice, known as &lt;strong&gt;rubber duck debugging&lt;/strong&gt;, has helped countless developers articulate problems and discover solutions. The idea is simple: by forcing yourself to explain your code, line by line, to an inanimate duck, you often stumble upon the bug or insight you needed. It&amp;rsquo;s like having a silent pair programmer who patiently listens as you work through the logic. But what if your rubber duck could &lt;strong&gt;talk back&lt;/strong&gt; and offer suggestions? Modern AI coding assistants (like GitHub Copilot) are becoming the new interactive rubber ducks, and that means a lot for debugging, brainstorming ideas, and reviewing code.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What is Rubber Duck Debugging?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Rubber duck debugging (or &amp;ldquo;rubberducking&amp;rdquo;) is a tried-and-true method for debugging code by explaining it aloud in plain language. The term comes from a story in &lt;em&gt;The Pragmatic Programmer&lt;/em&gt; where a developer carries around a rubber duck and debugs issues by describing code to the duck (&lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging#:~:text=In%20software%20engineering%20%2C%20rubber,2" target="_blank" rel="noreferrer"&gt;Rubber duck debugging - Wikipedia&lt;/a&gt;). The act of verbalizing your problem-&lt;strong&gt;even to an object that can&amp;rsquo;t respond&lt;/strong&gt;-often makes the solution obvious. By describing what your code is &lt;em&gt;supposed&lt;/em&gt; to do versus what it &lt;em&gt;actually&lt;/em&gt; does, any mismatch or flaw becomes apparent. Many programmers have experienced that &amp;ldquo;aha!&amp;rdquo; moment while explaining a bug to a colleague; rubber ducking achieves the same effect without needing to interrupt anyone else.&lt;/p&gt;
&lt;p&gt;This technique works because teaching or explaining forces you to view the problem from a different perspective. You slow down and examine your code step-by-step. In doing so, you might catch off-by-one errors, incorrect assumptions, or forgotten conditions that you overlooked before. It&amp;rsquo;s surprisingly effective-sometimes the mere act of stating the problem out loud is enough to trigger a solution.&lt;/p&gt;

&lt;h2 class="relative group"&gt;From Silent Duck to AI Assistant
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Traditional rubber duck debugging gives you a &lt;strong&gt;listener&lt;/strong&gt;, but in modern development we now also have &lt;strong&gt;interactive helpers&lt;/strong&gt;. Enter AI pair programming tools like &lt;strong&gt;GitHub Copilot&lt;/strong&gt;, &lt;strong&gt;OpenAI&amp;rsquo;s ChatGPT&lt;/strong&gt;, and others. These AI assistants serve as a kind of rubber duck that &lt;em&gt;talks back&lt;/em&gt;, offering hints, explanations, and even code suggestions in real time. It&amp;rsquo;s as if the duck on your desk suddenly gained a brain and a voice (though fortunately, not an actual quacking sound).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt;, for example, integrates into your editor and can suggest the next line of code or help fill in functions as you type. Ask it a question (via a comment or chat interface) and it can suggest a solution or explain a chunk of code. Similarly, ChatGPT and other AI chatbots can analyze a piece of code you provide and discuss it with you. This transforms the debugging process into an interactive dialogue. You describe the problem-just like you would to a rubber duck-but now you get a response that might point out the bug or offer a fresh approach.&lt;/p&gt;
&lt;p&gt;Using an AI assistant feels a bit like pair programming with a knowledgeable colleague who&amp;rsquo;s always available. The AI can highlight inconsistencies or recommend best practices on the fly, helping you stay in the &lt;strong&gt;flow state&lt;/strong&gt; of coding. In other words, you spend less time stuck on trivial issues or searching through documentation, and more time building solutions. The core benefit is the same as rubber duck debugging: you&amp;rsquo;re clarifying your own understanding. The difference is that the &amp;ldquo;duck&amp;rdquo; (the AI) can actively contribute to the conversation.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Practical Use Cases for an AI Rubber Duck
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;How exactly can AI tools enhance your development workflow? Let&amp;rsquo;s look at a few concrete scenarios where an AI assistant shines as a debugging and coding partner:&lt;/p&gt;

&lt;h3 class="relative group"&gt;1. Debugging with Interactive Assistance
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;When you hit a perplexing bug, an AI assistant can act as a debugging buddy. Instead of just explaining the issue to a silent duck, you can describe the problem to an AI and get immediate feedback. For example, if a function isn&amp;rsquo;t returning the expected output, you might ask the AI, &lt;em&gt;&amp;ldquo;Here&amp;rsquo;s my function and what it&amp;rsquo;s supposed to do. Can you spot why it&amp;rsquo;s returning the wrong value?&amp;rdquo;&lt;/em&gt; The AI can analyze your code and point out, say, a missed edge case or a variable that wasn&amp;rsquo;t updated correctly. This is like the duck not only listening, but actually &lt;strong&gt;highlighting the error&lt;/strong&gt; in your explanation.&lt;/p&gt;
&lt;p&gt;Modern tools like Copilot Chat (the chat interface of GitHub Copilot) go even further: they integrate with your development environment&amp;rsquo;s debugger. This means the AI can access the call stack, variable values, and error messages while you&amp;rsquo;re debugging. You can literally ask, &lt;em&gt;&amp;ldquo;Why did this null pointer exception occur?&amp;rdquo;&lt;/em&gt; and the AI, aware of the context, can suggest what might be null and where it came from. It&amp;rsquo;s an extension of the rubber duck method into a two-way conversation. By engaging in this dialogue, you still reap the benefit of thinking through the problem clearly, but you also get &lt;strong&gt;targeted hints&lt;/strong&gt; and potential fixes in real time.&lt;/p&gt;

&lt;h3 class="relative group"&gt;2. Brainstorming and Idea Generation
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Beyond bug squashing, AI &amp;ldquo;ducks&amp;rdquo; are fantastic for brainstorming solutions and generating ideas. Let&amp;rsquo;s say you&amp;rsquo;re &lt;strong&gt;starting a new feature&lt;/strong&gt; or unsure how to implement a particular algorithm. In the past, you might talk it out to your duck or jot ideas on a whiteboard. Now, you can bounce ideas off an AI. For instance, you could ask, &lt;em&gt;&amp;ldquo;What are some ways to optimize this search algorithm?&amp;rdquo;&lt;/em&gt; or &lt;em&gt;&amp;ldquo;Can you suggest a design pattern for this use-case?&amp;rdquo;&lt;/em&gt; The AI can enumerate a few approaches, outline code snippets, or even highlight pros and cons of each approach.&lt;/p&gt;
&lt;p&gt;This is like having an ever-helpful colleague who has read every Stack Overflow post and documentation page out there. The AI might surface an approach you hadn&amp;rsquo;t considered or remind you of edge cases you might have overlooked. Even if you don&amp;rsquo;t directly use the AI&amp;rsquo;s suggestion, it can spark new thoughts-much like a brainstorming session with a team. &lt;strong&gt;Idea generation&lt;/strong&gt; with an AI assistant can be especially useful for things like: finding the best name for a function or variable, exploring different algorithms or libraries to solve a problem, or generating test cases you might not have thought of. It&amp;rsquo;s an interactive creativity boost that complements your own knowledge.&lt;/p&gt;

&lt;h3 class="relative group"&gt;3. AI as a Code Reviewer
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Code review is a critical part of development, and here too an AI sidekick can help. Think of it as &lt;em&gt;preliminary rubber duck code review&lt;/em&gt;. Before you even send your code to a teammate for review, you can ask an AI to take a look. For example, you might prompt the AI with, &lt;em&gt;&amp;ldquo;Here&amp;rsquo;s my function (or pull request). Do you see any issues or improvements I can make?&amp;rdquo;&lt;/em&gt; The AI will examine the code and might point out potential bugs, performance issues, or stylistic inconsistencies. It could catch things like a missed null check, an inefficient loop, or a confusing variable name, and suggest a clearer alternative.&lt;/p&gt;
&lt;p&gt;This use case turns the AI into a tireless, instant code reviewer. Of course, AI won&amp;rsquo;t replace your human colleagues&amp;rsquo; feedback-humans have context and intuition that AI might lack-but it&amp;rsquo;s a great way to &lt;strong&gt;screen your code for obvious problems&lt;/strong&gt; and refine it before others see it. In essence, you&amp;rsquo;re explaining your code to the AI (just as you would to a duck or a colleague) and seeing what it flags. The process encourages you to double-check logic and consider improvements. It&amp;rsquo;s like having a second set of eyes on demand, helping you write cleaner, more robust code.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Engaging the AI, Preserving the Process
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;While AI tools are powerful, it&amp;rsquo;s important to note that they amplify rather than replace the core benefits of rubber duck debugging. The true magic of the rubber duck method lies in the &lt;em&gt;process&lt;/em&gt; of articulating your thoughts. When using AI, you still need to clearly explain the problem or ask the right question-doing so forces you to understand the problem better, just as talking to the duck would. In fact, sometimes just formulating a question for ChatGPT or Copilot can make you realize the answer before the AI even replies!&lt;/p&gt;
&lt;p&gt;The advantage of AI assistants is that they can respond with insights, which can save time and validate your thinking. They help you catch mistakes faster, explore more ideas, and learn new techniques. But you, the developer, are still in the driver&amp;rsquo;s seat. It&amp;rsquo;s your reasoning and intuition guiding the conversation. Think of the AI as an extension of the rubber duck: an &lt;strong&gt;extra knowledgeable duck&lt;/strong&gt; that can provide feedback, but one that still relies on your direction and understanding.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The rubber duck debugging method has long been a secret weapon for developers, enabling us to solve problems by simply explaining them. Today, with AI-based tools like GitHub Copilot acting as interactive rubber ducks, we have an even more powerful ally at our disposal. These AI assistants can listen to our problems and actually respond with useful answers-helping debug code, spark new ideas, and review our work. By combining the old practice of articulating our thoughts with the new capabilities of AI, we get the best of both worlds: clarity of thought and immediate expert feedback.&lt;/p&gt;
&lt;p&gt;In the end, whether you&amp;rsquo;re talking to a plastic duck or a digital assistant, the goal is the same: to understand the problem better and craft a solution. So don&amp;rsquo;t toss out your trusty rubber duck just yet-it has taught us the value of thinking aloud. But do consider inviting an AI partner into your workflow. You might be surprised at how engaging and insightful this &amp;ldquo;conversation&amp;rdquo; can be. After all, two heads (even if one is silicon-based) can be better than one. Happy debugging, and quack on!&lt;/p&gt;</content:encoded></item><item><title>New Team, New Focus</title><link>https://chris-ayers.com/posts/new-team-new-focus/</link><pubDate>Fri, 21 Mar 2025 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/new-team-new-focus/</guid><description>&lt;p&gt;I wanted to share a quick work update and also explain why I&amp;rsquo;ve been posting less frequently lately. Previously, I was on the Fast Track for Azure (FTA) team at Microsoft, part of the Customer Experience Program (CXP) within Azure Engineering (C+AI). In October, my team was reorganized and I was moved into the Azure Reliability (AzRel) group.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m now part of the AzRel Risk SRE team, focusing specifically on risks related to Azure Platform Reliability. My new role brings with it a significantly different skill set and area of focus, requiring a deep dive into new concepts, tools, and approaches. I&amp;rsquo;ve been fully engaged in learning and adapting, and I&amp;rsquo;ve also been actively developing new applications and solutions to address challenges faced by my new team.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I wanted to share a quick work update and also explain why I&amp;rsquo;ve been posting less frequently lately. Previously, I was on the Fast Track for Azure (FTA) team at Microsoft, part of the Customer Experience Program (CXP) within Azure Engineering (C+AI). In October, my team was reorganized and I was moved into the Azure Reliability (AzRel) group.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m now part of the AzRel Risk SRE team, focusing specifically on risks related to Azure Platform Reliability. My new role brings with it a significantly different skill set and area of focus, requiring a deep dive into new concepts, tools, and approaches. I&amp;rsquo;ve been fully engaged in learning and adapting, and I&amp;rsquo;ve also been actively developing new applications and solutions to address challenges faced by my new team.&lt;/p&gt;
&lt;p&gt;As the lead for the Platform Reliability Pillar, I&amp;rsquo;m utilizing advanced technologies like LLMs using OpenAI and SemanticKernel for comprehensive Outage Analysis. My role involves driving detailed cross-team analysis, fostering collaboration, and innovating solutions to improve the reliability and performance of the Azure platform. It&amp;rsquo;s a busy but exciting time, and I&amp;rsquo;m thrilled by the opportunity to make a meaningful impact with my new team and role. How often do you get to say that you help keep the Azure platform running smoothly?&lt;/p&gt;
&lt;p&gt;Additionally, I&amp;rsquo;m continuing to speak at conferences around the world, sharing insights and staying connected with the community. As I find my new rhythm, expect to see more frequent posts and lots of interesting content coming your way.&lt;/p&gt;
&lt;p&gt;Thank you for your patience and support as I navigate this new chapter and continue to learn and grow in my career!&lt;/p&gt;</content:encoded></item><item><title>When Technology Fails, GitHub Codespaces Saves the Day</title><link>https://chris-ayers.com/posts/github-codespaces-saves-the-day/</link><pubDate>Fri, 30 Aug 2024 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/github-codespaces-saves-the-day/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Copenhagen Developers Festival"
 width="1024"
 height="374"
 src="/images/logos/copenhagen-developers-festival-logo_hu_29e92a30bba188d.png"
 srcset="/images/logos/copenhagen-developers-festival-logo_hu_29e92a30bba188d.png 800w, /images/logos/copenhagen-developers-festival-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/copenhagen-developers-festival-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;Last week, I spoke at the &lt;a href="https://cphdevfest.com/" target="_blank" rel="noreferrer"&gt;Copenhagen Developers Festival&lt;/a&gt; about using &lt;a href="https://github.com/features/codespaces" target="_blank" rel="noreferrer"&gt;GitHub Codespaces&lt;/a&gt; for cloud-based development. This is an amazing conference with an incredible lineup. I&amp;rsquo;ve done this talk plenty of times, sharing how flexible and convenient cloud development environments can be. But this time, things didn&amp;rsquo;t go as planned.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Copenhagen Developers Festival"
 width="1024"
 height="374"
 src="/images/logos/copenhagen-developers-festival-logo_hu_29e92a30bba188d.png"
 srcset="/images/logos/copenhagen-developers-festival-logo_hu_29e92a30bba188d.png 800w, /images/logos/copenhagen-developers-festival-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/copenhagen-developers-festival-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;Last week, I spoke at the &lt;a href="https://cphdevfest.com/" target="_blank" rel="noreferrer"&gt;Copenhagen Developers Festival&lt;/a&gt; about using &lt;a href="https://github.com/features/codespaces" target="_blank" rel="noreferrer"&gt;GitHub Codespaces&lt;/a&gt; for cloud-based development. This is an amazing conference with an incredible lineup. I&amp;rsquo;ve done this talk plenty of times, sharing how flexible and convenient cloud development environments can be. But this time, things didn&amp;rsquo;t go as planned.&lt;/p&gt;
&lt;p&gt;I arrived early, as usual, to set up and greet attendees. When I tried to connect my laptop to the screen, though, nothing appeared. It wasn&amp;rsquo;t a simple projector issue either; the recording equipment was more complicated than I expected. I had two adapters with me, and the conference staff handed me two more, but none of them worked. I rebooted, resized my display, tried everything I could think of-still nothing.&lt;/p&gt;
&lt;p&gt;By now, I was a few minutes into my time slot, and the presentation seemed in jeopardy. I borrowed a local laptop, only to discover it was in Danish. Cue the comedy of errors! Finding the &lt;code&gt;*&lt;/code&gt; for my password and the &lt;code&gt;/&lt;/code&gt; for URLs was a nightmare. I ended up copying and pasting the &lt;code&gt;/&lt;/code&gt; multiple times just to get around. Hopefully, the audience found it more amusing than I did.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="the Danish keyboard layout"
 src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/84/KB_Danish.svg/630px-KB_Danish.svg.png"
 &gt;&lt;/figure&gt;
&lt;p&gt;Despite the keyboard struggles, I managed to pull it off. I used the browser to present my slides, which are hosted on GitHub Pages at &lt;a href="http://chris-ayers.com/dev-containers/" target="_blank" rel="noreferrer"&gt;http://chris-ayers.com/dev-containers&lt;/a&gt;. We discussed the Dev Containers spec, hosted at &lt;a href="https://containers.dev" target="_blank" rel="noreferrer"&gt;containers.dev&lt;/a&gt;. Then, I launched &lt;a href="https://github.com/features/codespaces" target="_blank" rel="noreferrer"&gt;GitHub Codespaces&lt;/a&gt; using my repository &lt;a href="https://github.com/codebytes/dev-containers" target="_blank" rel="noreferrer"&gt;https://github.com/codebytes/dev-containers&lt;/a&gt; and a few others (&lt;a href="https://github.com/python/cpython" target="_blank" rel="noreferrer"&gt;cpython&lt;/a&gt; and &lt;a href="https://github.com/home-assistant/core" target="_blank" rel="noreferrer"&gt;Home Assistant&lt;/a&gt;) to demonstrate its capabilities.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="GitHub Codespaces"
 width="750"
 height="350"
 src="/images/logos/github-codespaces-logo.png"
 srcset="/images/logos/github-codespaces-logo.png 800w, /images/logos/github-codespaces-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/github-codespaces-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;What could have been a total disaster turned into a great example of how powerful and resilient modern development tools are. &lt;a href="https://github.com/features/codespaces" target="_blank" rel="noreferrer"&gt;GitHub Codespaces&lt;/a&gt; didn&amp;rsquo;t just save my presentation; it became the star of the show, proving that with the right tools, we can roll with the punches and keep going.&lt;/p&gt;
&lt;p&gt;So, the next time technology decides not to cooperate, just remember-if your work&amp;rsquo;s in the cloud, you&amp;rsquo;re always just a few clicks away from getting back on track.&lt;/p&gt;</content:encoded></item><item><title>Writing Regex with Copilot</title><link>https://chris-ayers.com/posts/regex-with-copilot/</link><pubDate>Tue, 09 Apr 2024 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/regex-with-copilot/</guid><description>&lt;p&gt;I had a real world example today that I wanted to share about copilot helping me with a coding problem. A friend reached out asking if I knew regex. Its been a bit, but I was happy to help because I thought we could figure it out.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I had a real world example today that I wanted to share about copilot helping me with a coding problem. A friend reached out asking if I knew regex. Its been a bit, but I was happy to help because I thought we could figure it out.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Problem
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Her use case was that she had a string and she wanted to match a word only if another word appeared before it. She works at a University and is doing improvements to the CMS templates. They needed to do some updates to a Template only if it included certain tags. She was able to give me a sample that I could work with.&lt;/p&gt;
&lt;p&gt;She was able to boil it down to a simple example. Given the sample, &amp;lsquo;I think puppies are super cute, but turtles are super cute too.&amp;rsquo; make a regex that matches turtles only if puppies appears before it.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Solution
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Its been a while so I used GitHub Copilot Chat to help me out.&lt;/p&gt;
&lt;blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Given the sample, &amp;lsquo;I think puppies are super cute, but turtles are super cute too.&amp;rsquo; Make a regex that matches turtles only if puppies appears before it.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Sure, here is a regular expression that matches &amp;ldquo;turtles&amp;rdquo; only if &amp;ldquo;puppies&amp;rdquo; appears before it in the text:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;puppies.*turtles&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This regular expression works as follows:&lt;/p&gt;
&lt;p&gt;puppies matches the characters &amp;ldquo;puppies&amp;rdquo; literally.
.* is a special sequence that matches any character (except for a newline) 0 or more times.
turtles matches the characters &amp;ldquo;turtles&amp;rdquo; literally.&lt;/p&gt;
&lt;p&gt;So, this regular expression will match any string that has &amp;ldquo;turtles&amp;rdquo; somewhere after &amp;ldquo;puppies&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;We are using regexer.com to test out the regexes. The result was close but we got in a back and forth refining the regex.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Testing regexes on regexer.com"
 width="1107"
 height="310"
 src="/images/regex-copilot-1_hu_814a2b7f400f660d.png"
 srcset="/images/regex-copilot-1_hu_814a2b7f400f660d.png 800w, /images/regex-copilot-1.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/regex-copilot-1.png"&gt;&lt;/figure&gt;
&lt;blockquote&gt;&lt;blockquote&gt;&lt;p&gt;I want to match and return only the word turtles, not additional text.
To match and return only the word &amp;ldquo;turtles&amp;rdquo;, but only if &amp;ldquo;puppies&amp;rdquo; appears before it in the text, you can use a lookahead assertion in your regular expression. Here&amp;rsquo;s how you can do it:&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;&lt;code&gt;(?&amp;lt;=puppies.*)(turtles)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This regular expression works as follows:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(?&amp;lt;=puppies.*)&lt;/code&gt; is a positive lookbehind assertion that checks for the presence of &amp;ldquo;puppies&amp;rdquo; followed by any characters before the main expression.
&lt;code&gt;(turtles)&lt;/code&gt; is the main expression that matches the word &amp;ldquo;turtles&amp;rdquo;.
So, this regular expression will match the word &amp;ldquo;turtles&amp;rdquo; only if it appears somewhere after the word &amp;ldquo;puppies&amp;rdquo;, but it will only return &amp;ldquo;turtles&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Awesome!
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="matching turtles"
 width="976"
 height="303"
 src="/images/regex-copilot-2_hu_d87ba684d97725e0.png"
 srcset="/images/regex-copilot-2_hu_d87ba684d97725e0.png 800w, /images/regex-copilot-2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/regex-copilot-2.png"&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="not matching turtles"
 width="1017"
 height="345"
 src="/images/regex-copilot-3_hu_f634574e53c08a0c.png"
 srcset="/images/regex-copilot-3_hu_f634574e53c08a0c.png 800w, /images/regex-copilot-3.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/regex-copilot-3.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Done, Or Are We?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;My friend then threw a curveball at me. She wanted to match turtles only if puppies appears even if there are line breaks or other paragraphs. Like in this sample:&lt;/p&gt;
&lt;p&gt;&amp;lsquo;I think puppies are super cute,&lt;br /&gt; but turtles are super cute too.&amp;rsquo;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see if we can modify the regex to match this.&lt;/p&gt;
&lt;blockquote&gt;&lt;blockquote&gt;&lt;p&gt;Given the sample, &amp;lsquo;I think are super cute, but puppies turtles are super cute too.&amp;rsquo; and the regex &lt;code&gt;(?&amp;lt;=puppies.*)(turtles)&lt;/code&gt; I would like this to match turtles only if puppies appears even if there are line breaks or other paragraphs.. Like in this sample:
&amp;lsquo;I think puppies are super cute,
but turtles are super cute too.&amp;rsquo;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;The regular expression &lt;code&gt;(?&amp;lt;=puppies.*)(turtles)&lt;/code&gt; you provided uses a positive lookbehind &lt;code&gt;(?&amp;lt;=puppies.*)&lt;/code&gt; to check if &amp;ldquo;turtles&amp;rdquo; is preceded by &amp;ldquo;puppies&amp;rdquo;. However, the . character does not match line breaks in regular expressions.&lt;/p&gt;
&lt;p&gt;To match &amp;ldquo;turtles&amp;rdquo; only if &amp;ldquo;puppies&amp;rdquo; appears before it, even if there are line breaks or other paragraphs, you can use the &lt;code&gt;[\s\S]*&lt;/code&gt; pattern instead of .*. The &lt;code&gt;[\s\S]&lt;/code&gt; pattern matches any character including line breaks. Here&amp;rsquo;s how you can modify your regex:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(?&amp;lt;=puppies[\s\S]*)(turtles)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will match &amp;ldquo;turtles&amp;rdquo; only if &amp;ldquo;puppies&amp;rdquo; appears before it, regardless of line breaks or other paragraphs. For example, it will match &amp;ldquo;turtles&amp;rdquo; in the following text:&lt;/p&gt;
&lt;p&gt;I think puppies are super cute,
but turtles are super cute too.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Lets try this out.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="matching turtles same line"
 width="997"
 height="328"
 src="/images/regex-copilot-4_hu_1bb9359c80c3152.png"
 srcset="/images/regex-copilot-4_hu_1bb9359c80c3152.png 800w, /images/regex-copilot-4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/regex-copilot-4.png"&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="matching turtles different lines"
 width="664"
 height="373"
 src="/images/regex-copilot-5.png"
 srcset="/images/regex-copilot-5.png 800w, /images/regex-copilot-5.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/regex-copilot-5.png"&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="not matching turtles"
 width="657"
 height="388"
 src="/images/regex-copilot-6.png"
 srcset="/images/regex-copilot-6.png 800w, /images/regex-copilot-6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/regex-copilot-6.png"&gt;&lt;/figure&gt;
&lt;p&gt;Everything looks like its working.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;At this point, my friend had told me she had spent over an hour on stack overflow and other sites trying to figure this out. She had been hesitant to use GitHub Copilot but I think this was her aha moment. We literally solved her problem in under 5 minutes.&lt;/p&gt;
&lt;p&gt;I have been using it for a while now and I am happy with the results. I don&amp;rsquo;t worry as much about syntax, I&amp;rsquo;m really more focused on &lt;strong&gt;WHAT&lt;/strong&gt; I want to do and not &lt;strong&gt;HOW&lt;/strong&gt; to do it. I think this is a game changer for me. I&amp;rsquo;m excited to see how this tool evolves.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t tried out GitHub Copilot, I would recommend it. &lt;a href="https://github.com/features/copilot" target="_blank" rel="noreferrer"&gt;https://github.com/features/copilot&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Containerizing .NET - Part 2 - Considerations</title><link>https://chris-ayers.com/posts/containerizing-dotnet-part-2/</link><pubDate>Mon, 19 Feb 2024 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/containerizing-dotnet-part-2/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=".NET"
 width="456"
 height="456"
 src="/images/logos/dotnet-logo.png"
 srcset="/images/logos/dotnet-logo.png 800w, /images/logos/dotnet-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dotnet-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;This is part 2 of the Containerizing .NET series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/12/03/containerizing-dotnet-part-1" &gt;Containerizing .NET: Part 1 - A Guide to Containerizing .NET Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2024/2/19/containerizing-dotnet-part-2" &gt;Containerizing .NET: Part 2 - Considerations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Considerations
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Welcome to the second installment in our series on containerizing .NET applications. Building on the foundation laid in our first article-where we introduced Dockerfiles and the &lt;code&gt;dotnet publish&lt;/code&gt; command-this piece delves into pivotal considerations for transitioning .NET applications into containers. As containers become a cornerstone of the ecosystem, understanding these factors is critical for developers aiming to enhance application deployment in containerized environments.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=".NET"
 width="456"
 height="456"
 src="/images/logos/dotnet-logo.png"
 srcset="/images/logos/dotnet-logo.png 800w, /images/logos/dotnet-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dotnet-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;This is part 2 of the Containerizing .NET series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/12/03/containerizing-dotnet-part-1" &gt;Containerizing .NET: Part 1 - A Guide to Containerizing .NET Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2024/2/19/containerizing-dotnet-part-2" &gt;Containerizing .NET: Part 2 - Considerations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Considerations
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Welcome to the second installment in our series on containerizing .NET applications. Building on the foundation laid in our first article-where we introduced Dockerfiles and the &lt;code&gt;dotnet publish&lt;/code&gt; command-this piece delves into pivotal considerations for transitioning .NET applications into containers. As containers become a cornerstone of the ecosystem, understanding these factors is critical for developers aiming to enhance application deployment in containerized environments.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Architectural Alignment and Security
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Architectural Considerations in Containerization
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;As we delve into containerizing .NET applications, it&amp;rsquo;s essential to recognize that the architectural style-whether you&amp;rsquo;re working with a microservices pattern or a monolithic design-plays a pivotal role in shaping the containerization strategy. However, regardless of the architecture chosen, there are several critical considerations that universally impact the transition to a containerized environment.&lt;/p&gt;

&lt;h3 class="relative group"&gt;CI/CD and Deployment Strategies
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The move to containers necessitates a reevaluation of your Continuous Integration/Continuous Deployment (CI/CD) pipelines and deployment strategies. Containers offer the advantage of immutable deployment artifacts, which can streamline the CI/CD process by ensuring consistency across different environments. However, this also means adapting your pipelines to handle container image building, storage, and deployment, which may involve new tools and practices. I will dive into those in a future article.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Scalability Concerns
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Ensuring Scalable Design
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Your application must be architected to support horizontal scaling, allowing for the addition or removal of container instances based on demand. This scalability is crucial for optimizing resource use and maintaining performance across varying loads.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Session State Management
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;In containerized architectures, statelessness is paramount. Containers, designed to be ephemeral, should not maintain session states internally, as this can impede scalability. Opt for external storage solutions like Redis, SQL databases, or distributed caches to handle session states, ensuring your application remains scalable and responsive to load changes.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Dependency Management Strategies
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Linux Compatibility
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Migration to containerized environments often involves transitioning from Windows to Linux-based containers. Ensure that your application&amp;rsquo;s dependencies and libraries are compatible with Linux, and that your Dockerfile and container environment are configured accordingly.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Handling Internal Dependencies
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Ensure all necessary libraries and components are either bundled within the container or accessible via network endpoints, enabling your application to function seamlessly in its containerized form.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Integrating with External Services
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Containerization demands a dynamic approach to connecting with external services like databases and messaging systems. Implement configurations that allow for flexible service discovery and connections through environment variables or specialized discovery tools.&lt;/p&gt;

&lt;h2 class="relative group"&gt;File and Network Access
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;File Access Considerations
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The encapsulated filesystem within containers requires a strategic approach to file access. Unlike traditional deployments where applications might directly access local file paths, containerized applications should be designed with portability and flexibility in mind. Here are some strategies to consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Volume Mounts&lt;/strong&gt;: Use Docker volumes or Kubernetes persistent volumes to persist data outside containers, enabling state persistence across container restarts and deployments. This approach is particularly useful for databases, logs, or any data that needs to survive beyond the container&amp;rsquo;s lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud Storage Services&lt;/strong&gt;: For applications that require access to large amounts of data or need to share data across multiple instances, integrating with cloud storage services (like Azure Blob Storage, Amazon S3, or Google Cloud Storage) provides a scalable and secure solution. This not only decouples your application from the underlying infrastructure but also enhances scalability by leveraging the cloud provider&amp;rsquo;s global network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File Permissions and Security&lt;/strong&gt;: Carefully manage file permissions within the container to prevent unauthorized access. Ensure that your application runs with the least privileges necessary to access only the files it needs, enhancing security within the containerized environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Network Configuration and Service Discovery
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Containers often run in orchestrated environments where networking is dynamically managed, and services discover each other through service discovery mechanisms rather than static IP addresses or hostnames. Consider these aspects to ensure robust network configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Service Discovery&lt;/strong&gt;: Utilize service discovery tools provided by container orchestration platforms (like Kubernetes DNS or Docker Swarm&amp;rsquo;s embedded DNS) to dynamically discover and communicate with other services within the cluster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container Networking Models&lt;/strong&gt;: Familiarize yourself with the container network models (such as bridge, overlay, or host networks) and choose the appropriate model based on your application&amp;rsquo;s needs. For instance, overlay networks facilitate communication between containers across different hosts in a cluster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port Configuration and Exposition&lt;/strong&gt;: Explicitly define and manage which ports are exposed by your container and how they are mapped to the host system. This is crucial for ensuring that your application&amp;rsquo;s services are accessible as intended while maintaining control over network security.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Identity and Authentication Adjustments
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In containerized environments, traditional methods of managing identity and authentication may not directly apply. Here are ways to adapt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Managed Identities for Azure Resources&lt;/strong&gt;: Azure offers managed identities, automatically handling the management of credentials for accessing Azure services. This eliminates the need to store sensitive credentials in your application code or configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OAuth and OpenID Connect&lt;/strong&gt;: Implement OAuth 2.0 and OpenID Connect protocols to manage user identities and authenticate against identity providers. This approach is effective for applications that require user authentication and can be integrated with most identity providers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets Management&lt;/strong&gt;: Use a secrets management tool (like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault) to securely store and access API keys, database connection strings, and other sensitive information. Modern container orchestration platforms, such as Kubernetes, offer native secrets management capabilities, allowing you to inject secrets into containers at runtime securely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt;: Implement RBAC within your application and infrastructure to ensure that only authorized users and services can perform specific actions. This is particularly important in microservices architectures where different services may have different access requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Configuration Management
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Efficient configuration management emerges as a critical component in the containerization of .NET applications. The dynamic nature of containerized environments necessitates a flexible and secure approach to configuring applications, ensuring they can adapt to different environments without necessitating changes to the container images themselves.&lt;/p&gt;
&lt;p&gt;The .NET ecosystem offers various strategies for managing configurations effectively, aligning with cloud-native best practices. There are configuration providers for reading settings from environment variables, JSON files, and other sources, enabling applications to adapt to different environments seamlessly. Here are some strategies to consider:&lt;/p&gt;

&lt;h3 class="relative group"&gt;Environment Variables
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dynamic Configuration&lt;/strong&gt;: Utilize environment variables to externalize configuration settings, enabling applications to adapt to various environments (development, staging, production) seamlessly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;: Define environment variables in container orchestration configurations, such as Kubernetes manifests or Docker Compose files, to inject settings at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Configuration Files
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Externalized Settings&lt;/strong&gt;: Store configuration settings in external files (e.g., &lt;code&gt;appsettings.json&lt;/code&gt; for .NET applications) that can be mounted into containers at runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volume Mounts&lt;/strong&gt;: Use Docker volumes or Kubernetes ConfigMaps and Secrets to mount configuration files into containers, ensuring sensitive information is managed securely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Centralized Configuration Services
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud Services&lt;/strong&gt;: Leverage cloud-based configuration services like Azure App Configuration or AWS Parameter Store to centralize and manage application settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service Discovery&lt;/strong&gt;: Integrate service discovery mechanisms to dynamically locate services and resources, reducing the need for hard-coded configurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Secrets Management
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure Storage&lt;/strong&gt;: Utilize dedicated secrets management tools (e.g., Azure Key Vault, HashiCorp Vault) to securely store and manage sensitive configuration data such as passwords, tokens, and connection strings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime Injection&lt;/strong&gt;: Automate the injection of secrets into containers at runtime using platforms like Kubernetes Secrets, CSI Secret Store, or specific cloud provider integrations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Immutable Configurations
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Immutable Infrastructure&lt;/strong&gt;: Adopt an immutable infrastructure mindset, where configuration changes require redeploying containers rather than modifying running containers. This approach enhances consistency, reliability, and auditability across environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Configuration Drift Prevention
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version Control&lt;/strong&gt;: Keep configuration files and definitions under version control to track changes and prevent configuration drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Integration&lt;/strong&gt;: Integrate configuration management into the CI/CD pipeline, ensuring configurations are tested and validated before deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Incorporating these configuration management strategies within the containerization process for .NET applications not only enhances flexibility and scalability but also bolsters security and compliance, aligning with best practices for cloud-native development.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Security and Compliance
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In the realm of containerization, adherence to stringent security and compliance frameworks becomes paramount. The encapsulated nature of containers introduces unique security considerations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability Scanning&lt;/strong&gt;: Implementing automated tools to scan container images for known vulnerabilities at each stage of the CI/CD pipeline ensures that only secure images are deployed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-Root Privileges&lt;/strong&gt;: Running containers as non-root users minimizes the risk of privileged escalations if a container is compromised. This practice is essential for limiting the attack surface and safeguarding the underlying host system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secrets Management&lt;/strong&gt;: Securely handling secrets necessitates moving away from embedding sensitive information within container images or environment variables. Utilizing dedicated secrets management tools or services, such as Kubernetes Secrets, HashiCorp Vault, or Azure Key Vault, allows for dynamic, secure injection of credentials and keys at runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network Policies and Firewall Rules&lt;/strong&gt;: Enforcing strict network policies and firewall rules to control inbound and outbound traffic to containers can prevent unauthorized access and mitigate potential attacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read-Only Filesystems&lt;/strong&gt;: Where applicable, configuring containers with read-only filesystems can prevent malicious attempts to alter the runtime environment, further enhancing security posture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuous Monitoring and Logging&lt;/strong&gt;: Implementing real-time monitoring and logging mechanisms to detect unusual activities and potential security breaches. Tools like Prometheus, Grafana, and ELK stack play a pivotal role in observing container behavior and ensuring operational integrity.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Tools, Frameworks, and Ecosystems
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Distributed Application Runtime (DAPR)
 
 
 
 
&lt;/h3&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="DAPR"
 width="1197"
 height="583"
 src="/images/logos/dapr-logo_hu_2d956ced629fd0e6.png"
 srcset="/images/logos/dapr-logo_hu_2d956ced629fd0e6.png 800w, /images/logos/dapr-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dapr-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;DAPR (Distributed Application Runtime) has emerged as a transformative tool, simplifying the development of distributed applications. DAPR abstracts complex tasks such as state management, service discovery, and messaging into straightforward, consistent APIs, enabling developers to focus on business logic rather than infrastructure concerns. This abstraction is particularly beneficial in a containerized environment, where applications must be flexible, scalable, and capable of running across diverse platforms.&lt;/p&gt;
&lt;p&gt;DAPR&amp;rsquo;s cloud-agnostic design ensures seamless integration with various cloud services, including Azure, without locking developers into a specific ecosystem. It supports dynamic configuration and facilitates local development, mirroring cloud environments on developers&amp;rsquo; machines. By decoupling application logic from infrastructure intricacies, DAPR enhances portability and eases the transition of .NET applications into the cloud-native landscape, making it an indispensable tool for developers navigating the complexities of modern application development.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Azure Developer CLI
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The Azure Developer CLI (azd) significantly streamlines the journey of containerizing and deploying .NET applications to the cloud. A pivotal feature, &lt;code&gt;azd init&lt;/code&gt;, automates the scaffolding process, generating Dockerfiles and Azure resource definitions tailored to your project&amp;rsquo;s needs. This command is instrumental for developers seeking to swiftly prepare their applications for Azure, ensuring an optimized setup for either Azure Container Apps (ACA) or Azure Kubernetes Service (AKS). By abstracting the complexities of Docker and Kubernetes, azd allows developers to concentrate on building their applications, while effortlessly integrating with Azure&amp;rsquo;s robust cloud infrastructure.&lt;/p&gt;

&lt;h3 class="relative group"&gt;.NET Aspire
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;.NET Aspire equips developers with an opinionated framework tailored for crafting observable, distributed .NET applications that are primed for cloud environments. It simplifies the development process by offering a curated collection of NuGet packages, each addressing specific cloud-native application challenges such as service integration, state management, and messaging. .NET Aspire stands out by facilitating the creation of microservices and distributed applications, enabling seamless service connections and promoting architectural best practices. This framework not only accelerates the development of cloud-ready .NET applications but also ensures they are scalable, resilient, and maintainable, aligning with the principles of modern, cloud-native development.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The journey to containerizing .NET applications is paved with considerations that span architecture, security, performance, and beyond. By addressing these aspects thoughtfully, developers can harness the full potential of containerization, ensuring their .NET applications are efficient, secure, and poised for the cloud-native future. Stay tuned for subsequent articles, where we&amp;rsquo;ll explore strategies and tools to navigate these considerations, empowering your .NET applications to excel in a containerized landscape.&lt;/p&gt;</content:encoded></item><item><title>Dev Containers - Part 1</title><link>https://chris-ayers.com/posts/dev-containers-part-1/</link><pubDate>Wed, 27 Dec 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/dev-containers-part-1/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="dev containers"
 src="/images/logos/dev-containers-logo.svg"
 &gt;&lt;/figure&gt;
&lt;blockquote&gt;&lt;p&gt;This article is part of the Festive Tech Calendar 2023. For more articles in the series by other authors, visit &lt;a href="https://festivetechcalendar.com/" target="_blank" rel="noreferrer"&gt;https://festivetechcalendar.com/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dev Containers can revolutionize the way we approach development environments, offering a fast, consistent setup across different projects. As a developer who uses Dev Containers in VS Code for various projects, I&amp;rsquo;ve experienced firsthand the benefits of having an environment that&amp;rsquo;s ready to go as soon as I clone a project.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="low"
 alt="dev containers"
 src="/images/logos/dev-containers-logo.svg"
 &gt;&lt;/figure&gt;
&lt;blockquote&gt;&lt;p&gt;This article is part of the Festive Tech Calendar 2023. For more articles in the series by other authors, visit &lt;a href="https://festivetechcalendar.com/" target="_blank" rel="noreferrer"&gt;https://festivetechcalendar.com/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Dev Containers can revolutionize the way we approach development environments, offering a fast, consistent setup across different projects. As a developer who uses Dev Containers in VS Code for various projects, I&amp;rsquo;ve experienced firsthand the benefits of having an environment that&amp;rsquo;s ready to go as soon as I clone a project.&lt;/p&gt;
&lt;p&gt;I use Dev Containers in VS Code or GitHub Codespaces for all my development work. I have a Dev Container for each project I work on. This ensures that I have a consistent environment for each project. It also allows me to quickly get up and running with a new project. I don&amp;rsquo;t have to worry about installing dependencies or setting up my environment. I can simply clone the project and start coding.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What are Dev Containers?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Dev Containers, or Development Containers, are an innovative tool for modern software development. They leverage the power of containerization technology, similar to Docker, to create isolated, consistent, and fully equipped development environments. Here&amp;rsquo;s what sets them apart:&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Dev Container Architecture"
 width="968"
 height="402"
 src="/images/dev-containers-architecture_hu_62bb226ba51ae1d0.png"
 srcset="/images/dev-containers-architecture_hu_62bb226ba51ae1d0.png 800w, /images/dev-containers-architecture.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/dev-containers-architecture.png"&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Environment Isolation&lt;/strong&gt;: Each Dev Container operates in its own isolated environment. This means that it can have its own set of tools, software dependencies, and settings, separate from the host machine and other containers. This isolation reduces &amp;ldquo;it works on my machine&amp;rdquo; problems, ensuring that the environment is consistent across all developers&amp;rsquo; machines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibility and Versatility&lt;/strong&gt;: Dev Containers can be tailored to support various programming languages, frameworks, and tools. They can range from simple environments with just a basic editor and runtime to more complex setups with databases, compilers, debuggers, and other tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration with Development Tools&lt;/strong&gt;: Perhaps one of the most powerful features of Dev Containers is their integration with popular development tools like Visual Studio Code. This integration allows developers to use the rich features of their favorite IDEs, like IntelliSense, debuggers, and extensions, inside the container.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Why Use Dev Containers?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The adoption of Dev Containers in your development process can bring numerous advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rapid Onboarding and Consistency&lt;/strong&gt;: New team members can get up and running quickly. Instead of spending time setting up their development environment, they can simply pull the Dev Container configuration and start coding. This process ensures that every team member works in an environment that is consistent with production and their peers, reducing &amp;ldquo;works on my machine&amp;rdquo; issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reproducible Environments&lt;/strong&gt;: Dev Containers ensure that the development environment is reproducible. This is critical when working with complex applications that require specific configurations, tools, or dependencies. With a Dev Container, these configurations are codified, making it easy to recreate the environment anytime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Seamless CI/CD Integration&lt;/strong&gt;: Dev Containers can be integrated with Continuous Integration and Continuous Deployment (CI/CD) pipelines. This ensures that the applications are built and tested in the same environment in which they were developed, leading to fewer surprises when moving from development to production.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Platform Independence&lt;/strong&gt;: Developers can work on different operating systems or platforms without worrying about compatibility issues. The containerized environment abstracts away the underlying platform, allowing for a seamless development experience across Windows, macOS, and Linux.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus on Coding, Not Configuration&lt;/strong&gt;: Dev Containers abstract away the need for developers to manage their development environment. This allows developers to focus on writing code, rather than spending time configuring and troubleshooting their local setup.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Solving Version Conflicts with Dev Containers: A Real-World Scenario
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;The Challenge of Multiple Projects with Different Dependencies
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;In software development, it&amp;rsquo;s common to work on multiple projects simultaneously. However, this can lead to significant challenges, particularly when these projects depend on different versions of languages or tools. Let&amp;rsquo;s consider an example involving Python or Node.js.&lt;/p&gt;
&lt;p&gt;Imagine working on two separate projects: Project A and Project B. Project A requires Node.js 18, while Project B needs Node.js 20. Setting up these projects on the same development machine traditionally leads to version conflicts. Installing the dependencies for Project B might break Project A, and vice versa. This is a common headache for developers, leading to a lot of time spent on managing and troubleshooting environments rather than coding.&lt;/p&gt;

&lt;h3 class="relative group"&gt;How Dev Containers Provide a Solution
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Dev Containers offer an elegant solution to this problem. With Dev Containers, each project runs in its own isolated containerized environment. This isolation means you can have different versions of Python, Node.js, or any other dependencies in each container without them conflicting with each other&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: Each Dev Container provides an isolated environment. You can configure Project A&amp;rsquo;s container with Node.js 18, and Project B&amp;rsquo;s container with Node.js 20.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: The environments remain consistent regardless of other projects. Setting up Project B won&amp;rsquo;t affect Project A at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ease of Switching&lt;/strong&gt;: Switching between projects is as simple as switching containers. There&amp;rsquo;s no need to reconfigure your environment or worry about conflicting versions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replicability&lt;/strong&gt;: These containerized environments can be replicated across different machines or team members, ensuring that everyone is working in the same setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specialized Configurations&lt;/strong&gt;: Dev Containers can include specific configurations for each project, such as extensions, settings, and tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Real-World Applications of Dev Containers: CPython and Home Assistant
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;CPython: Streamlining Python Development
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The CPython project, the source code of the Python programming language itself, is an excellent example of Dev Containers in action. CPython&amp;rsquo;s GitHub repository features a &lt;a href="https://github.com/python/cpython/blob/main/.devcontainer/devcontainer.json" target="_blank" rel="noreferrer"&gt;&lt;code&gt;devcontainer.json&lt;/code&gt;&lt;/a&gt; file, which simplifies the setup process for contributors. This file specifies a Docker image tailored for Python development, ensuring that contributors have all the necessary tools and dependencies.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Key Features of CPython&amp;rsquo;s Dev Container
 
 
 
 
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Specific Docker Image&lt;/strong&gt;: The container uses an image with all dependencies required to build and test Python.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VS Code Extensions&lt;/strong&gt;: Extensions like Python, C++, and Test Explorer are pre-configured, streamlining the development experience.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent Settings&lt;/strong&gt;: Settings are predefined to match the project&amp;rsquo;s coding standards, ensuring consistency across all contributions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Home Assistant: Facilitating Smart Home Development
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Home Assistant, an open-source home automation platform, also leverages Dev Containers to provide a consistent development environment. Their repository includes a &lt;a href="https://github.com/home-assistant/core/blob/dev/.devcontainer/devcontainer.json" target="_blank" rel="noreferrer"&gt;&lt;code&gt;devcontainer.json&lt;/code&gt;&lt;/a&gt; file, which sets up an environment with all the necessary dependencies and extensions for development.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Advantages for Home Assistant Contributors
 
 
 
 
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Custom Docker Image&lt;/strong&gt;: Ensures that all the specific requirements for developing Home Assistant are met, including the right versions of Python and other dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predefined Extensions and Settings&lt;/strong&gt;: The Dev Container includes extensions like pylint and pylance to align with the project&amp;rsquo;s development practices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ease of Contribution&lt;/strong&gt;: New contributors can quickly set up a development environment that is consistent with the project&amp;rsquo;s standards, facilitating easier and more efficient contributions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Tool Support for Dev Containers
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Dev Containers are supported by a range of tools and services, each enhancing the development experience in unique ways. Here&amp;rsquo;s an overview of some of the key tools and integrations:&lt;/p&gt;

&lt;h3 class="relative group"&gt;Visual Studio Code and Dev Containers Extension
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integrated Development Environment&lt;/strong&gt;: Visual Studio Code, along with its Dev Containers extension, is a key component in the Dev Container ecosystem. It provides a full range of features for containerized development, including IntelliSense, debuggers, and various extensions.
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Dev Containers Extension"
 width="2230"
 height="460"
 src="/images/dev-containers-extension_hu_541c4f62d0fb396.png"
 srcset="/images/dev-containers-extension_hu_541c4f62d0fb396.png 800w, /images/dev-containers-extension_hu_31cc677511bb6852.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/dev-containers-extension.png"&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Docker Integration&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Local Connection&lt;/strong&gt;: The extension seamlessly integrates with local Docker installations, allowing developers to easily build and manage containers on their machine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remote Connection&lt;/strong&gt;: Supports connecting to Docker in remote environments, such as VMs or cloud-hosted servers, enabling development in complex, distributed architectures.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enhanced Workflow&lt;/strong&gt;: This integration simplifies workflows by managing container lifecycles, port forwarding, and providing a consistent development environment regardless of the host system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;GitHub Codespaces
 
 
 
 
&lt;/h3&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="GitHub Codespaces"
 width="1440"
 height="1030"
 src="/images/dev-containers-codespaces_hu_2d73509b32e30c75.png"
 srcset="/images/dev-containers-codespaces_hu_2d73509b32e30c75.png 800w, /images/dev-containers-codespaces_hu_afbc405354d6b3b6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/dev-containers-codespaces.png"&gt;&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cloud-Based Environments&lt;/strong&gt;: Offers a range of VM options from 2 to 32 cores and up to 64 GB of memory for versatile cloud development.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remote Access&lt;/strong&gt;: Easily accessible for coding via the browser or Visual Studio Code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quick Setup&lt;/strong&gt;: Features pre-configured environments for immediate project start-up and branch-specific setups from GitHub imports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Editor Compatibility&lt;/strong&gt;: Supports various editors, including the Codespaces web editor and Visual Studio Code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable Configurations&lt;/strong&gt;: Tailor environments using &lt;code&gt;devcontainer.json&lt;/code&gt;, with specific GitHub Codespaces properties.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure and Integrated&lt;/strong&gt;: Ensures a secure development experience in ephemeral VMs, integrated with numerous development tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Visual Studio
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C++ Project Support&lt;/strong&gt;: Visual Studio 2022 version 17.4 and later includes Dev Containers support for C++ projects, enhancing workflows in Linux and embedded development contexts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;IntelliJ IDEA
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Early Dev Containers Support&lt;/strong&gt;: Offers preliminary support for Dev Containers, enabling both remote operation via SSH and local usage with Docker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;CodeSandbox
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web-Based and Cloud Development&lt;/strong&gt;: CodeSandbox offers both an online IDE for rapid web development and cloud development environments running on a microVM architecture. It supports a range of specs and multiple editors, including the CodeSandbox web editor, VS Code, and the CodeSandbox iOS app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;DevPod
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flexible Environment Creation&lt;/strong&gt;: DevPod is a client-only tool that creates reproducible developer environments based on &lt;code&gt;devcontainer.json&lt;/code&gt;, compatible with various backends such as local machines, Kubernetes clusters, remote machines, or cloud VMs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Dev Container CLI
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reference Implementation&lt;/strong&gt;: The Dev Container CLI acts as the standard implementation for the Dev Container Spec, facilitating the creation and management of Dev Containers, particularly in CI/DevOps scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Building a Dev Container
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;You can build a Dev Container using the Command Palette in VS Code. This process involves choosing the right Dev Container for your project based on your needs.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Dev Containers Command Palette"
 width="1521"
 height="655"
 src="/images/dev-containers-add-files_hu_c6e9b5edfaef28f7.png"
 srcset="/images/dev-containers-add-files_hu_c6e9b5edfaef28f7.png 800w, /images/dev-containers-add-files_hu_65c6da447cf9c934.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/dev-containers-add-files.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Templates and Customizations
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Dev Containers come with various templates and customization options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Templates: Choose from base templates like Alpine, Debian, Ubuntu, or focus on specific languages or tools. &lt;a href="https://containers.dev/templates" target="_blank" rel="noreferrer"&gt;Full list of templates&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Dockerfile or Docker Compose: You can use a Dockerfile or Docker Compose file to define and configure your Dev Container.&lt;/li&gt;
&lt;li&gt;Customizations: Include features, extensions, settings, startup tasks, and networking configurations specific to your project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Features
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;You can add various features to your Dev Container:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLIs for cloud platforms like Azure, GitHub, GCP, AWS.&lt;/li&gt;
&lt;li&gt;Tools like Terraform, Kubernetes.&lt;/li&gt;
&lt;li&gt;Runtimes including Node, Python, Go, Java, .NET, PHP, Ruby, Rust, C/C++, C#.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://containers.dev/features" target="_blank" rel="noreferrer"&gt;Full list of features&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Samples
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/search?q=org%3Amicrosoft&amp;#43;vscode-remote-try-&amp;amp;type=Repositories" target="_blank" rel="noreferrer"&gt;vscode-remote-try-*&lt;/a&gt; repositories include sample Dev Containers for various languages and tools. These repositories are a great place to start if you&amp;rsquo;re new to Dev Containers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/vscode-remote-try-node" target="_blank" rel="noreferrer"&gt;Node Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/vscode-remote-try-python" target="_blank" rel="noreferrer"&gt;Python Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/vscode-remote-try-go" target="_blank" rel="noreferrer"&gt;Go Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/vscode-remote-try-java" target="_blank" rel="noreferrer"&gt;Java Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/vscode-remote-try-dotnetcore" target="_blank" rel="noreferrer"&gt;.NET Core Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/vscode-remote-try-rust" target="_blank" rel="noreferrer"&gt;Rust Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/microsoft/vscode-remote-try-cpp" target="_blank" rel="noreferrer"&gt;C++ Sample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Incorporating Dev Containers into your workflow can streamline development, ensuring consistency and efficiency. For more information, check out the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://containers.dev/templates" target="_blank" rel="noreferrer"&gt;Dev Container Templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://containers.dev/features" target="_blank" rel="noreferrer"&gt;Dev Container Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/docs/devcontainers/tutorial" target="_blank" rel="noreferrer"&gt;VSCode: Dev Containers Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/shows/beginners-series-to-dev-containers/" target="_blank" rel="noreferrer"&gt;Beginner&amp;rsquo;s Series to Dev Containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;My Talk on Dev Containers: &lt;a href="https://github.com/codebytes/dev-containers" target="_blank" rel="noreferrer"&gt;Codebytes/Dev-Containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Containerizing .NET - Part 1</title><link>https://chris-ayers.com/posts/containerizing-dotnet/</link><pubDate>Sun, 03 Dec 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/containerizing-dotnet/</guid><description>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=".NET"
 width="456"
 height="456"
 src="/images/logos/dotnet-logo.png"
 srcset="/images/logos/dotnet-logo.png 800w, /images/logos/dotnet-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dotnet-logo.png"&gt;&lt;/figure&gt;
&lt;blockquote&gt;&lt;p&gt;This article is part of C# Advent 2023. For more articles in the series by other authors, visit &lt;a href="https://www.csadvent.christmas/" target="_blank" rel="noreferrer"&gt;https://www.csadvent.christmas/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;This is the first in a series of articles on containerizing .NET applications. We&amp;rsquo;ll explore how to containerize .NET applications using Dockerfiles and &lt;code&gt;dotnet publish&lt;/code&gt;. Containers have become an essential part of the DevOps ecosystem, offering a lightweight, portable, and scalable solution for deploying applications. This process is crucial for developers looking to streamline app deployment in containerized environments, focusing on efficiency, security, compliance, and more.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=".NET"
 width="456"
 height="456"
 src="/images/logos/dotnet-logo.png"
 srcset="/images/logos/dotnet-logo.png 800w, /images/logos/dotnet-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/dotnet-logo.png"&gt;&lt;/figure&gt;
&lt;blockquote&gt;&lt;p&gt;This article is part of C# Advent 2023. For more articles in the series by other authors, visit &lt;a href="https://www.csadvent.christmas/" target="_blank" rel="noreferrer"&gt;https://www.csadvent.christmas/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;This is the first in a series of articles on containerizing .NET applications. We&amp;rsquo;ll explore how to containerize .NET applications using Dockerfiles and &lt;code&gt;dotnet publish&lt;/code&gt;. Containers have become an essential part of the DevOps ecosystem, offering a lightweight, portable, and scalable solution for deploying applications. This process is crucial for developers looking to streamline app deployment in containerized environments, focusing on efficiency, security, compliance, and more.&lt;/p&gt;
&lt;p&gt;You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/12/03/containerizing-dotnet-part-1" &gt;Containerizing .NET: Part 1 - A Guide to Containerizing .NET Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2024/2/19/containerizing-dotnet-part-2" &gt;Containerizing .NET: Part 2 - Considerations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;What are Containers?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Containers are a lightweight, virtualized environment that provide an isolated space for running applications. Unlike traditional virtual machines that require a full-fledged operating system, containers share the host system&amp;rsquo;s kernel but encapsulate an application&amp;rsquo;s code, dependencies, and libraries in a self-contained unit. This encapsulation ensures uniform and consistent application performance across different computing environments.&lt;/p&gt;
&lt;p&gt;Containers offer numerous benefits. They are efficient in terms of resource utilization, smaller in size, and require less overhead than virtual machines. Containers also enhance the portability of applications, enabling developers to easily move applications from local development machines to production servers. This portability, combined with their isolated nature, facilitates continuous integration and continuous deployment (CI/CD) practices, making containers a cornerstone of modern DevOps workflows. Moreover, containers help ensure software runs reliably when moved from one computing environment to another, mitigating the &amp;ldquo;it works on my machine&amp;rdquo; problem.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Dotnet with a Dockerfile
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Building and packaging dotnet applications into Docker images has been straightforward and well-documented. The &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/docker/build-container?tabs=linux&amp;amp;pivots=dotnet-8-0" target="_blank" rel="noreferrer"&gt;dotnet documentation&lt;/a&gt; provides an excellent starting point. A simple Dockerfile can build a dotnet application and package it into a Docker image, using multi-stage builds to keep the final image size small.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:8.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build-env&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/App&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Copy everything&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; . ./&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Restore as distinct layers&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; dotnet restore&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Build and publish a release&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; dotnet publish -c Release -o out&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Build runtime image&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:8.0&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/App&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;build-env /App/out .&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;dotnet&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DotNet.Docker.dll&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can build this docker image by running the following command, where &lt;code&gt;dotnet-docker&lt;/code&gt; is the name of the image.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build -t dotnet-docker .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I won&amp;rsquo;t go into all the details of a dockerfile or registries right now. We&amp;rsquo;ll cover that in a future article. However, while this approach is great for those wanting full control over the image-building process, it can be daunting for newcomers to containers or those who prefer to focus on building applications rather than managing Docker images.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Containerizing .NET with dotnet publish
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;.NET has a built-in mechanism for building and packaging applications into Docker images. This approach is ideal for those who want to focus on application development rather than managing Docker images. We&amp;rsquo;ll explore how to containerize .NET applications using &lt;code&gt;dotnet publish&lt;/code&gt;. This method doesn&amp;rsquo;t require a Dockerfile. Instead, it uses a set of publish properties to build and package applications into Docker images.&lt;/p&gt;
&lt;p&gt;Starting from scratch, we&amp;rsquo;ll containerize a simple dotnet application using the dotnet CLI.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new dotnet console application&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet new console -o hello-containers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Let&amp;rsquo;s add the required nuget package, Microsoft.NET.Build.Containers&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet add package Microsoft.NET.Build.Containers&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Now we can build and package our application into a docker image&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet publish -t:PublishContainer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;At this point you should see something like the following:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MSBuild version 17.8.3+195e7f5a3 &lt;span class="k"&gt;for&lt;/span&gt; .NET
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; All projects are up-to-date &lt;span class="k"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hello-containers -&amp;gt; /workspaces/dotnet-containers/hello-containers/bin/Release/net8.0/hello-containers.dll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hello-containers -&amp;gt; /workspaces/dotnet-containers/hello-containers/bin/Release/net8.0/publish/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Building image &lt;span class="s1"&gt;&amp;#39;hello-containers&amp;#39;&lt;/span&gt; with tags &lt;span class="s1"&gt;&amp;#39;latest&amp;#39;&lt;/span&gt; on top of base image &lt;span class="s1"&gt;&amp;#39;mcr.microsoft.com/dotnet/runtime:8.0&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Pushed image &lt;span class="s1"&gt;&amp;#39;hello-containers:latest&amp;#39;&lt;/span&gt; to &lt;span class="nb"&gt;local&lt;/span&gt; registry via &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Notice that we didn&amp;rsquo;t have to create a Dockerfile, we didn&amp;rsquo;t have to manage dependencies, and we didn&amp;rsquo;t have to package our application into a docker image.
Let&amp;rsquo;s compare the docker image we just created with the one we created earlier. The following is the docker image we created earlier using a Dockerfile.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker images
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REPOSITORY TAG IMAGE ID CREATED SIZE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello-containers latest 83963b52892f &lt;span class="m"&gt;5&lt;/span&gt; seconds ago 193MB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet-docker latest 2d24bdc0b15d &lt;span class="m"&gt;57&lt;/span&gt; seconds ago 217MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Making it Smaller
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;To make our images smaller, we have a few options. The first is to choose a smaller base image. Popular choices include Alpine, a lightweight Linux distribution, and Ubuntu Chiseled, a lightweight version of Ubuntu. Using these base images can significantly reduce the final image size.&lt;/p&gt;
&lt;p&gt;By introducing the &lt;code&gt;-p:ContainerFamily&lt;/code&gt; parameter, we can specify the image family; And the &lt;code&gt;-p:ContainerRepository&lt;/code&gt; parameter we can name our image. By leveraging image families like Alpine and Ubuntu Chiseled, we can significantly reduce the final image size without trimming or optimizing via Native AOT (Ahead of Time compilation). We&amp;rsquo;ll cover those optimization techniques in a future article.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Alpine
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Alpine Linux is a highly favored choice for container images, known for its lightweight nature and security-focused architecture. Its minimal footprint significantly enhances efficiency in resource usage, making it ideal for streamlined container images. This results in reduced overhead and improved performance in containerized applications. However, it&amp;rsquo;s important to consider certain aspects when using Alpine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Resource Optimization: Alpine&amp;rsquo;s small size optimizes resource consumption.&lt;/li&gt;
&lt;li&gt;Compliance: Minimal dependencies make it compliance-friendly.&lt;/li&gt;
&lt;li&gt;Compatibility Note: Alpine uses musl libc instead of glibc, potentially leading to compatibility issues with glibc-dependent applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When using dotnet publish to containerize an application with Alpine, you can specify the container family as alpine to use this lightweight base image:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dotnet publish -t:PublishContainer -p:ContainerFamily&lt;span class="o"&gt;=&lt;/span&gt;alpine -p:ContainerRepository&lt;span class="o"&gt;=&lt;/span&gt;hello-containers-alpine
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MSBuild version 17.8.3+195e7f5a3 &lt;span class="k"&gt;for&lt;/span&gt; .NET
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; All projects are up-to-date &lt;span class="k"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hello-containers -&amp;gt; /workspaces/dotnet-containers/hello-containers/bin/Release/net8.0/hello-containers.dll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hello-containers -&amp;gt; /workspaces/dotnet-containers/hello-containers/bin/Release/net8.0/publish/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Building image &lt;span class="s1"&gt;&amp;#39;hello-containers-alpine&amp;#39;&lt;/span&gt; with tags &lt;span class="s1"&gt;&amp;#39;latest&amp;#39;&lt;/span&gt; on top of base image &lt;span class="s1"&gt;&amp;#39;mcr.microsoft.com/dotnet/runtime:8.0-alpine&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Pushed image &lt;span class="s1"&gt;&amp;#39;hello-containers-alpine:latest&amp;#39;&lt;/span&gt; to &lt;span class="nb"&gt;local&lt;/span&gt; registry via &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The resulting base image, mcr.microsoft.com/dotnet/runtime:8.0-alpine, is smaller compared to the standard mcr.microsoft.com/dotnet/runtime:8.0. Here&amp;rsquo;s a comparison of image sizes:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker images
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REPOSITORY TAG IMAGE ID CREATED SIZE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello-containers-alpine latest 60a63389d474 &lt;span class="m"&gt;18&lt;/span&gt; seconds ago 82.7MB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello-containers latest ae96d4d7ac36 &lt;span class="m"&gt;23&lt;/span&gt; seconds ago 193MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This demonstrates that Alpine can significantly reduce image size, enhancing the efficiency of .NET containerized applications.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Ubuntu Chiseled
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Ubuntu Chiseled is a lightweight, container-optimized version of the popular Ubuntu Linux distribution. Featuring ultra-small OCI images, it includes only the application and its runtime dependencies, leading to several key benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Streamlined Design: The reduced footprint of Ubuntu Chiseled images enables lightweight maintenance and efficient resource utilization.&lt;/li&gt;
&lt;li&gt;Enhanced Security: The focus on essential components minimizes security risks by reducing potential vulnerabilities.&lt;/li&gt;
&lt;li&gt;Size Reduction: Compared to traditional Ubuntu images, Chiseled images are significantly smaller, comparable in size to Alpine, and offer glibc compatibility.&lt;/li&gt;
&lt;li&gt;Fewer Components: With fewer components, there&amp;rsquo;s a reduced CVE exposure, aligning well with development and production environments.&lt;/li&gt;
&lt;li&gt;Strong Support: Developed through a collaboration between Canonical and Microsoft, these images offer robust support, particularly suitable for .NET versions 6, 7, and 8.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Ubuntu Chiseled"
 width="1854"
 height="982"
 src="/images/ubuntu-chiseled_hu_1c230cb53a585ab9.png"
 srcset="/images/ubuntu-chiseled_hu_1c230cb53a585ab9.png 800w, /images/ubuntu-chiseled_hu_38dfe67ff09df12b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/ubuntu-chiseled.png"&gt;&lt;/figure&gt;
&lt;p&gt;The following example demonstrates containerizing a .NET application using Ubuntu Chiseled, showcasing the notable reduction in image size:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dotnet publish -t:PublishContainer -p:ContainerFamily&lt;span class="o"&gt;=&lt;/span&gt;jammy-chiseled -p:ContainerRepository&lt;span class="o"&gt;=&lt;/span&gt;hello-containers-chiseled
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MSBuild version 17.8.3+195e7f5a3 &lt;span class="k"&gt;for&lt;/span&gt; .NET
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; All projects are up-to-date &lt;span class="k"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hello-containers -&amp;gt; /workspaces/dotnet-containers/hello-containers/bin/Release/net8.0/hello-containers.dll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hello-containers -&amp;gt; /workspaces/dotnet-containers/hello-containers/bin/Release/net8.0/publish/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Building image &lt;span class="s1"&gt;&amp;#39;hello-containers-chiseled&amp;#39;&lt;/span&gt; with tags &lt;span class="s1"&gt;&amp;#39;latest&amp;#39;&lt;/span&gt; on top of base image &lt;span class="s1"&gt;&amp;#39;mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled&amp;#39;&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Pushed image &lt;span class="s1"&gt;&amp;#39;hello-containers-chiseled:latest&amp;#39;&lt;/span&gt; to &lt;span class="nb"&gt;local&lt;/span&gt; registry via &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Notice how the base image was changed from &lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0&lt;/code&gt; to &lt;code&gt;mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled&lt;/code&gt;. This is a smaller base image, which results in a smaller final image. Let&amp;rsquo;s take a look at the size difference between the images.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker images
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REPOSITORY TAG IMAGE ID CREATED SIZE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello-containers-chiseled latest bfdb924079ab &lt;span class="m"&gt;6&lt;/span&gt; seconds ago 85.7MB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello-containers-alpine latest cde2172a1f17 &lt;span class="m"&gt;6&lt;/span&gt; minutes ago 82.7MB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello-containers latest 83963b52892f &lt;span class="m"&gt;16&lt;/span&gt; minutes ago 193MB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet-docker latest 2d24bdc0b15d &lt;span class="m"&gt;17&lt;/span&gt; minutes ago 217MB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The comparison illustrates how Ubuntu Chiseled effectively reduces the container image size, making it an efficient choice for cloud and containerized environments.&lt;/p&gt;

&lt;h2 class="relative group"&gt;&lt;strong&gt;Streamlining with Project File Properties&lt;/strong&gt;
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Streamlining the deployment process in .NET can be achieved by integrating publish properties directly into the project file. This approach enhances compliance and security while reducing the likelihood of errors. Consider a typical .csproj file:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net8.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.NET.Build.Containers&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;8.0.100&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To streamline, we add container-specific properties directly into the .csproj:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;&amp;lt;!-- Existing properties --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ContainerFamily&amp;gt;&lt;/span&gt;jammy-chiseled&lt;span class="nt"&gt;&amp;lt;/ContainerFamily&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ContainerRepository&amp;gt;&lt;/span&gt;hello-containers-chiseled&lt;span class="nt"&gt;&amp;lt;/ContainerRepository&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Our final csproj file looks like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Project&lt;/span&gt; &lt;span class="na"&gt;Sdk=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Exe&lt;span class="nt"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net8.0&lt;span class="nt"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span class="nt"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ContainerFamily&amp;gt;&lt;/span&gt;jammy-chiseled&lt;span class="nt"&gt;&amp;lt;/ContainerFamily&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ContainerRepository&amp;gt;&lt;/span&gt;hello-containers-chiseled&lt;span class="nt"&gt;&amp;lt;/ContainerRepository&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Microsoft.NET.Build.Containers&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;8.0.100&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;With these additions, the deployment command simplifies to:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet publish -t:PublishContainer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This method eliminates the need to specify publish properties each time, as they are already defined in the project file. It ensures a consistent and repeatable process, making deployments smoother and more efficient.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Publishing .NET console apps as container images offers a range of options, like targeting specific Linux distributions or families. Each method has unique benefits in terms of size, security, compliance, composability, compatibility, and support. This approach signifies a move towards more specialized, purpose-built container images, shaping the future of cloud applications.&lt;/p&gt;
&lt;p&gt;Thank you for joining me on this exploration of .NET and containers. Stay tuned for more insights and guides on containerizing .NET!&lt;/p&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#containers" target="_blank" rel="noreferrer"&gt;https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#containers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/docker/build-container" target="_blank" rel="noreferrer"&gt;https://learn.microsoft.com/en-us/dotnet/core/docker/build-container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/core/docker/publish-as-container" target="_blank" rel="noreferrer"&gt;https://learn.microsoft.com/en-us/dotnet/core/docker/publish-as-container&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/" target="_blank" rel="noreferrer"&gt;https://devblogs.microsoft.com/dotnet/announcing-dotnet-chiseled-containers/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://canonical.com/blog/chiselled-ubuntu-ga" target="_blank" rel="noreferrer"&gt;https://canonical.com/blog/chiselled-ubuntu-ga&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ubuntu.com/containers/chiselled/dotnet" target="_blank" rel="noreferrer"&gt;https://ubuntu.com/containers/chiselled/dotnet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Two Incredible Years at Microsoft</title><link>https://chris-ayers.com/posts/2-years/</link><pubDate>Sun, 29 Oct 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/2-years/</guid><description>&lt;!-- textlint-disable terminology --&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Microsoft"
 width="761"
 height="183"
 src="/images/logos/mslogo.png"
 srcset="/images/logos/mslogo.png 800w, /images/logos/mslogo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/mslogo.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Two Incredible Years at Microsoft: A Journey of Growth, Connection, and Remote Collaboration
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;As I sit at my desk, keyboard beneath my fingertips, I&amp;rsquo;m reminded that it&amp;rsquo;s been two years since I first embarked on my journey with Microsoft. Joining during COVID meant that I did not get the onboarding experiences of a lot of Microsoft employees, a trip and onboarding in Redmond. Today, as I write this reflection, I&amp;rsquo;m filled with a mixture of nostalgia and pride over how much I, and the team I belong to, have grown-despite the miles that separate us.&lt;/p&gt;</description><content:encoded>&lt;!-- textlint-disable terminology --&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Microsoft"
 width="761"
 height="183"
 src="/images/logos/mslogo.png"
 srcset="/images/logos/mslogo.png 800w, /images/logos/mslogo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/mslogo.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Two Incredible Years at Microsoft: A Journey of Growth, Connection, and Remote Collaboration
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;As I sit at my desk, keyboard beneath my fingertips, I&amp;rsquo;m reminded that it&amp;rsquo;s been two years since I first embarked on my journey with Microsoft. Joining during COVID meant that I did not get the onboarding experiences of a lot of Microsoft employees, a trip and onboarding in Redmond. Today, as I write this reflection, I&amp;rsquo;m filled with a mixture of nostalgia and pride over how much I, and the team I belong to, have grown-despite the miles that separate us.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Virtual Teamwork
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md float-right m-4"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Microsoft Teams"
 width="3000"
 height="2000"
 src="/images/logos/ms-teams-logo_hu_5bc41af9f72bc327.png"
 srcset="/images/logos/ms-teams-logo_hu_5bc41af9f72bc327.png 800w,/images/logos/ms-teams-logo_hu_a0e48d0dbdf47dea.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/ms-teams-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Even before my first day, I was living in Teams. I interviewed over Teams, went through NEO Training and onboarding over Teams. My day to day, my virtual office, is in Teams.&lt;/p&gt;
&lt;p&gt;While I use and prefer Microsoft Teams over other tools, we do share a classic love-hate relationship. Some days, it&amp;rsquo;s the camera playing hide-and-seek, on others, it&amp;rsquo;s the audio doing the silent treatment or the occasional unexpected crash just to keep me on my toes. I&amp;rsquo;ve learned to embrace these quirks, and they&amp;rsquo;ve become an integral part of my remote work experience. For our team, Teams is a space where ideas, laughter, and solutions flow freely. We&amp;rsquo;ve faced challenges, celebrated victories, and everything in between, all while being fully remote.&lt;/p&gt;

&lt;h2 class="relative group"&gt;My Evolving Team
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md float-right m-4"
 src="/images/azure.svg"
 alt="Azure"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;When I joined Microsoft, I was brought on board with the &lt;a href="https://azure.microsoft.com/en-us/pricing/offers/azure-fasttrack/" target="_blank" rel="noreferrer"&gt;Fast Track for Azure team&lt;/a&gt;, an integral part of Azure CXP, which is led by Billy Anders. Our team operates under the broader umbrella of C+AI, a division headed by Scott Guthrie. Being nestled within Azure engineering, our team plays a pivotal role in the larger organization that designs and nurtures Azure.&lt;/p&gt;
&lt;p&gt;Earlier this year, our team felt the ripple effects of the widespread industry layoffs. Amidst these challenges, we embarked on a transformative journey, evolving into what we internally call Fast Track Gen 3. This transition is shifting our focus to Adoptability and resiliency challenges that our customers face. Through these times, we&amp;rsquo;ve solidified our bond, becoming more cohesive and even more dedicated to offering the best for our customers. In collaboration with various leaders across Microsoft, we&amp;rsquo;ve refined our focus, aiming for an enhanced experience for our clientele. I love how we&amp;rsquo;ve incorporated the principles of DevOps, especially methodologies like feedback loops, enhancing our processes and upscaling our deliverables.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Power of Initiatives
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Throughout these years, I&amp;rsquo;ve had the privilege of contributing to several groundbreaking initiatives-all from the comfort of my home:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customers&lt;/strong&gt;: Working with customers has been a truly enriching experience. I&amp;rsquo;ve had the opportunity to collaborate with several customers, helping them navigate the Azure ecosystem. I&amp;rsquo;ve been involved in several projects, from helping a customer migrate their on-premises infrastructure to Azure to assisting another customer in their journey to the cloud. We&amp;rsquo;ve helped customers with their DevOps journey, guiding them through the intricacies of Azure DevOps and pipelines. I&amp;rsquo;ve also been involved in several customer workshops, helping them understand the nuances of Azure and its offerings. I&amp;rsquo;ve taken their feedback and submitted it to our PGs building Azure. These interactions have been a great learning experience, and I&amp;rsquo;m grateful for the chance to work with such a diverse clientele.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mentoring and Culture&lt;/strong&gt;: My journey at Microsoft has been one of continuous growth and learning. One of the greatest privileges has been the chance to mentor several newcomers. Witnessing their progress and seeing them carve out their niches has been deeply rewarding. Beyond individual mentorship, I&amp;rsquo;ve been deeply involved in numerous cultural initiatives, particularly those focused on Diversity and Inclusion. My active participation in &lt;a href="https://www.microsoft.com/en-us/diversity/inside-microsoft/default.aspx" target="_blank" rel="noreferrer"&gt;ERGs (Employee Resource Groups)&lt;/a&gt; like Blacks At Microsoft (BAM) and The Hispanic and Latinx Organization of Leaders in Action (HOLA) has been both enlightening and fulfilling. Taking what I&amp;rsquo;ve learned about allyship, I crafted a talk that I&amp;rsquo;ve been honored to present at multiple conferences. Every event and initiative reaffirms my pride in being part of a team that genuinely values diversity and inclusion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Azure Architecture Center&lt;/strong&gt;: Crafting content for the &lt;a href="https://learn.microsoft.com/en-us/azure/architecture/" target="_blank" rel="noreferrer"&gt;Azure Architecture Center&lt;/a&gt;, I delved deep into the intricacies of Azure, ensuring that our users have a clear roadmap. Each article, each tutorial felt like building bridges in the vast ecosystem of Microsoft&amp;rsquo;s offerings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;FTA Live&lt;/strong&gt;: Engaging with our customers virtually brought both challenges and exhilaration. This was a great opportunity to scale our reach and connect with a wider audience. I&amp;rsquo;m proud to have been part of this initiative, which has now become a staple of our team.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;WAF (Well Architected Framework)&lt;/strong&gt;: Being part of &lt;a href="https://learn.microsoft.com/en-us/azure/well-architected/" target="_blank" rel="noreferrer"&gt;WAF&lt;/a&gt; reinforced my belief in creating robust and efficient architectures. I contributed to a few different areas in the past 2 years, like IoT, Security, and Operational Excellence. I&amp;rsquo;m excited to see how this initiative will evolve in the future.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IoT Training Pack&lt;/strong&gt;: This was a great opportunity to collaborate with our IoT team and create a comprehensive training pack for some production scenarios. I feel it really helped plug a gap in some of our IoT offerings.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;The AI Revolution
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="OpenAI"
 width="1200"
 height="831"
 src="/images/logos/openai-logo_hu_3abce388071a0320.png"
 srcset="/images/logos/openai-logo_hu_3abce388071a0320.png 800w,/images/logos/openai-logo_hu_547458bf8e65861b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/openai-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The past year has been a whirlwind in terms of AI shaping our daily routines. OpenAI&amp;rsquo;s creations, ChatGPT and Dalle, have found their way into many of our processes, making tasks smoother.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="GitHub Copilot"
 width="1000"
 height="420"
 src="/images/github-copilot_hu_7062fc180808f74b.png"
 srcset="/images/github-copilot_hu_7062fc180808f74b.png 800w,/images/github-copilot_hu_82c378d330d222e.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/github-copilot.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;One tool that&amp;rsquo;s genuinely changed my workflow is GitHub Copilot. It&amp;rsquo;s not just another fancy tool on the list - I genuinely use Copilot every day. Its code suggestions often feel like having a co-developer beside me. Beyond just using these tools, I&amp;rsquo;ve delved deeper into the AI realm, learning about concepts like the semantic kernel, diving into prompt engineering, and even building internal AI tools and copilots. This continual learning and exploration at the forefront of technology is truly invigorating. And as Microsoft ventures further into the AI domain, it&amp;rsquo;s evident we&amp;rsquo;re all part of this exhilarating journey of technological evolution.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Future Focus: Content &amp;amp; Enablement
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Looking ahead, my trajectory is clearly marked by two guiding stars: Content and Enablement. I am driven to produce content that not only explains Azure but also empowers its users. Beyond just explaining, I aim to unravel complex topics, making them accessible and comprehensible to all. I want to showcase the application of DevOps principles, from infrastructure as code (IaC), pipelines, to automation - demystifying these critical aspects of modern cloud computing.&lt;/p&gt;
&lt;p&gt;Security, reliability, and scalability are pillars of any robust cloud service, and my goal is to enlighten users on best practices around these elements. Parallel to this, I&amp;rsquo;m passionate about championing enablement initiatives. My aspiration is to ensure that anyone interacting with Azure feels well-supported and is fully equipped to leverage its vast capabilities. Using the platform every day, I&amp;rsquo;m intimately familiar with its nuances and intricacies. Sharing this knowledge, making it accessible and actionable, is what drives me.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Speaking My Heart Out
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;One of the unexpected joys of this remote journey has been the myriad of speaking opportunities on Teams. From giving lightning talks during our weekly team sessions to presenting at FTA Live, these platforms have been avenues for both sharing and absorbing knowledge. More than just presentations, they&amp;rsquo;ve been a medium to assist coworkers, to exchange insights, and to foster a culture of continuous learning. Every virtual interaction, every blog post, and every lightning talk reinforces the fact that, thanks to technology, distances have become negligible and our world, smaller and more interconnected. The support and encouragement from my team to voice ideas and share experiences has been invaluable, proving that every member&amp;rsquo;s insights are cherished and essential.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Looking Ahead
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;As another year beckons, I&amp;rsquo;m energized by our past achievements and the endless horizon of future possibilities. Physical boundaries have been rendered irrelevant, and it&amp;rsquo;s the bond of purpose, our shared mission, that keeps us united.&lt;/p&gt;
&lt;p&gt;I would be remiss if I didn&amp;rsquo;t extend my deepest gratitude to my incredible team, knowledgeable coworkers, and supportive managers. Your guidance, camaraderie, and unwavering support have been the backbone of every success and the silver lining in every challenge.&lt;/p&gt;
&lt;p&gt;Thank you, Microsoft, for an unforgettable two years. To my team and everyone who&amp;rsquo;s been a part of this journey, thank you for making it truly special. Here&amp;rsquo;s to many more years filled with innovation, growth, and limitless connections!&lt;/p&gt;</content:encoded></item><item><title>Enhancing Your Workflow with the GitHub Actions VSCode Extension</title><link>https://chris-ayers.com/posts/github-actions-in-vscode/</link><pubDate>Tue, 29 Aug 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/github-actions-in-vscode/</guid><description>&lt;p&gt;In today&amp;rsquo;s dynamic software development landscape, every second counts. Maximizing efficiency and ensuring error-free processes are top priorities for developers. GitHub Actions has already revolutionized workflow automation, and the game has just been upped with the introduction of its new Visual Studio Code (VSCode) extension. This article will explore these innovative features, shedding light on how they can supercharge your development workflow.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="GitHub Actions VSCode Extension"
 width="288"
 height="288"
 src="/images/vscode-ghactions_hu_dc87bb20416a5947.png"
 srcset="/images/vscode-ghactions_hu_dc87bb20416a5947.png 800w,/images/vscode-ghactions_hu_12b0c543093edd6c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-ghactions.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;GitHub Actions: Automating Software Workflows Efficiently
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;GitHub Actions stands as one of the most versatile tools in the developer&amp;rsquo;s toolkit, allowing for comprehensive automation of software workflows. Its strength lies in its seamless integration with GitHub, offering flexible and potent Continuous Integration/Continuous Deployment (CI/CD) capabilities without the reliance on external platforms. This integration ensures that developers can focus on coding while GitHub Actions takes care of the build, test, and deployment processes. From facilitating automated build checks to executing comprehensive test suites and ensuring smooth deployments, GitHub Actions is the one-stop solution for developers looking to enhance their CI/CD workflows.&lt;/p&gt;</description><content:encoded>&lt;p&gt;In today&amp;rsquo;s dynamic software development landscape, every second counts. Maximizing efficiency and ensuring error-free processes are top priorities for developers. GitHub Actions has already revolutionized workflow automation, and the game has just been upped with the introduction of its new Visual Studio Code (VSCode) extension. This article will explore these innovative features, shedding light on how they can supercharge your development workflow.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="GitHub Actions VSCode Extension"
 width="288"
 height="288"
 src="/images/vscode-ghactions_hu_dc87bb20416a5947.png"
 srcset="/images/vscode-ghactions_hu_dc87bb20416a5947.png 800w,/images/vscode-ghactions_hu_12b0c543093edd6c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-ghactions.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;GitHub Actions: Automating Software Workflows Efficiently
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;GitHub Actions stands as one of the most versatile tools in the developer&amp;rsquo;s toolkit, allowing for comprehensive automation of software workflows. Its strength lies in its seamless integration with GitHub, offering flexible and potent Continuous Integration/Continuous Deployment (CI/CD) capabilities without the reliance on external platforms. This integration ensures that developers can focus on coding while GitHub Actions takes care of the build, test, and deployment processes. From facilitating automated build checks to executing comprehensive test suites and ensuring smooth deployments, GitHub Actions is the one-stop solution for developers looking to enhance their CI/CD workflows.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Uniting VSCode with GitHub: An Enhanced Development Experience
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Visual Studio Code (VSCode) is more than just a lightweight source code editor-it&amp;rsquo;s a hub that caters to developers&amp;rsquo; evolving needs. Its extensive ecosystem of extensions ensures that its capabilities extend far beyond basic editing.&lt;/p&gt;
&lt;p&gt;Merging the intuitive features of VSCode with the collaborative and version control strengths of GitHub transforms the development process. This integration allows developers to clone repositories, adjust code, stage commits, and push updates, all within a singular, cohesive environment. The outcome is a workflow designed for maximum efficiency and streamlined operations.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Introducing the New VSCode Extension for GitHub
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The new VSCode extension for GitHub takes this integration a step further, providing you with a set of tools to manage and author workflows directly within your editor. The extension enables you to keep track of your workflows, view the run history, and get instant feedback about your workflow status.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="423"
 height="877"
 src="/images/github-actions-vscode_hu_840e67c35e5fe16a.png"
 srcset="/images/github-actions-vscode_hu_840e67c35e5fe16a.png 800w,/images/github-actions-vscode_hu_924685ffc8266782.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/github-actions-vscode.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Managing Workflows and Monitoring Workflow Runs
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The new extension provides an interactive interface for managing your workflows. You can monitor the runs for workflows in your repository, cancel and re-run them, or even trigger new ones for manually triggered workflows, all within your editor.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="418"
 height="392"
 src="/images/github-actions-vscode-workflows_hu_46cc1317ea2d97b.png"
 srcset="/images/github-actions-vscode-workflows_hu_46cc1317ea2d97b.png 800w,/images/github-actions-vscode-workflows_hu_5bf114a92d77938b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/github-actions-vscode-workflows.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Imagine you have a continuous integration workflow that runs for every pushed branch. You can edit your code, push it to GitHub, and then monitor the status of your workflows directly in VSCode. You can investigate failures by drilling down from runs to jobs to steps and even view logs without having to leave your editor. This immediate feedback allows you to quickly identify and fix issues, enhancing your productivity.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Workflow Authoring
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The new VSCode extension for GitHub also significantly improves the process of authoring workflows. It provides syntax highlighting for workflows and expressions, integrated documentation for the workflow schema, expression functions, and even event payloads. This helps reduce the required context switches and speeds up workflow editing.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="717"
 height="425"
 src="/images/github-actions-vscode-editing_hu_df2269581bb370e8.png"
 srcset="/images/github-actions-vscode-editing_hu_df2269581bb370e8.png 800w,/images/github-actions-vscode-editing_hu_10345d5b93e72e40.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/github-actions-vscode-editing.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Streamlined Variable Handling
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The extension&amp;rsquo;s simplified interface focuses on easy variable interactions. Whether you&amp;rsquo;re initiating a fresh workflow or adjusting a current one, adding or editing variables is straightforward.&lt;/p&gt;
&lt;p&gt;For example, if you&amp;rsquo;re working with a deployment workflow that varies based on development stages, you can swiftly toggle between &amp;lsquo;development&amp;rsquo;, &amp;lsquo;staging&amp;rsquo;, or &amp;lsquo;production&amp;rsquo; settings using defined variables. By making these adjustments directly in VSCode, you ensure that your workflows can adapt to different conditions without diving deep into complex configurations.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="414"
 height="694"
 src="/images/github-actions-vscode-variables_hu_cc64772726dbcbcc.png"
 srcset="/images/github-actions-vscode-variables_hu_cc64772726dbcbcc.png 800w,/images/github-actions-vscode-variables_hu_781e1194607d6ea2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/github-actions-vscode-variables.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Combining GitHub Actions with VSCode simplifies a developer&amp;rsquo;s workflow, making it easier to manage and run projects. The new VSCode extension for GitHub is like adding a Swiss army knife to your toolset, allowing you to tackle various tasks in one place. This not only streamlines the coding process but also ensures that developers can easily adapt to new challenges. In essence, with these tools hand in hand, we&amp;rsquo;re better equipped for the future of software development.&lt;/p&gt;

&lt;h2 class="relative group"&gt;References &amp;amp; Further Reading
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions" target="_blank" rel="noreferrer"&gt;GitHub Actions Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=github.vscode-github-actions" target="_blank" rel="noreferrer"&gt;VSCode Extension for GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Embedding Draw.io Diagrams in VSCode</title><link>https://chris-ayers.com/posts/drawio-in-vscode/</link><pubDate>Thu, 18 May 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/drawio-in-vscode/</guid><description>&lt;p&gt;If you&amp;rsquo;re like me, you love discovering new ways to boost your productivity and workflows. One of my favorite tools is &lt;a href="https://draw.io" target="_blank" rel="noreferrer"&gt;Draw.io&lt;/a&gt;. I&amp;rsquo;ve used the desktop tool and the site, but I found a new integration that has significantly elevated my VSCode experience: the &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio" target="_blank" rel="noreferrer"&gt;Draw.io Integration extension&lt;/a&gt;.&lt;/p&gt;</description><content:encoded>&lt;p&gt;If you&amp;rsquo;re like me, you love discovering new ways to boost your productivity and workflows. One of my favorite tools is &lt;a href="https://draw.io" target="_blank" rel="noreferrer"&gt;Draw.io&lt;/a&gt;. I&amp;rsquo;ve used the desktop tool and the site, but I found a new integration that has significantly elevated my VSCode experience: the &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio" target="_blank" rel="noreferrer"&gt;Draw.io Integration extension&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="draw.io logo"
 width="256"
 height="256"
 src="/images/logos/drawio-logo_hu_58493dce8bd3c9e8.png"
 srcset="/images/logos/drawio-logo_hu_58493dce8bd3c9e8.png 800w,/images/logos/drawio-logo_hu_96fdc7985ab1c2c3.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/drawio-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Draw.io
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In the world of diagramming software, Draw.io really shines. It enables users to convert complex concepts and detailed workflows into clear, easy-to-understand diagrams. Its user-friendly interface combined with a wide-ranging set of features makes it an ideal choice for anyone needing to visually represent information.&lt;/p&gt;
&lt;p&gt;Draw.io provides a wide variety of diagramming options. Whether you&amp;rsquo;re creating a basic flowchart, process diagram, org chart, UML, ER diagram, or network diagram, Draw.io has got you covered. Its versatility is one of the many reasons why it&amp;rsquo;s a reliable tool for users with diverse needs.&lt;/p&gt;
&lt;p&gt;Draw.io is also incredibly user-friendly. Regardless of whether you&amp;rsquo;re new to diagramming or a seasoned pro, Draw.io is accessible and straightforward to navigate. It&amp;rsquo;s designed to help you quickly and accurately capture your ideas without requiring extensive training or having to navigate a steep learning curve.&lt;/p&gt;
&lt;p&gt;Another significant aspect of Draw.io is its collaborative capabilities. It&amp;rsquo;s a fantastic choice for team projects as it allows you to share your diagrams and collaborate with others, fostering a cooperative environment and ensuring everyone stays on the same page.&lt;/p&gt;
&lt;p&gt;And the cherry on top? All these fantastic features come at no cost. Draw.io is free to use, making it a universally accessible tool for individuals and teams.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Draw.io Integration Extension by Henning Dieterichs
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Draw.io&amp;rsquo;s reputation for ease of use and an extensive set of features is well-deserved. The &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio" target="_blank" rel="noreferrer"&gt;Draw.io Integration extension&lt;/a&gt; brings this powerful functionality right into your VSCode environment, allowing you to create and edit diagrams without needing to switch back and forth between your code editor and a separate diagramming tool. This integration is a productivity booster, maintaining the flow of your workflow.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1895"
 height="1093"
 src="/images/drawio-demo_hu_4c41e102eb44119d.gif"
 srcset="/images/drawio-demo_hu_4c41e102eb44119d.gif 800w,/images/drawio-demo_hu_8cf0bead375b7a44.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/drawio-demo.gif"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;A standout feature of the &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio" target="_blank" rel="noreferrer"&gt;Draw.io Integration extension&lt;/a&gt; is its unique approach to saving diagrams. You can save diagrams as drawio.png or drawio.svg files. These formats not only store the diagram as an image but also preserve the diagram data within the image metadata. This innovative feature means you can share diagrams as images viewable in any standard image viewer. If the recipients also use Draw.io or the hediet.vscode-drawio extension, they can even edit the diagrams, thanks to the original diagram data preserved in the image file.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="321"
 height="61"
 src="/images/diagrams/sample.drawio_hu_78104c733028864d.png"
 srcset="/images/diagrams/sample.drawio_hu_78104c733028864d.png 800w,/images/diagrams/sample.drawio_hu_139670bff8ab5ae.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/sample.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Tips
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;To truly maximize the use of Draw.io within VSCode, you should not only leverage the diagramming functionality but also manage and integrate your diagrams effectively into your projects. Here are a few tips and tricks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Save diagrams as &lt;code&gt;drawio.png&lt;/code&gt; or &lt;code&gt;drawio.svg&lt;/code&gt; files&lt;/strong&gt;: Regularly saving your diagrams in these formats ensures you don&amp;rsquo;t lose your work. These formats also allow you (or others) to edit the diagrams later, courtesy of the diagram data stored within the image metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="321"
 height="61"
 src="/images/diagrams/sample.drawio_hu_78104c733028864d.png"
 srcset="/images/diagrams/sample.drawio_hu_78104c733028864d.png 800w,/images/diagrams/sample.drawio_hu_139670bff8ab5ae.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/diagrams/sample.drawio.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;hr&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1895"
 height="1093"
 src="/images/drawio-png_hu_56420f44c88b11f2.gif"
 srcset="/images/drawio-png_hu_56420f44c88b11f2.gif 800w,/images/drawio-png_hu_992a7a51fbdc060b.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/drawio-png.gif"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integrate diagrams into your Git repository&lt;/strong&gt;: After saving your diagrams as &lt;code&gt;drawio.png&lt;/code&gt; or &lt;code&gt;drawio.svg&lt;/code&gt; files, you can conveniently add them to your Git repository. This integration allows you to version control your diagrams alongside your code and share them with others through the repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reference diagrams directly in your Markdown files&lt;/strong&gt;: Once your diagrams are stored in your Git repository, they can be directly referenced in your Markdown files. This includes README files, blog posts, or MARP presentations. To do this, use the standard Markdown image syntax, &lt;code&gt;![Alt text](url)&lt;/code&gt;, where &lt;code&gt;url&lt;/code&gt; is the path to your &lt;code&gt;drawio.png&lt;/code&gt; or &lt;code&gt;drawio.svg&lt;/code&gt; file in the repository. This technique enables a seamless integration of visual content into your written material.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explore Draw.io&amp;rsquo;s extensive shape and icon libraries&lt;/strong&gt;: Draw.io provides a vast library of shapes and icons that can help make your diagrams visually appealing and intuitive. Different shapes can represent various elements in your diagrams, enhancing their readability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use color and style strategically&lt;/strong&gt;: Utilizing different colors and styles can aid in distinguishing between different elements in your diagrams. Strategic use of these features can improve the clarity of your diagrams and make them easier to interpret.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By adopting these tips and tricks, you can effectively manage your diagrams and seamlessly integrate them into your development workflow. This not only enhances communication within your team but also promotes a better understanding of the concepts you&amp;rsquo;re working with.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Other Features
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Besides the features I&amp;rsquo;ve covered so far, the &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio" target="_blank" rel="noreferrer"&gt;Draw.io Integration extension&lt;/a&gt; also provides a host of other functionalities. These include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offline support: By default, the extension uses an offline version of Draw.io.&lt;/li&gt;
&lt;li&gt;Themes: Multiple Draw.io themes are available for users to choose from.&lt;/li&gt;
&lt;li&gt;Collaboration: The extension supports collaborative editing of diagrams using VS Code Liveshare. Users can edit and present diagrams remotely, with participants able to see each other&amp;rsquo;s cursors and selections.&lt;/li&gt;
&lt;li&gt;Code link feature: The extension includes a code link feature where users can enable or disable linking nodes/edges with code spans. Double-clicking on a node with a label starting with &amp;ldquo;#&amp;rdquo; performs a workspace search for a matching symbol, allowing users to jump to its source code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio" target="_blank" rel="noreferrer"&gt;Draw.io Integration extension&lt;/a&gt; supports the integration of Draw.io into VSCode, providing a powerful and efficient way to create and manipulate diagrams. This feature-rich extension enables developers and other professionals to visualize their ideas and workflows without leaving their coding environment. Furthermore, the unique &lt;code&gt;drawio.png&lt;/code&gt; and &lt;code&gt;drawio.svg&lt;/code&gt; file formats ensure that your diagrams remain visually accessible and editable, making sharing and collaboration easier. This integration is a shining example of the flexibility and extensibility of the VSCode platform, demonstrating its ability to cater to a wide range of user needs, well beyond just writing code.&lt;/p&gt;</content:encoded></item><item><title>Optimizing VSCode Startup Time with Profiles</title><link>https://chris-ayers.com/posts/profiles-in-vscode/</link><pubDate>Tue, 16 May 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/profiles-in-vscode/</guid><description>&lt;p&gt;Visual Studio Code (VS Code) is a popular code editor that offers a wide range of extensions to enhance its functionality. However, having too many extensions installed can increase the startup time of the editor, which can be very annoying. In this blog post, we&amp;rsquo;ll explore how to optimize the startup time of VS Code by using profiles, a feature introduced in VS Code earlier this year.&lt;/p&gt;</description><content:encoded>&lt;p&gt;Visual Studio Code (VS Code) is a popular code editor that offers a wide range of extensions to enhance its functionality. However, having too many extensions installed can increase the startup time of the editor, which can be very annoying. In this blog post, we&amp;rsquo;ll explore how to optimize the startup time of VS Code by using profiles, a feature introduced in VS Code earlier this year.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md mx-auto"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="vscode logo"
 width="360"
 height="360"
 src="/images/logos/vscode-logo_hu_1e78250c3d593fc6.png"
 srcset="/images/logos/vscode-logo_hu_1e78250c3d593fc6.png 800w,/images/logos/vscode-logo_hu_e439205036e94438.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/vscode-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;What are VS Code Profiles?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In Visual Studio Code (VS Code), profiles are a feature that allows users to create sets of customizations for the editor. VS Code has a vast array of settings, thousands of extensions, and numerous ways to adjust the user interface layout in order to personalize the editor according to the user&amp;rsquo;s preferences. With VS Code Profiles, users can create and save different sets of customizations, and then quickly switch between them as needed. Additionally, users can share their profiles with others, making it easier to collaborate with a consistent development environment&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why Use Profiles?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;VS Code Profiles can be used for various purposes, including the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimizing Multiple Work Scenarios&lt;/strong&gt;: Users juggling diverse projects ranging from work assignments to personal open-source contributions and side gigs can leverage VS Code Profiles. These profiles permit the creation and swapping of distinct configurations, each tailored to meet the unique requirements of different tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Facilitating Collaborative Coding&lt;/strong&gt;: Profiles can standardize the coding environment across a team, mitigating configuration discrepancies and ensuring everyone works with the same set of tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Streamlining Training and Onboarding&lt;/strong&gt;: Organizations can expedite the setup process for new hires or trainees by creating VS Code Profiles equipped with the requisite tools and configurations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Managing Visual Preferences&lt;/strong&gt;: For users fond of alternating between themes or appearances based on tasks or time, profiles can ease this transition.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tailoring Language-Specific Environments&lt;/strong&gt;: Developers dealing with multiple programming languages can create a profile for each, complete with relevant extensions, linters, and settings, promoting a more fluid workflow.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supporting Learning of New Technologies&lt;/strong&gt;: When embarking on learning a new technology or language, users can set up a separate profile equipped with all necessary tools and configurations, thereby fostering a more focused learning environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enriching Educational Experiences&lt;/strong&gt;: Profiles allow educators to craft VS Code setups, replete with specific extensions and settings suitable for particular classes such as computer science. Students can then access these custom setups, aiding in a more effective learning process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Overall, VS Code Profiles provide a convenient way to manage and switch between different sets of customizations, ensuring that users have the optimal development environment for their current task or project.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Tips for Organizing Profiles
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep a &amp;ldquo;Default&amp;rdquo; Profile:&lt;/strong&gt; You may want to have a &amp;ldquo;default&amp;rdquo; profile with commonly used extensions that you find useful across all projects. This profile can serve as your go-to for general development tasks. Mine has my common font and color settings, as well as extensions like Dev Containers, Live Share, and GitHub Copilot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Name Profiles Descriptively:&lt;/strong&gt; Choose descriptive names for your profiles to make it easier to remember what each profile is intended for. For example, &amp;ldquo;Web Dev,&amp;rdquo; &amp;ldquo;Python,&amp;rdquo; or &amp;ldquo;Markdown Editing.&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create Task-Specific Profiles:&lt;/strong&gt; Consider creating profiles for specific tasks, such as web development, Python development, or Markdown editing. Include only the extensions relevant to each task in their respective profiles.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Profile&lt;/th&gt;
 &lt;th&gt;Extensions and Settings&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;.NET Development&lt;/td&gt;
 &lt;td&gt;C# Extension, NuGet Package Manager GUI, REST Client&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Azure Development&lt;/td&gt;
 &lt;td&gt;Azure Extensions, AZ CLI, Azure Terraform, ARM Visualizer&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Python Development&lt;/td&gt;
 &lt;td&gt;Python Extension, Pylance, Jupyter, Azure Functions&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Markdown Editing&lt;/td&gt;
 &lt;td&gt;Markdown All in One, Markdown Preview Enhanced, Markdownlint&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 class="relative group"&gt;How to Create and Use Profiles
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;To create a new profile in VS Code, you can use two different methods.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Via the Settings Menu you can create, delete, export, and import profiles

 &lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="526"
 height="599"
 src="/images/vscode-profiles-manage_hu_fec963b40f7af271.png"
 srcset="/images/vscode-profiles-manage_hu_fec963b40f7af271.png 800w,/images/vscode-profiles-manage_hu_a759984db0c8690f.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-profiles-manage.png"
 /&gt;
 
 
 &lt;/figure&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Via the Command Palette, follow these steps:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Open the Command Palette (&lt;code&gt;Ctrl+Shift+P&lt;/code&gt; or &lt;code&gt;Cmd+Shift+P&lt;/code&gt; on macOS) and search for the &amp;ldquo;Profile:&amp;rdquo; command.&lt;/li&gt;
&lt;li&gt;You can create, delete, export, and more from here.

 &lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="870"
 height="571"
 src="/images/vscode-profiles-palette_hu_dfb5f07c13087b51.png"
 srcset="/images/vscode-profiles-palette_hu_dfb5f07c13087b51.png 800w,/images/vscode-profiles-palette_hu_bdd852e0d1e83cd.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-profiles-palette.png"
 /&gt;
 
 
 &lt;/figure&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To switch between profiles, use the &amp;ldquo;Profile: Switch Profile&amp;rdquo; command from the Manage menu or Command Palette and select the desired profile. This will activate the selected profile and reload the editor with the specified extensions and settings.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Suggestions
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In my experience, creating a profile from scratch tends to reset various elements such as themes, custom fonts, and other personalized settings. As a workaround, I typically maintain my Default profile with my preferred configurations. When I need a new profile, I simply clone the Default one and then tailor it to my specific needs.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;By using profiles in VS Code, you can optimize the startup time of your editor and ensure that only the necessary extensions are activated for each project or task. This not only improves performance but also helps you stay organized and focused on the task at hand. Give profiles a try, and experience a more streamlined coding environment!&lt;/p&gt;</content:encoded></item><item><title>Secure Terraform - Part 5 - Terraform State</title><link>https://chris-ayers.com/posts/secure-terraform-part5-terraform-state/</link><pubDate>Wed, 05 Apr 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/secure-terraform-part5-terraform-state/</guid><description>&lt;p&gt;This is part 5 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="terraform-logo"
 width="1348"
 height="1316"
 src="/images/logos/terraform-logo_hu_a8b4832162edf9a1.png"
 srcset="/images/logos/terraform-logo_hu_a8b4832162edf9a1.png 800w, /images/logos/terraform-logo_hu_674d9869ac9927c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/terraform-logo.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terraform is an immensely popular Infrastructure as Code (IaC) tool that allows you to manage and provision infrastructure resources using configuration files and automation tools.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is part 5 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="terraform-logo"
 width="1348"
 height="1316"
 src="/images/logos/terraform-logo_hu_a8b4832162edf9a1.png"
 srcset="/images/logos/terraform-logo_hu_a8b4832162edf9a1.png 800w, /images/logos/terraform-logo_hu_674d9869ac9927c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/terraform-logo.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terraform is an immensely popular Infrastructure as Code (IaC) tool that allows you to manage and provision infrastructure resources using configuration files and automation tools.&lt;/p&gt;
&lt;p&gt;Securing your Terraform state is crucial for maintaining the integrity and security of your infrastructure. The state file contains a wealth of sensitive information, including resource configurations, connection details, and even secret values such as API keys and passwords. In the hands of bad actors, this data could be exploited to gain unauthorized access, launch attacks, or cause irreparable damage to your infrastructure. To protect your organization from these threats, it&amp;rsquo;s essential to understand the importance of securing your Terraform state and implement best practices that safeguard your infrastructure&amp;rsquo;s state data.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why Secure Your Terraform State?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Securing your Terraform state is crucial for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prevent Unauthorized Access&lt;/strong&gt;: A secure Terraform state prevents unauthorized individuals from gaining access to sensitive information about your infrastructure, which could be used for malicious purposes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintain Infrastructure Integrity&lt;/strong&gt;: By securing your Terraform state, you can ensure that your infrastructure remains in a consistent state and prevent unauthorized changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid Infrastructure Drift&lt;/strong&gt;: Securing your Terraform state helps prevent infrastructure drift, which can lead to inconsistencies and potential security vulnerabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Safeguard Sensitive Data&lt;/strong&gt;: Your Terraform state may contain sensitive data, such as API keys, passwords, and other secrets. Securing this data is essential to protect your organization from data breaches and other security risks.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Best Practices for Securing Terraform State
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Here are some best practices to follow when securing your Terraform state:&lt;/p&gt;

&lt;h3 class="relative group"&gt;1. Use Remote State Storage
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Storing your Terraform state file remotely is a vital step in securing it. Remote state storage options, such as Azure Blob Storage, Amazon S3, or Google Cloud Storage, offer built-in encryption for data at rest and provide access control features that can help secure your Terraform state.&lt;/p&gt;

&lt;h3 class="relative group"&gt;2. Enable Encryption at Rest
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;When using remote state storage, ensure that encryption is enabled for data at rest. This will protect your Terraform state from unauthorized access and prevent sensitive data from being exposed.&lt;/p&gt;

&lt;h3 class="relative group"&gt;3. Implement Access Control
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Implement access control mechanisms, such as role-based access control (RBAC), to limit who can access your Terraform state file. This will ensure that only authorized users can view or modify your infrastructure&amp;rsquo;s state.&lt;/p&gt;

&lt;h3 class="relative group"&gt;4. Use Versioning
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Enable versioning for your remote state storage. This allows you to maintain a history of your Terraform state and makes it possible to roll back to a previous state if necessary.&lt;/p&gt;

&lt;h3 class="relative group"&gt;5. Lock Your State
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;When working with a team, it&amp;rsquo;s essential to lock your Terraform state during operations. This prevents multiple users from making changes simultaneously and causing conflicts or inconsistencies in your infrastructure&amp;rsquo;s state.&lt;/p&gt;

&lt;h3 class="relative group"&gt;6. Perform Regular Audits
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Regularly audit your Terraform state to ensure that it remains secure and up-to-date. This can help identify potential vulnerabilities and ensure that your infrastructure is compliant with your organization&amp;rsquo;s security policies.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Securing your Terraform state is a critical aspect of managing your infrastructure with Terraform. By following the best practices outlined above, you can protect your organization from unauthorized access, maintain the integrity of your infrastructure, and safeguard sensitive data. By doing so, you can ensure that your infrastructure remains secure and reliable throughout its lifecycle.&lt;/p&gt;</content:encoded></item><item><title>Unleash Your Creativity with Marp Presentation Customization</title><link>https://chris-ayers.com/posts/customizing-marp/</link><pubDate>Fri, 31 Mar 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/customizing-marp/</guid><description>&lt;p&gt;This is part 2 of the &lt;a href="https://github.com/marp-team/marp" target="_blank" rel="noreferrer"&gt;MARP&lt;/a&gt; series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/03/26/marp-create-presentations-with-markdown" &gt;Marp - Create Presentations with Markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/31/customizing-marp" &gt;Unleash Your Creativity with Marp Presentation Customization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp is a powerful Markdown presentation framework that enables you to create stunning slides effortlessly. By using simple text-based formatting, you can easily customize your presentations to suit your unique needs and style. Although Marp provides built-in themes and configurations, the true potential of this framework can be realized by customizing your presentations to suit your unique needs and style. In this blog post, we will delve into some of the key customization options available in Marp and guide you through the process of creating a truly standout presentation.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is part 2 of the &lt;a href="https://github.com/marp-team/marp" target="_blank" rel="noreferrer"&gt;MARP&lt;/a&gt; series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/03/26/marp-create-presentations-with-markdown" &gt;Marp - Create Presentations with Markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/31/customizing-marp" &gt;Unleash Your Creativity with Marp Presentation Customization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp is a powerful Markdown presentation framework that enables you to create stunning slides effortlessly. By using simple text-based formatting, you can easily customize your presentations to suit your unique needs and style. Although Marp provides built-in themes and configurations, the true potential of this framework can be realized by customizing your presentations to suit your unique needs and style. In this blog post, we will delve into some of the key customization options available in Marp and guide you through the process of creating a truly standout presentation.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Built-in Themes
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp comes with &lt;a href="https://github.com/marp-team/marp-core/tree/main/themes" target="_blank" rel="noreferrer"&gt;three built-in themes&lt;/a&gt; that you can use to get started. The three themes, Default, Gaia, and Uncover, are shown below along with the frontmatter to use them and a few inverted examples.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Default Theme
 
 
 
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Default - Normal
 
 
 
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="948"
 height="534"
 src="/images/marp-default_hu_d8eec58685de122b.png"
 srcset="/images/marp-default_hu_d8eec58685de122b.png 800w,/images/marp-default_hu_f265962980d7d72b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-default.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h4 class="relative group"&gt;Default - Inverted
 
 
 
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;invert&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="944"
 height="530"
 src="/images/marp-default-invert_hu_e350362bd28ee859.png"
 srcset="/images/marp-default-invert_hu_e350362bd28ee859.png 800w,/images/marp-default-invert_hu_7879cf8fea2af705.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-default-invert.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Gaia Theme
 
 
 
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Gaia Normal
 
 
 
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gaia&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="948"
 height="531"
 src="/images/marp-gaia_hu_50d639be12ce6cdb.png"
 srcset="/images/marp-gaia_hu_50d639be12ce6cdb.png 800w,/images/marp-gaia_hu_8ea9e12c589711b3.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-gaia.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h4 class="relative group"&gt;Gaia - Lead / Invert
 
 
 
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gaia&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;invert&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;lead&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="953"
 height="531"
 src="/images/marp-gaia-lead-invert_hu_a979fc311387ebd5.png"
 srcset="/images/marp-gaia-lead-invert_hu_a979fc311387ebd5.png 800w,/images/marp-gaia-lead-invert_hu_59b269e275299805.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-gaia-lead-invert.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Uncover
 
 
 
 
&lt;/h3&gt;

&lt;h4 class="relative group"&gt;Uncover Normal
 
 
 
 
&lt;/h4&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;uncover&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="946"
 height="535"
 src="/images/marp-uncover_hu_a4ba9d250ddd329a.png"
 srcset="/images/marp-uncover_hu_a4ba9d250ddd329a.png 800w,/images/marp-uncover_hu_4fddbdcc07cdbe54.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-uncover.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Directives
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Directives are commands that you can use to customize your slides. They allow you to control the appearance, layout, and behavior of your presentation. Many of the directives apply globally to the entire presentation, but some are local and only apply to the current slide. A few of the directives include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fit&lt;/li&gt;
&lt;li&gt;backgroundColor&lt;/li&gt;
&lt;li&gt;color&lt;/li&gt;
&lt;li&gt;header&lt;/li&gt;
&lt;li&gt;footer&lt;/li&gt;
&lt;li&gt;class&lt;/li&gt;
&lt;li&gt;paginate&lt;/li&gt;
&lt;li&gt;many more&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To learn more about the directives and see more examples, check out the &lt;a href="https://marpit.marp.app/directives" target="_blank" rel="noreferrer"&gt;Marp documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Custom Themes
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp&amp;rsquo;s built-in themes serve as an excellent starting point. In the marp-slides-template repository, we demonstrate how you can extend a theme. Let&amp;rsquo;s take a look at the CSS file, &lt;code&gt;custom-default.css&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;/* custom-default.css */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;/* @theme custom-default */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;/* Override default background */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#fff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The crucial aspect for the Marp theme engine is the theme directive: &lt;code&gt;@theme custom-default&lt;/code&gt;. This theme name is what you&amp;rsquo;ll use in your slides. To link it to your Markdown file, apply the theme option in the front matter:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;marp: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;theme: custom-default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In your CSS file, override or extend Marp&amp;rsquo;s default styles to craft your unique appearance. For instance, you can modify the background color, font, or slide transitions. For a comprehensive list of available style properties, refer to the Marp documentation: &lt;a href="https://marpit.marp.app/theme-css" target="_blank" rel="noreferrer"&gt;https://marpit.marp.app/theme-css&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Backgrounds and Images
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Backgrounds
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Adding custom backgrounds and images can make your presentation more engaging and visually appealing. To add a background image to a specific slide, use the following syntax:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;bg&lt;/span&gt;](&lt;span class="na"&gt;path/to/your/background-image.jpg&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1789"
 height="1003"
 src="/images/marp-bg-full_hu_d4731993baa8f28.png"
 srcset="/images/marp-bg-full_hu_d4731993baa8f28.png 800w,/images/marp-bg-full_hu_2857cec7fc73d238.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-full.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;You can also use keywords to control how the background shows up. For instance, you can you left or right:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;bg right&lt;/span&gt;](&lt;span class="na"&gt;path/to/your/background-image.jpg&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="2277"
 height="1276"
 src="/images/marp-bg-right_hu_d68ff6b47d3db80.png"
 srcset="/images/marp-bg-right_hu_d68ff6b47d3db80.png 800w,/images/marp-bg-right_hu_b70f87f6d15783b7.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-right.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;bg left&lt;/span&gt;](&lt;span class="na"&gt;path/to/your/background-image.jpg&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="2275"
 height="1279"
 src="/images/marp-bg-left_hu_7942a84f0f54ec30.png"
 srcset="/images/marp-bg-left_hu_7942a84f0f54ec30.png 800w,/images/marp-bg-left_hu_c34c7611cb555d40.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-left.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Another option is to specify a percentage, like if you wanted the image to only take up a smaller percentage.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;bg left:35%&lt;/span&gt;](&lt;span class="na"&gt;path/to/your/background-image.jpg&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="Marp left background - 35%"
 width="2277"
 height="1279"
 src="/images/marp-bg-left-35_hu_95fb50701ceb37cc.png"
 srcset="/images/marp-bg-left-35_hu_95fb50701ceb37cc.png 800w,/images/marp-bg-left-35_hu_61ef7cfed249da9e.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-left-35.png"
 /&gt;
 
 &lt;figcaption&gt;Marp left background - 35%&lt;/figcaption&gt;
 &lt;/figure&gt;
&lt;p&gt;There are also a bunch of filters that you can apply to your images to get various styles, like grayscale, sepia, blur, and opacity.&lt;/p&gt;
&lt;p&gt;
&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1558"
 height="891"
 src="/images/marp-bg-right-grayscale_hu_7f771ca76cb2602b.png"
 srcset="/images/marp-bg-right-grayscale_hu_7f771ca76cb2602b.png 800w,/images/marp-bg-right-grayscale_hu_ef679d0a6e7b060f.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-right-grayscale.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1560"
 height="882"
 src="/images/marp-bg-right-sepia_hu_c2420576ca0d9cae.png"
 srcset="/images/marp-bg-right-sepia_hu_c2420576ca0d9cae.png 800w,/images/marp-bg-right-sepia_hu_ce47183f428b7c1d.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-right-sepia.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1549"
 height="870"
 src="/images/marp-bg-right-blur_hu_b15c482929fc6ea8.png"
 srcset="/images/marp-bg-right-blur_hu_b15c482929fc6ea8.png 800w,/images/marp-bg-right-blur_hu_9fceb5a7100a2d.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-right-blur.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1555"
 height="876"
 src="/images/marp-bg-right-opacity_hu_4284e9beb85e3dd8.png"
 srcset="/images/marp-bg-right-opacity_hu_4284e9beb85e3dd8.png 800w,/images/marp-bg-right-opacity_hu_4d0c782f7f1422d3.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-bg-right-opacity.png"
 /&gt;
 
 
 &lt;/figure&gt;&lt;/p&gt;
&lt;p&gt;These filters apply to not only backgrounds, but regular images. You can also combine them.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1545"
 height="864"
 src="/images/marp-image-filters_hu_8c1b222d14cd5f44.png"
 srcset="/images/marp-image-filters_hu_8c1b222d14cd5f44.png 800w,/images/marp-image-filters_hu_f1eb33da924c008f.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-image-filters.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;You can also add background colors and gradients using CSS.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Inline CSS
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Another thing I&amp;rsquo;ve done is add inline CSS to my slides. I&amp;rsquo;ve also enabled HTML tags in my slides, so I can use divs and classes besides the standard Markdown syntax. To do this, you can use the following syntax in your frontmatter.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;columns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;grid-template-columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;minmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can then use these classes in our slides and mix and match Markdown and HTML if needed.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;bg opacity&lt;/span&gt;](&lt;span class="na"&gt;https://picsum.photos/800/600?image=53&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Slide 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;div class=&amp;#34;columns&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Left
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Right
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this example, I&amp;rsquo;m adding a grid layout to my slides so I have have split columns. You can add any CSS you want here.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="947"
 height="525"
 src="/images/marp-html-css_hu_b694043a1d9b6d3a.png"
 srcset="/images/marp-html-css_hu_b694043a1d9b6d3a.png 800w,/images/marp-html-css_hu_cb2122f39a797f4f.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-html-css.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Font Awesome
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://fontawesome.com/" target="_blank" rel="noreferrer"&gt;Font Awesome&lt;/a&gt; is a great resource for designers, developers, and content creators who want to add visual elements to their projects without having to create them from scratch. Font Awesome provides a library of over &lt;a href="https://fontawesome.com/search?o=r&amp;amp;m=free" target="_blank" rel="noreferrer"&gt;2,000 free icons&lt;/a&gt; that you can use to enhance your presentations.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1417"
 height="796"
 src="/images/marp-fa-icons_hu_e978547e2387a2e5.png"
 srcset="/images/marp-fa-icons_hu_e978547e2387a2e5.png 800w,/images/marp-fa-icons_hu_123c9e0c979a493.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-fa-icons.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;To do this, you can use the following syntax in your frontmatter to import and style icons.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;marp&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;fa-twitter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;aqua&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;fa-mastodon&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;purple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;fa-linkedin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;fa-window-maximize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;skyblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And then in your slides, you can use the following syntax to add icons.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Slide 6
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;i class=&amp;#34;fa-brands fa-twitter&amp;#34;&amp;gt;&amp;lt;/i&amp;gt; Twitter:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;i class=&amp;#34;fa-brands fa-mastodon&amp;#34;&amp;gt;&amp;lt;/i&amp;gt; Mastodon:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;i class=&amp;#34;fa-brands fa-linkedin&amp;#34;&amp;gt;&amp;lt;/i&amp;gt; LinkedIn:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;i class=&amp;#34;fa fa-window-maximize&amp;#34;&amp;gt;&amp;lt;/i&amp;gt; Blog:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;i class=&amp;#34;fa-brands fa-github&amp;#34;&amp;gt;&amp;lt;/i&amp;gt; GitHub:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Mermaid Support
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp also supports &lt;a href="https://mermaid-js.github.io/mermaid/#/" target="_blank" rel="noreferrer"&gt;Mermaid&lt;/a&gt;, a JavaScript library for generating diagrams and flowcharts from text. Mermaid provides an easy way to create visual representations of complex ideas and concepts, and it integrates seamlessly with Markdown syntax.&lt;/p&gt;
&lt;p&gt;To use Mermaid diagrams in your Marp presentation, you need to add the following script to your Markdown file:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Add this anywhere in your Markdown file --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;script type=&amp;#34;module&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; import mermaid from &amp;#39;https://cdn.jsdelivr.net/npm/mermaid&lt;span class="ni"&gt;@10/dist/mermaid&lt;/span&gt;.esm.min.mjs&amp;#39;;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mermaid.initialize({ startOnLoad: true });
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can then create Mermaid diagrams using the div element with the mermaid class. For example:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# Mermaid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;div class=&amp;#34;mermaid&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;graph TD;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A--&amp;gt;B;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A--&amp;gt;C;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; B--&amp;gt;D;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; C--&amp;gt;D;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="943"
 height="529"
 src="/images/marp-mermaid_hu_5cb3dc1d7af2b813.png"
 srcset="/images/marp-mermaid_hu_5cb3dc1d7af2b813.png 800w,/images/marp-mermaid_hu_bc1409141baa29e4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-mermaid.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Marp will automatically convert this code into a diagram, which you can then style using CSS. By default, Mermaid diagrams are quite small, but you can adjust their size using CSS. This CSS code will make your Mermaid diagrams larger and easier to read. You can add this code to the frontmatter section of your Markdown file.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="o"&gt;^=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mermaid-&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And we get the following:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="949"
 height="528"
 src="/images/marp-mermaid-styled_hu_c00d9fb244492181.png"
 srcset="/images/marp-mermaid-styled_hu_c00d9fb244492181.png 800w,/images/marp-mermaid-styled_hu_535c5a634b025e54.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-mermaid-styled.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Note that out of the box, VS Code doesn&amp;rsquo;t support Mermaid diagrams with a preview, but you can add this feature using the &lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid" target="_blank" rel="noreferrer"&gt;Markdown Preview Mermaid Support&lt;/a&gt; extension. This extension adds support for Mermaid diagrams in the preview pane of VS Code.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Speaker Mode
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp comes with a built-in speaker mode that allows presenters to view their notes and upcoming slides while giving a presentation. To enable speaker mode:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Export your presentation to HTML (you can also use my template and leverage GitHub Pages).&lt;/li&gt;
&lt;li&gt;Open your Marp presentation file in your browser.&lt;/li&gt;
&lt;li&gt;Press &amp;ldquo;P&amp;rdquo; on your keyboard or click on the icon for speaker mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="316"
 height="87"
 src="/images/marp-speaker_hu_7f2b4cb22925fbd8.png"
 srcset="/images/marp-speaker_hu_7f2b4cb22925fbd8.png 800w,/images/marp-speaker_hu_1d6d92e0ea9f9e8c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-speaker.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;A new window will open, displaying the current slide, next slide, and speaker notes.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1544"
 height="879"
 src="/images/marp-speaker-mode_hu_f2d769e4a424c9ad.png"
 srcset="/images/marp-speaker-mode_hu_f2d769e4a424c9ad.png 800w,/images/marp-speaker-mode_hu_2a36963c9f4de8f4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/marp-speaker-mode.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Adding Speaker Notes
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;To add speaker notes to your Marp slides, use the following syntax:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!-- Your speaker notes go here. --&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!--
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Or Your speaker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notes go here.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will create a hidden area for your speaker notes, which will be visible only in speaker mode. You can also export them to PowerPoints and PDF.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In conclusion, Marp is a powerful and flexible presentation tool that can help you create stunning slides with ease. Unlike traditional presentation tools like PowerPoint, Marp uses Markdown, a lightweight markup language that allows you to focus on your content without getting bogged down in formatting details. With Marp, you can easily customize your presentations with custom themes, images, CSS, and other features, making your slides truly stand out. Additionally, Marp is free and open-source, so you don&amp;rsquo;t have to worry about licensing fees or vendor lock-in. Overall, Marp is a fantastic alternative to traditional presentation tools, and we encourage you to give it a try!&lt;/p&gt;
&lt;p&gt;Get started today by using the &lt;a href="https://github.com/codebytes/marp-slides-template" target="_blank" rel="noreferrer"&gt;Marp Slides Template&lt;/a&gt; to create your own Marp presentation!&lt;/p&gt;

&lt;h2 class="relative group"&gt;References
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/codebytes/marp-slides-template" target="_blank" rel="noreferrer"&gt;Marp Slides Template&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marp.app/" target="_blank" rel="noreferrer"&gt;Marp Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marpit.marp.app/" target="_blank" rel="noreferrer"&gt;Marp Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fontawesome.com/" target="_blank" rel="noreferrer"&gt;Font Awesome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mermaid-js.github.io/mermaid/#/" target="_blank" rel="noreferrer"&gt;Mermaid Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid" target="_blank" rel="noreferrer"&gt;Markdown Preview Mermaid Support Extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Marp - Create Presentations with Markdown</title><link>https://chris-ayers.com/posts/marp-create-presentations-with-markdown/</link><pubDate>Sun, 26 Mar 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/marp-create-presentations-with-markdown/</guid><description>&lt;p&gt;This is part 1 of the &lt;a href="https://github.com/marp-team/marp" target="_blank" rel="noreferrer"&gt;MARP&lt;/a&gt; series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/03/26/marp-create-presentations-with-markdown" &gt;Marp - Create Presentations with Markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/31/customizing-marp" &gt;Unleash Your Creativity with Marp Presentation Customization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp is a powerful and user-friendly presentation framework that simplifies the process of creating visually appealing slide decks using Markdown. In this blog post, we&amp;rsquo;ll explore what Marp is, why you might want to use it, how to get started. I&amp;rsquo;ll share my process and show you how you can automate hosting your presentations on GitHub Pages using GitHub Actions.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is part 1 of the &lt;a href="https://github.com/marp-team/marp" target="_blank" rel="noreferrer"&gt;MARP&lt;/a&gt; series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2023/03/26/marp-create-presentations-with-markdown" &gt;Marp - Create Presentations with Markdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/31/customizing-marp" &gt;Unleash Your Creativity with Marp Presentation Customization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp is a powerful and user-friendly presentation framework that simplifies the process of creating visually appealing slide decks using Markdown. In this blog post, we&amp;rsquo;ll explore what Marp is, why you might want to use it, how to get started. I&amp;rsquo;ll share my process and show you how you can automate hosting your presentations on GitHub Pages using GitHub Actions.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What is Marp?
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1280"
 height="640"
 src="/images/logos/marp-logo_hu_ddf7e3510a49e53.png"
 srcset="/images/logos/marp-logo_hu_ddf7e3510a49e53.png 800w,/images/logos/marp-logo_hu_da2d010dcb127cc6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/marp-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Marp is an open-source presentation framework that allows you to create beautiful, customizable slide decks using the simplicity and flexibility of Markdown. By harnessing the power of Markdown, Marp enables you to focus on your content and message without getting bogged down in complex formatting and design choices. Marp has CLI and VS Code extensions, and it supports exporting presentations to various formats, including HTML, PDF, and PowerPoint.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Why Use Marp?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;There are several reasons why you might want to consider using Marp for your presentations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplicity:&lt;/strong&gt; Marp allows you to write your presentations in plain text using the intuitive Markdown syntax, which is easy to learn and use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on content:&lt;/strong&gt; With Marp, you can concentrate on your message and content without worrying about complex formatting and design choices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customizable:&lt;/strong&gt; Marp offers a range of customization options, allowing you to create presentations that align with your personal style and preferences.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration with Visual Studio Code:&lt;/strong&gt; Marp seamlessly integrates with the popular Visual Studio Code editor, providing real-time previews and a smooth workflow for creating and editing presentations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Export options:&lt;/strong&gt; Marp supports exporting presentations to various formats, including HTML, PDF, and PowerPoint.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1416"
 height="166"
 src="/images/vscode-marp-export_hu_c6dc7e8e020301d4.png"
 srcset="/images/vscode-marp-export_hu_c6dc7e8e020301d4.png 800w,/images/vscode-marp-export_hu_b8dd0b2af53649b2.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-marp-export.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Working with Marp
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;For me, the ideal way to interact and work with Marp is through the &lt;a href="https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode" target="_blank" rel="noreferrer"&gt;Marp for VS Code extension&lt;/a&gt;. This extension provides a seamless workflow for creating and editing presentations in Visual Studio Code. It also offers real-time previews, allowing you to see your presentation as you write it.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1928"
 height="1028"
 src="/images/vscode-editing-marp_hu_c3017f67891463aa.png"
 srcset="/images/vscode-editing-marp_hu_c3017f67891463aa.png 800w,/images/vscode-editing-marp_hu_e3b9ad5b28bd7589.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-editing-marp.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;To get started, install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode" target="_blank" rel="noreferrer"&gt;Marp for VS Code extension&lt;/a&gt; and open a new Markdown file. You can then start writing your presentation. A sample presentation is shown below:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;marp: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;# My Presentation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Slide 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Item 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Item 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Item 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Slide 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;![&lt;span class="nt"&gt;Image&lt;/span&gt;](&lt;span class="na"&gt;https://picsum.photos/800/600&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Slide 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;&amp;gt; &lt;/span&gt;&lt;span class="ge"&gt;This is a quote.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Slide 4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Column 1 | Column 2 |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| -------- | -------- |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Item 1 | Item 2 |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Item 3 | Item 4 |&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Once you&amp;rsquo;ve added the content to VSCode with Marp, your presentation will look like this:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1930"
 height="1029"
 src="/images/vscode-marp-sample_hu_62efb04abc97e9bd.png"
 srcset="/images/vscode-marp-sample_hu_62efb04abc97e9bd.png 800w,/images/vscode-marp-sample_hu_801ce7944436e1d4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/vscode-marp-sample.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Official Themes and Resources
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp comes with a few &lt;a href="https://github.com/marp-team/marp-core/tree/main/themes" target="_blank" rel="noreferrer"&gt;built-in themes&lt;/a&gt; that you can use as a starting point for your custom themes or as inspiration for your own designs. You can also refer to the Marpit documentation for more information on styling Marp presentations.
There is good documentation on &lt;a href="https://marpit.marp.app/image-syntax" target="_blank" rel="noreferrer"&gt;image sizing and positioning&lt;/a&gt; on the Marp site.&lt;/p&gt;

&lt;h2 class="relative group"&gt;GitHub Pages and Marp
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I have created a GitHub repository for each of my talks. For each talk, I have a &lt;code&gt;slides&lt;/code&gt; folder that contains the Markdown files for the presentation. All images are stored in &lt;code&gt;slides/img&lt;/code&gt;. I use GitHub Pages to host the HTML files, which allows me to share the presentation with others. I use a GitHub Actions workflow that automatically builds and publishes the presentation to GitHub Pages whenever I push changes to the slides folder. This workflow is shown below:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy marp site to Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Runs on pushes targeting the default branch&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;main&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Allows you to run this workflow manually from the Actions tab&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pages&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Build job&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Copy images&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mkdir build &amp;amp;&amp;amp; cp -R slides/img build/img&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Marp Build (README)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker://marpteam/marp-cli:v1.7.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;slides/Slides.md -o build/index.html --html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MARP_USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root:root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/configure-pages@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Upload artifact&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/upload-pages-artifact@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Upload entire repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;build&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy to GitHub Pages&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/deploy-pages@v1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Marp Template
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve created a &lt;a href="https://github.com/codebytes/marp-slides-template" target="_blank" rel="noreferrer"&gt;marp-slides-template&lt;/a&gt;, which provides a minimal template to create a Marp site that can be built and published on GitHub Pages. This template comes with a GitHub Pages / Actions workflow, allowing you to easily build and publish your Marp presentation on GitHub Pages. With this template, you can quickly create and customize your presentation, preview it in Visual Studio Code using the Marp extension, and then share it with the world by publishing it to GitHub Pages. You can &lt;a href="https://github.com/codebytes/marp-slides-template/generate" target="_blank" rel="noreferrer"&gt;use the template&lt;/a&gt; by clicking on the link or visiting the repository and clicking on the &amp;ldquo;Use this template&amp;rdquo; button.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Marp is a powerful, flexible, and user-friendly presentation framework that simplifies the process of creating visually appealing slide decks. By harnessing the simplicity of Markdown and offering a range of customization options, Marp enables you to focus on your content and message without getting bogged down in complex formatting and design choices. With its seamless integration into Visual Studio Code and various export options, Marp is an excellent choice for anyone looking to streamline their presentation creation process. Check out the official documentation and repositories to get started on creating your next presentation with Marp today.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Resources
 
 
 
 
&lt;/h2&gt;

&lt;h3 class="relative group"&gt;Some of my Marp Presentations
 
 
 
 
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chris-ayers.com/feature-flags/" target="_blank" rel="noreferrer"&gt;Feature Flags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chris-ayers.com/github-actions-demos/" target="_blank" rel="noreferrer"&gt;GitHub Actions Demos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chris-ayers.com/secure-terraform-on-azure/" target="_blank" rel="noreferrer"&gt;Secure Terraform on Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chris-ayers.com/build-with-bicep/" target="_blank" rel="noreferrer"&gt;Build with Bicep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chris-ayers.com/dotnet-configuration-in-depth/" target="_blank" rel="noreferrer"&gt;Dotnet Configuration in Depth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://chris-ayers.com/dev-containers/" target="_blank" rel="noreferrer"&gt;Dev Containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;Official Repos and Docs
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;For more information on Marp and to dive deeper into its features and capabilities, check out the following resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;CommonMark Markdown syntax: &lt;a href="https://commonmark.org/help/" target="_blank" rel="noreferrer"&gt;https://commonmark.org/help/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Marp Official Repository: &lt;a href="https://github.com/marp-team/marp" target="_blank" rel="noreferrer"&gt;https://github.com/marp-team/marp&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Marp Official Documentation: &lt;a href="https://marpit.marp.app/markdown" target="_blank" rel="noreferrer"&gt;https://marpit.marp.app/markdown&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Marp for VS Code Documentation: &lt;a href="https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode" target="_blank" rel="noreferrer"&gt;https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Secure Terraform - Part 4 - checkov</title><link>https://chris-ayers.com/posts/secure-terraform-part4-checkov/</link><pubDate>Fri, 24 Mar 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/secure-terraform-part4-checkov/</guid><description>&lt;p&gt;This is part 4 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In this fourth installment of our Secure Terraform series, we&amp;rsquo;ll discuss Checkov, a powerful open-source static code analysis tool supported by Bridgecrew. Checkov supports Terraform, Kubernetes, Dockerfiles, AWS CloudFormation, and other Infrastructure as Code (IaC) frameworks. It focuses on security best practices, policy compliance, and industry standards. You can find Checkov&amp;rsquo;s documentation and samples at &lt;a href="https://www.checkov.io/" target="_blank" rel="noreferrer"&gt;https://www.checkov.io/&lt;/a&gt;.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is part 4 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In this fourth installment of our Secure Terraform series, we&amp;rsquo;ll discuss Checkov, a powerful open-source static code analysis tool supported by Bridgecrew. Checkov supports Terraform, Kubernetes, Dockerfiles, AWS CloudFormation, and other Infrastructure as Code (IaC) frameworks. It focuses on security best practices, policy compliance, and industry standards. You can find Checkov&amp;rsquo;s documentation and samples at &lt;a href="https://www.checkov.io/" target="_blank" rel="noreferrer"&gt;https://www.checkov.io/&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="checkov-logo"
 width="2120"
 height="1060"
 src="/images/logos/checkov-logo_hu_fbda4e49fc23a80e.png"
 srcset="/images/logos/checkov-logo_hu_fbda4e49fc23a80e.png 800w, /images/logos/checkov-logo_hu_88f70f7081523e61.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/checkov-logo.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Checkov Installation and CLI
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Checkov can be easily installed using pip, a package installer for Python:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pip install checkov&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Alternatively, you can install Checkov using Docker:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker pull bridgecrew/checkov&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once installed, Checkov provides a command-line interface (CLI) to scan your IaC files. To scan your Terraform code, navigate to your project&amp;rsquo;s root directory and run the following command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;checkov -d .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Checkov will then analyze your Terraform files, checking for security and compliance issues. It will output the results, including the test status (PASSED or FAILED), file paths, resource types, and relevant information about the issue.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1151"
 height="855"
 src="/images/checkov_hu_24809b167f7382d3.png"
 srcset="/images/checkov_hu_24809b167f7382d3.png 800w,/images/checkov_hu_f830ff367b1edd17.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/checkov.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;You can customize the output format using the &amp;ndash;output flag. Checkov supports various output formats, including JSON, JUnit XML, and SARIF:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;checkov -d . --output json&lt;/code&gt;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Checkov extensions for VS Code
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Checkov provides extensions for VS Code and other IDEs, allowing you to scan your Terraform code directly from your code editor. This is a convenient way to scan your code as you write it, ensuring that you don&amp;rsquo;t introduce any security or compliance issues.&lt;/p&gt;
&lt;p&gt;To install the Checkov extension for VS Code, search for &amp;ldquo;Checkov&amp;rdquo; in the Extensions tab or install it from the &lt;a href="https://marketplace.visualstudio.com/items?itemName=Bridgecrew.checkov" target="_blank" rel="noreferrer"&gt;marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1006"
 height="48"
 src="/images/checkov-vscode_hu_cd1c012198b7a412.png"
 srcset="/images/checkov-vscode_hu_cd1c012198b7a412.png 800w,/images/checkov-vscode_hu_7841940db223e086.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/checkov-vscode.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;You can scan your code and view the results in the Checkov Output tab. You can also view the results in the Problems tab, which provides a convenient way to navigate to the specific line of code that caused the issue.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1643"
 height="1279"
 src="/images/checkov-vscode-output_hu_f687849b99c9a564.png"
 srcset="/images/checkov-vscode-output_hu_f687849b99c9a564.png 800w,/images/checkov-vscode-output_hu_5e40f2847c061ec8.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/checkov-vscode-output.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;And if you hold your cursor over the issue, you can see the violations and links to the related checks.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1513"
 height="477"
 src="/images/checkov-vscode-problems_hu_dfba1c1dbdfccf3b.png"
 srcset="/images/checkov-vscode-problems_hu_dfba1c1dbdfccf3b.png 800w,/images/checkov-vscode-problems_hu_7f2daae4a44f8c49.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/checkov-vscode-problems.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;If you click on the check, you&amp;rsquo;ll get a pop-up and follow the link to the Checkov documentation.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="496"
 height="146"
 src="/images/checkov-vscode-check_hu_c934fff31fd9b02a.png"
 srcset="/images/checkov-vscode-check_hu_c934fff31fd9b02a.png 800w,/images/checkov-vscode-check_hu_4c372b52145b3aef.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/checkov-vscode-check.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1704"
 height="1311"
 src="/images/checkov-vscode-check-doc_hu_416a1fcb4a0c8a79.png"
 srcset="/images/checkov-vscode-check-doc_hu_416a1fcb4a0c8a79.png 800w,/images/checkov-vscode-check-doc_hu_33b1cddd04db1b36.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/checkov-vscode-check-doc.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This is an amazing developer experience, allowing you to scan your code as you write it and fix any issues immediately.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Checkov in CI/CD Pipelines
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Integrating Checkov into your CI/CD pipeline helps ensure that your infrastructure is secure and compliant with every code change. Here&amp;rsquo;s an example of how to integrate Checkov into a GitHub Actions pipeline:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkov&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python 3.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test with Checkov&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;checkov&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bridgecrewio/checkov-action@master&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;example/examplea&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;terraform&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This example workflow triggers on push and pull_request events for the main branch. It checks out the repository, sets up Python, installs Checkov, and runs a scan on the specified Terraform files.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Custom Policies and Suppressions
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Checkov supports custom policies written in Python or Yaml. You can create a custom policy by following these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new directory for your custom policies, e.g., custom_policies.&lt;/li&gt;
&lt;li&gt;Inside the custom_policies directory, create a new Python or Yaml file for your custom rule, e.g., my_custom_rule.py or my_custom_rule.yaml.&lt;/li&gt;
&lt;li&gt;Write your custom rule using Python or the Yaml language. Refer to the Checkov documentation for guidance on writing custom policies.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In addition to custom policies, Checkov allows you to suppress specific checks or resources using inline comments in your Terraform code.&lt;/p&gt;
&lt;p&gt;This can be useful when you want to exclude certain resources from specific checks. To suppress a check, add the following comment above the resource block:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# checkov:skip=CKV_AZURE_123:This check is not applicable for this resource
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azurerm_storage_account&amp;#34; &amp;#34;example&amp;#34;&lt;/span&gt; {&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this example, the CKV_AZURE_123 check will be skipped for the azurerm_storage_account resource with a reason &amp;ldquo;This check is not applicable for this resource.&amp;rdquo;&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Checkov serves as a robust tool to bolster the security and compliance of your Terraform code. By seamlessly integrating Checkov into your development workflow, CI/CD pipeline, and leveraging it with your code editor, you can proactively tackle potential security risks and policy violations during the development stage itself.&lt;/p&gt;
&lt;p&gt;The adaptability of Checkov, with its support for custom policies and the ability to selectively suppress checks, empowers organizations to customize the tool to align with their specific security objectives.&lt;/p&gt;
&lt;p&gt;Utilizing tools like Checkov, along with others like tfsec and Terrascan, plays a pivotal role in creating and sustaining a secure and compliant infrastructure in today&amp;rsquo;s rapidly evolving landscape, where Infrastructure as Code has become a fundamental component of modern development practices.&lt;/p&gt;</content:encoded></item><item><title>Secure Terraform - Part 3 - terrascan</title><link>https://chris-ayers.com/posts/secure-terraform-part3-terrascan/</link><pubDate>Wed, 22 Mar 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/secure-terraform-part3-terrascan/</guid><description>&lt;p&gt;This is part 3 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terrascan is another great tool for terraform security from tenable.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is part 3 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terrascan is another great tool for terraform security from tenable.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="terrascan"
 width="2791"
 height="959"
 src="/images/logos/terrascan-logo_hu_17b806c8674bef27.png"
 srcset="/images/logos/terrascan-logo_hu_17b806c8674bef27.png 800w, /images/logos/terrascan-logo_hu_a08d40025e856090.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/terrascan-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;Terrascan is an open-source static code analysis tool for security compliance of your Infrastructure as Code (IaC). It has rules for various platforms, including Kubernetes, Dockerfile, AWS, Azure, GCP, and Terraform. It is developed in GoLang and has a CLI, API, and Jenkins plugin. Terrascan can be used to scan your IaC files for security vulnerabilities and policy violations. It can help you identify security issues before you deploy your code, and it can also help you enforce compliance policies.&lt;/p&gt;
&lt;p&gt;You can see a lot of relevant documentation at: &lt;a href="https://runterrascan.io/" target="_blank" rel="noreferrer"&gt;runterrascan.io&lt;/a&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Terrascan CLI
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In addition to tfsec, there is another static code analysis tool for Terraform called Terrascan. Terrascan is open source and supported by tenable.&lt;/p&gt;
&lt;p&gt;You can download the Terrascan CLI from the official GitHub repository: &lt;a href="https://github.com/accurics/terrascan/releases" target="_blank" rel="noreferrer"&gt;https://github.com/accurics/terrascan/releases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It is available for Windows, macOS, Linux, and docker.&lt;/p&gt;
&lt;p&gt;Once you have the Terrascan CLI installed, you can use it to scan your Terraform code. Terrascan comes with a number of pre-built policies that you can use. Policies define a set of rules that you want to enforce on your Terraform code. You can also create your own custom policies.&lt;/p&gt;
&lt;p&gt;To scan your Terraform code using Terrascan, navigate to the root directory of your project and run the following command:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terrascan scan&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;By default, Terrascan will scan all Terraform files in the current directory and its subdirectories. You can specify a different directory by passing the &lt;code&gt;--path&lt;/code&gt; option:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terrascan scan --path=path/to/terraform/files&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Terrascan will output a list of violations that it found in your code. Each violation will have a rule ID, a description, and a severity level. The severity level can be either HIGH, MEDIUM, or LOW. The output will also include the filename, line number, and column number where the violation was found.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1381"
 height="935"
 src="/images/terrascan_hu_25a2debc85ee60f5.png"
 srcset="/images/terrascan_hu_25a2debc85ee60f5.png 800w,/images/terrascan_hu_603bd6581249a024.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/terrascan.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;You can specify a different output format using the &lt;code&gt;--format&lt;/code&gt; option. Terrascan supports a number of output formats, including JSON, YAML, and JUnit. Here&amp;rsquo;s an example of how to output the results in JSON format:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terrascan scan --format=json &amp;gt; output.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You can also specify a policy path using the &lt;code&gt;--policy-path&lt;/code&gt; option. By default, Terrascan will use the policies that are included with the tool. However, you can also use your own custom policies by specifying a policy path:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terrascan scan --policy-path=path/to/policies&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Terrascan also supports scanning remote Terraform modules. You can use the &lt;code&gt;--remote&lt;/code&gt; option to scan a remote module:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terrascan scan --remote=github.com/tenable/terrascan&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Terrascan supports a number of other options and flags that you can use. You can view the full list of options by running &lt;code&gt;terrascan help scan&lt;/code&gt;.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Terrascan in CI/CD Pipelines
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Integrating Terrascan into your CI/CD pipeline is an essential step in ensuring the security of your infrastructure. By adding Terrascan to your pipeline, you can automatically scan your Terraform code for security vulnerabilities and policy violations every time you commit changes.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of how to integrate Terrascan into a GitHub Actions pipeline:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;terrascan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull-requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;terrascan_job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;terrascan-action&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Run Terrascan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;terrascan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tenable/terrascan-action@main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;iac_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;terraform&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#iac_version: &amp;#39;v14&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#policy_type: &amp;#39;azure&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;only_warn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#scm_token: ${{ secrets.ACCESS_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sarif_upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#non_recursive:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#iac_dir: demos&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#policy_path:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#skip_rules:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#config_path:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#find_vulnerabilities:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#webhook_url:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#webhook_token:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this example, the workflow is triggered on push and pull_request events for the main branch. The workflow checks out the repository, sets up Go, installs Terrascan, and then runs a scan on the specified Terraform files.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Custom Policies and Rules
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terrascan allows you to create custom policies and rules to enforce specific security and compliance requirements. You can write custom policies in Rego, a high-level declarative language used by the Open Policy Agent (OPA) project.&lt;/p&gt;
&lt;p&gt;To create a custom policy, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new directory for your custom policies, e.g., custom_policies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inside the custom_policies directory, create a new Rego file for your custom rule, e.g., my_custom_rule.rego.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write your custom rule using the Rego language. Refer to the OPA documentation for guidance on writing Rego policies.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rego" data-lang="rego"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;accurics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;azureKeyVaultSoftDeleteRetentionDays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resource&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="nx"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_key_vault&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&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="s2"&gt;&amp;#34;azurerm_key_vault&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;properties&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="nx"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;soft_delete_retention_days&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the Rule json metadata file&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azureKeyVaultSoftDeleteRetentionDays&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azureKeyVaultSoftDeleteRetentionDays.rego&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;policy_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azure&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;resource_type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azurerm_key_vault&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;template_args&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;severity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;MEDIUM&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Key Vault Soft Delete Retention Days should be more than 14 days&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;category&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Data Protection&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AC_AZURE_1000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you have created your custom policy, you can use the &lt;code&gt;--policy-path&lt;/code&gt; option with Terrascan to include your custom policies in the scan:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;terrascan scan -p custom_policies/ -p ~/.terrascan/pkg/policies/opa/rego&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If there are errors, you should get output like the following:&lt;/p&gt;

 &lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1118"
 height="243"
 src="/images/customer-terrascan-violation_hu_4e059963bf4d03fe.png"
 srcset="/images/customer-terrascan-violation_hu_4e059963bf4d03fe.png 800w,/images/customer-terrascan-violation_hu_1afebf14d4a7bfb1.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/customer-terrascan-violation.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terrascan is a powerful tool that can help you improve the security and compliance of your Terraform code. By integrating Terrascan into your development process, including your CI/CD pipeline and using it alongside your code editor, you can catch potential security vulnerabilities and policy violations early on.&lt;/p&gt;
&lt;p&gt;With its support for custom policies, Terrascan allows you to tailor its scanning capabilities to meet your organization&amp;rsquo;s specific requirements.&lt;/p&gt;
&lt;p&gt;In conclusion, using tools like Terrascan, alongside others like tfsec, is an essential part of maintaining a secure and compliant infrastructure in a world increasingly reliant on Infrastructure as Code.&lt;/p&gt;</content:encoded></item><item><title>Secure Terraform - Part 2 - tfsec Customization</title><link>https://chris-ayers.com/posts/secure-terraform-part2-tfsec-customization/</link><pubDate>Sun, 29 Jan 2023 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/secure-terraform-part2-tfsec-customization/</guid><description>&lt;p&gt;This is part 2 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In the previous article, we discussed tfsec, a static code analysis tool for Terraform. We also learned how to use it in VSCode and GitHub Actions to scan our Terraform code. We learned how to override the severity of rules. In this article, we will learn how to customize the rules and add our own rules.&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is part 2 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In the previous article, we discussed tfsec, a static code analysis tool for Terraform. We also learned how to use it in VSCode and GitHub Actions to scan our Terraform code. We learned how to override the severity of rules. In this article, we will learn how to customize the rules and add our own rules.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="tfsec logo"
 width="300"
 height="412"
 src="/images/logos/tfsec-logo.png"
 srcset="/images/logos/tfsec-logo.png 800w, /images/logos/tfsec-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/tfsec-logo.png"&gt;&lt;/figure&gt;

&lt;h2 class="relative group"&gt;Customizing tfsec Rules
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Tfsec allows you to customize the rules that are used to scan your Terraform code. You can do this by creating a file ending in &lt;code&gt;_tfchecks.json&lt;/code&gt; or &lt;code&gt;_tfchecks.yaml&lt;/code&gt; in the .tfsec folder in the root of project. You can also put these files in a different folder and pass the option &lt;code&gt;--custom-check-dir&lt;/code&gt; or &lt;code&gt;--custom-check-url&lt;/code&gt; to the tfsec command. This is covered in the documentation: &lt;a href="https://aquasecurity.github.io/tfsec/v1.28.1/guides/configuration/custom-checks/" target="_blank" rel="noreferrer"&gt;https://aquasecurity.github.io/tfsec/v1.28.1/guides/configuration/custom-checks/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The documentation references a tool called &lt;code&gt;tfsec-checkgen&lt;/code&gt; that you can install. This tool will validate your check file or help perform tests to ensure that it is valid for use with tfsec. I found that the tool helped me create and validate checks but not run the &lt;code&gt;test-check&lt;/code&gt; action.&lt;/p&gt;
&lt;p&gt;In this post, we will take a look at how to create a few different custom rules. The first rule we&amp;rsquo;ll work on is for a required tag for our Azure resources. There is an example of this in the tfsec documentation, but its for AWS.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CUS001&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Custom check to ensure the CostCentre tag is applied to EC2 instances&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;impact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;By not having CostCentre we can&amp;#39;t keep track of billing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Add the CostCentre tag&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;resource&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;aws_instance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ERROR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tags&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;contains&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CostCentre&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;The required CostCentre tag was missing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relatedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;http://internal.acmecorp.com/standards/aws/tagging.html&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Lets look at my Azure example and discuss the tweaks and how to use it.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tags-resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Custom check to ensure the CostCenter tag is applied to Azure Resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;impact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;By not having CostCenter we can&amp;#39;t keep track of billing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Add the CostCenter tag&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;resource&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_subscription&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_linux_web_app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_windows_web_app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_service_plan&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_app_service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;HIGH&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tags&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;contains&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CostCenter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;The required CostCenter tag was missing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relatedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-tagging&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;ve changed the name to &lt;code&gt;tags-resources&lt;/code&gt; to make it more descriptive. I&amp;rsquo;ve also changed the requiredLabels to include the resources I want to check for the tag. This rule will only trigger on the resource types listed under requiredLabels. I&amp;rsquo;ve also changed the severity to HIGH. I&amp;rsquo;ve also added a link to the Azure Best Practices for Resource Tagging.&lt;/p&gt;
&lt;p&gt;This is saved to &lt;code&gt;.tfsec/custom_tfchecks.yaml&lt;/code&gt;. The tfsec vscode extension we installed before will automatically pick up the new rule. We can see it highlighting the code with an issue and showing up in the results screen.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1720"
 height="1380"
 src="/images/custom-tfsec-tag_hu_3d3bb6c2ab0520fa.png"
 srcset="/images/custom-tfsec-tag_hu_3d3bb6c2ab0520fa.png 800w,/images/custom-tfsec-tag_hu_393c2a91d4978da5.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/custom-tfsec-tag.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Issues also show up as problems at the bottom of the screen for you to see, click on, and navigate to the right code section.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="948"
 height="466"
 src="/images/custom-tfsec-problems_hu_35d26e13f6a63dca.png"
 srcset="/images/custom-tfsec-problems_hu_35d26e13f6a63dca.png 800w,/images/custom-tfsec-problems_hu_32298845d6741451.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/custom-tfsec-problems.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;A Custom Rule for Naming
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s try something a little more complex. We can try to enforce a naming scheme for our resources. I want to enforce a naming scheme that all resource groups must follow a pattern of &lt;code&gt;rg-app-env-region&lt;/code&gt;. This will help us identify which resources belong to which applications or environments. This is a great way to enforce a naming scheme and keep things organized.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rg-naming-pattern&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Custom check to check resource group naming&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;impact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;resource groups should be named consistently&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;use the pattern rg-app-env-region&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;resource&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;HIGH&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;regexMatches&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^rg-[a-zA-Z]+-[a-zA-Z]+-[a-zA-Z]+&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;improperly named resource group&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relatedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This rule, only checks against resource groups as identified by the &lt;code&gt;requiredLabels&lt;/code&gt; property containing &lt;code&gt;azurerm_resource_group&lt;/code&gt;. The matchSpec uses the regexMatches action. I am able to provide my regular expression and the error message.
There are a bunch of provided &lt;a href="https://aquasecurity.github.io/tfsec/v1.28.1/guides/configuration/custom-checks/#check-actions" target="_blank" rel="noreferrer"&gt;check actions&lt;/a&gt; that you can use to develop your custom checks.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Custom Checks for Deprecated Resources
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Tfsec also allows you to create custom checks for deprecated resources. This is a great way to keep up with the latest changes in Terraform and Azure. I&amp;rsquo;ve created a custom check for the deprecated azurerm_app_service resource. This resource has been deprecated in favor of azurerm_linux_web_app and azurerm_windows_web_app. I&amp;rsquo;ve created a custom check to warn us when we use the deprecated resource.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;app-service-deprecated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Custom check to warn on deprecated app service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;impact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;using deprecated app service resource instead of azurerm_linux_web_app or azurerm_windows_web_app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Use azurerm_linux_web_app or azurerm_windows_web_app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;resource&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requiredLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;azurerm_app_service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WARN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;azurerm_app_service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;isPresent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Using a deprecated resource - azurerm_app_service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;relatedLinks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This time I&amp;rsquo;m using the isPresent action to check if the resource is present. I&amp;rsquo;ve also added a link to the documentation for the resource.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Rego Policies
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;What about Rego policies?&lt;/p&gt;
&lt;p&gt;We can create a folder to hold all of our rego policies. I created a file called &lt;code&gt;keyvault_softdeleteretentiondays.rego&lt;/code&gt; and added the following code.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rego" data-lang="rego"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;custom&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyvault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;softdeleteretentiondays&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;]&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kv&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="nx"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyvault&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vaults&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;softdeleteretentiondays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&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="s2"&gt;&amp;#34;Key Vault Soft Delete Retention Days is less than 14 days&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To understand the json input going into the rego policy, you can use the following command:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tfsec --print-rego-input&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will give you a ton of output that you can filter with &lt;code&gt;jq&lt;/code&gt; to find the specific resource you are looking for. For example, to find the keyvault resource, you can use the following command:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vscode ➜ /workspaces/secure-terraform-on-azure/ &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; $ tfsec --print-rego-input &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;.azure.keyvault&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;vaults&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;__defsec_metadata&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;endline&amp;#34;&lt;/span&gt;: 52,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;explicit&amp;#34;&lt;/span&gt;: false,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;filepath&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;workspaces/secure-terraform-on-azure/custom_checks_examples/keyvault/fail/main.tf&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;managed&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;resource&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;azurerm_key_vault.example&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;startline&amp;#34;&lt;/span&gt;: &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;enablepurgeprotection&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;endline&amp;#34;&lt;/span&gt;: 27,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;explicit&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;filepath&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;workspaces/secure-terraform-on-azure/custom_checks_examples/keyvault/fail/main.tf&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;managed&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;resource&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;azurerm_key_vault.example.purge_protection_enabled&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;startline&amp;#34;&lt;/span&gt;: 27,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;networkacls&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;__defsec_metadata&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;endline&amp;#34;&lt;/span&gt;: 34,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;explicit&amp;#34;&lt;/span&gt;: false,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;filepath&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;workspaces/secure-terraform-on-azure/custom_checks_examples/keyvault/fail/main.tf&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;managed&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;resource&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;network_acls&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;startline&amp;#34;&lt;/span&gt;: &lt;span class="m"&gt;31&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;defaultaction&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;endline&amp;#34;&lt;/span&gt;: 33,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;explicit&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;filepath&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;workspaces/secure-terraform-on-azure/custom_checks_examples/keyvault/fail/main.tf&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;managed&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;resource&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;network_acls.default_action&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;startline&amp;#34;&lt;/span&gt;: 33,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Deny&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;softdeleteretentiondays&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;endline&amp;#34;&lt;/span&gt;: 26,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;explicit&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;filepath&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;workspaces/secure-terraform-on-azure/custom_checks_examples/keyvault/fail/main.tf&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;managed&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;resource&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;azurerm_key_vault.example.soft_delete_retention_days&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;startline&amp;#34;&lt;/span&gt;: 26,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;: &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I used this output to develop the policy. I had a few issues with the samples from the docs, and there is an open GitHub issue. To run the rego policies with tfsec, you have to pass the &lt;code&gt;--rego-policy-dir&lt;/code&gt; command like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vscode ➜ /workspaces/secure-terraform-on-azure &lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; $ tfsec --rego-policy-dir ./tfsec_rego_policies/ ./custom_checks_examples/keyvault/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Result &lt;span class="c1"&gt;#1 Key Vault Soft Delete Retention Days is less than 14 days&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;───────────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;───────────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; fail Rego Package custom.azure.keyvault.softdeleteretentiondays
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Rego Rule deny
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;───────────────────────────────────────────────────────────────────────────────────────────────&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can see the results of the rego policy in the output.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;While rego policies support are nice, I think the yaml policies are more flexible and easier to use. Having the ability to use a URL for custom checks allows you to share your checks with others.&lt;/p&gt;
&lt;p&gt;I wanted to show how to do checks in Azure because I didn&amp;rsquo;t see a lot of examples or docs on Azure resources specifically.&lt;/p&gt;
&lt;p&gt;I hope this deeper dive into custom checks was helpful.&lt;/p&gt;</content:encoded></item><item><title>Secure Terraform - Part 1 - tfsec</title><link>https://chris-ayers.com/posts/secure-terraform-part1-tfsec/</link><pubDate>Thu, 29 Dec 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/secure-terraform-part1-tfsec/</guid><description>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://festivetechcalendar.com/" target="_blank" rel="noreferrer"&gt;Festive Tech Calendar 2022&lt;/a&gt;. I really want to thank the organizers for helping set this up!&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/gregor_suttie" target="_blank" rel="noreferrer"&gt;Gregor Suttie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/Pixel_Robots" target="_blank" rel="noreferrer"&gt;Richard Hooper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/MrKeithAtherton" target="_blank" rel="noreferrer"&gt;Keith Atherton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/smoon_lee" target="_blank" rel="noreferrer"&gt;Simon Lee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/Lizaard08" target="_blank" rel="noreferrer"&gt;Lisa Hoving&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Look for the hashtag &lt;code&gt;#FestiveTechCalendar2022&lt;/code&gt; on social media! Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;re done here&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is part 1 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;</description><content:encoded>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://festivetechcalendar.com/" target="_blank" rel="noreferrer"&gt;Festive Tech Calendar 2022&lt;/a&gt;. I really want to thank the organizers for helping set this up!&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/gregor_suttie" target="_blank" rel="noreferrer"&gt;Gregor Suttie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/Pixel_Robots" target="_blank" rel="noreferrer"&gt;Richard Hooper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/MrKeithAtherton" target="_blank" rel="noreferrer"&gt;Keith Atherton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/smoon_lee" target="_blank" rel="noreferrer"&gt;Simon Lee&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/Lizaard08" target="_blank" rel="noreferrer"&gt;Lisa Hoving&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Look for the hashtag &lt;code&gt;#FestiveTechCalendar2022&lt;/code&gt; on social media! Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;re done here&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is part 1 of the Secure Terraform series. You can read the series of articles here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/2022/12/29/secure-terraform-part1-tfsec" &gt;Secure Terraform - Part 1 - tfsec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/01/29/secure-terraform-part2-tfsec-customization" &gt;Secure Terraform - Part 2 - tfsec customization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/22/secure-terraform-part3-terrascan" &gt;Secure Terraform - Part 3 - terrascan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/03/24/secure-terraform-part4-checkov" &gt;Secure Terraform - Part 4 - checkov&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/2023/04/05/secure-terraform-part5-terraform-state" &gt;Secure Terraform - Part 5 - terraform state&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Introduction
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Securing Terraform starts before we even deploy anything. Our tooling is a great place to start! We can leverage one or more static code analyzers to look for misconfigurations, security issues, and other problems. Many of these great tools not only plug into our CI/CD pipeline, they also work within our IDEs. This allows us to catch issues while we work. We can also leverage pre-commit hooks to catch things before we even commit our code. With Cloud infrastructure, its easy to make mistakes, and these tools can help us catch them.&lt;/p&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="terraform logo"
 width="1348"
 height="1316"
 src="/images/logos/terraform-logo_hu_a8b4832162edf9a1.png"
 srcset="/images/logos/terraform-logo_hu_a8b4832162edf9a1.png 800w, /images/logos/terraform-logo_hu_674d9869ac9927c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/terraform-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;Today we are going to look at a one of these tools, tfsec, and how we can leverage it to secure our Terraform code. I&amp;rsquo;ll work through the rest of the ones in the list in follow on posts, but there are many more out there. I encourage you to check them out and see which ones work best for you.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aquasecurity/tfsec" target="_blank" rel="noreferrer"&gt;tfsec&lt;/a&gt; - &lt;a href="https://tfsec.dev/" target="_blank" rel="noreferrer"&gt;docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tenable/terrascan" target="_blank" rel="noreferrer"&gt;terrascan&lt;/a&gt; - &lt;a href="https://runterrascan.io/" target="_blank" rel="noreferrer"&gt;docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bridgecrewio/checkov" target="_blank" rel="noreferrer"&gt;checkov&lt;/a&gt; - &lt;a href="https://www.checkov.io/" target="_blank" rel="noreferrer"&gt;docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snyk.io/" target="_blank" rel="noreferrer"&gt;snyk&lt;/a&gt; - &lt;a href="https://docs.snyk.io/products/snyk-infrastructure-as-code/getting-started-snyk-iac" target="_blank" rel="noreferrer"&gt;docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;tfsec
 
 
 
 
&lt;/h2&gt;
&lt;figure&gt;&lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="tfsec logo"
 width="300"
 height="412"
 src="/images/logos/tfsec-logo.png"
 srcset="/images/logos/tfsec-logo.png 800w, /images/logos/tfsec-logo.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/tfsec-logo.png"&gt;&lt;/figure&gt;
&lt;p&gt;Let&amp;rsquo;s start with tfsec by aquasecurity. Aquasecurity supports multiple amazing tools like &lt;a href="https://trivy.dev/" target="_blank" rel="noreferrer"&gt;trivy&lt;/a&gt; and &lt;a href="https://tfsec.dev" target="_blank" rel="noreferrer"&gt;tfsec&lt;/a&gt;. Tfsec is an open-source tool available on GitHub at &lt;a href="https://github.com/aquasecurity/tfsec" target="_blank" rel="noreferrer"&gt;https://github.com/aquasecurity/tfsec&lt;/a&gt;. The docs are hosted at: &lt;a href="https://aquasecurity.github.io/tfsec/" target="_blank" rel="noreferrer"&gt;https://aquasecurity.github.io/tfsec/&lt;/a&gt;. There are some great guides for installing it and configuring it for GitHub Actions. Tfsec even has an integration with GitHub Advanced security to show the results of the scan in the security tab of the repository.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Installation
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;We can run tfsec from a docker container or we can install it locally via homebrew, chocolatey, go, or the &lt;a href="https://github.com/aquasecurity/tfsec/releases" target="_blank" rel="noreferrer"&gt;releases page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After tfsec is installed (or docker is setup), we can run it against our Terraform code. Running it with defaults is quite easy, just invoking the command &lt;code&gt;tfsec&lt;/code&gt; will run it against the current directory. If it finds files it will scan them and output the results. By default it checks all rules and outputs all severities found.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Running tfsec
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at the output of tfsec against a simple Terraform file:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azurerm_storage_account&amp;#34; &amp;#34;sa&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;samplesa${random_uuid.uuid.result}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; resource_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;location&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; account_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Standard&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; account_replication_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;LRS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Running tfsec against this file produces the following output:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ tfsec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Result &lt;span class="c1"&gt;#1 CRITICAL Storage account uses an insecure TLS version.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;─────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; insecure-terraform/main.tf:8-14
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;─────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;8&lt;/span&gt; resource &lt;span class="s2"&gt;&amp;#34;azurerm_storage_account&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sa&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;9&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;samplesa&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;random_uuid&lt;/span&gt;&lt;span class="p"&gt;.uuid.result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="nv"&gt;resource_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; azurerm_resource_group.rg.name
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;11&lt;/span&gt; &lt;span class="nv"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; azurerm_resource_group.rg.location
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="nv"&gt;account_tier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Standard&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="nv"&gt;account_replication_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;LRS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;14&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;─────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ID azure-storage-use-secure-tls-policy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Impact The TLS version being outdated and has known vulnerabilities
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Resolution Use a more recent TLS/SSL policy &lt;span class="k"&gt;for&lt;/span&gt; the load balancer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; More Information
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - https://aquasecurity.github.io/tfsec/latest/checks/azure/storage/use-secure-tls-policy/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#min_tls_version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;─────────────────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; timings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ──────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; disk i/o 28.432µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; parsing 509.357µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; adaptation 93.3µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; checks 5.59119ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; total 6.222279ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; counts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ──────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; modules downloaded &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; modules processed &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; blocks processed &lt;span class="m"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; files &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; results
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ──────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; passed &lt;span class="m"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ignored &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; critical &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; high &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; medium &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; low &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;7&lt;/span&gt; passed, &lt;span class="m"&gt;1&lt;/span&gt; potential problem&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; detected.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As you can see, tfsec found a potential problem with the TLS version being used. This could be a critical issue and should be addressed. We can see the ID of the rule, the impact, and the resolution. We can also see a link to the documentation for the rule and the link to the documentation for the resource.&lt;/p&gt;
&lt;p&gt;I can also apply a custom configuration to tfsec to fine tune how I want to scan my code. This can be either a file I pass to tfsec or a config.json or config.yml inside a .tfsec directory. We could override the minimum severity to only show critical issues, or we could ignore a specific rule.&lt;/p&gt;
&lt;p&gt;If I had misconfigured my storage account to accept traffic on http instead of HTTPS, tfsec would have found that as well. But maybe I don&amp;rsquo;t agree with the default severity of that rule. I can override the severity of the rule to be critical instead of high.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;severity_overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;azure-storage-enforce-https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;CRITICAL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;minimum_severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MEDIUM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I&amp;rsquo;ve got a few simple misconfigurations checked into my sample repository at &lt;a href="https://github.com/Codebytes/secure-terraform-on-azure" target="_blank" rel="noreferrer"&gt;secure-terraform-on-azure&lt;/a&gt;. They aren&amp;rsquo;t on the main branch, but the &lt;a href="https://github.com/Codebytes/secure-terraform-on-azure/tree/demos" target="_blank" rel="noreferrer"&gt;demos&lt;/a&gt; branch.&lt;/p&gt;

&lt;h3 class="relative group"&gt;VSCode
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;What about adding tfsec to VSCode? Well, we can install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=tfsec.tfsec" target="_blank" rel="noreferrer"&gt;tfsec extension&lt;/a&gt;. This will use the installed version of tfsec to scan our code. It will also show the results so we can see them right in our editor and go to the line where the issue is.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1635"
 height="1295"
 src="/images/tfsec-extension-errors_hu_bc512e67db2be899.png"
 srcset="/images/tfsec-extension-errors_hu_bc512e67db2be899.png 800w,/images/tfsec-extension-errors_hu_c60c2178a184843b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/tfsec-extension-errors.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Great! Now we can see the issues right in our editor. But what if we forget to check the problems? How do we catch the issues and resolve them in our pipeline?&lt;/p&gt;

&lt;h3 class="relative group"&gt;GitHub Actions
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Aquasecurity has provided a few GitHub Actions and sample workflows to scan our terraform during every pull request. We can use the &lt;a href="https://github.com/marketplace/actions/tfsec-action" target="_blank" rel="noreferrer"&gt;tfsec-action&lt;/a&gt; to scan our code.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tfsec&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tfsec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tfsec&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Clone repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@master&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tfsec&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;aquasecurity/tfsec-action@v1.0.0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now because I&amp;rsquo;ve got some tf nested, I added the following line:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;additional_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;force-all-dirs&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;All of the available options and parameters are documented on the actions page.&lt;/p&gt;
&lt;p&gt;This will run tfsec against our repository on every check-in to main and every pull request. If we have insecure code, it will fail the workflow.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1357"
 height="927"
 src="/images/tfsec-workflow-errors_hu_6267ae1155583903.png"
 srcset="/images/tfsec-workflow-errors_hu_6267ae1155583903.png 800w,/images/tfsec-workflow-errors_hu_747ad92375b36f34.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/tfsec-workflow-errors.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;There is another action, &lt;a href="https://github.com/marketplace/actions/run-tfsec-pr-commenter" target="_blank" rel="noreferrer"&gt;tfsec-pr-commenter-action&lt;/a&gt; that is supposed to automatically add comments to the pull request with the results of the scan. I haven&amp;rsquo;t been able to get it to work because of my file nesting, but I&amp;rsquo;m sure it will be fixed soon.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Pre-commit Hooks
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The last thing I want to talk about is pre-commit hooks. Pre-commit hooks are a way to run a script or command before you commit your code. This is a great way to make sure you don&amp;rsquo;t commit any code that doesn&amp;rsquo;t pass your checks.&lt;/p&gt;
&lt;p&gt;There is a great framework for running pre-commit hooks called &lt;a href="https://pre-commit.com/" target="_blank" rel="noreferrer"&gt;pre-commit&lt;/a&gt;. It is a python package that you can install with pip. Once you have it installed, you can add a .pre-commit-config.yaml file to your repository. This file will contain the hooks you want to run.&lt;/p&gt;
&lt;p&gt;There is a project for setting up pre-commit hooks to scan terraform. It is called &lt;a href="https://github.com/antonbabenko/pre-commit-terraform#terraform_tfsec" target="_blank" rel="noreferrer"&gt;pre-commit-terraform&lt;/a&gt;. It will run tfsec against your code and fail the commit if there are any issues.&lt;/p&gt;
&lt;p&gt;This runs BEFORE you code gets committed and before it gets pushed to GitHub. Your CI/CD workflow will never run if the pre-commit hook blocks the commit.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Terraform is a great tool for infrastructure as code. Leveraging smart tools like tfsec at various places in the development cycle can help you find and fix those issues before they become a problem. Join me again soon as we look at more tools to help you secure your infrastructure.&lt;/p&gt;</content:encoded></item><item><title>Multiple Domains on GitHub Pages</title><link>https://chris-ayers.com/posts/multiple-domains-on-github-pages/</link><pubDate>Tue, 27 Dec 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/multiple-domains-on-github-pages/</guid><description>&lt;p&gt;Something I found out after moving from WordPress to GitHub Pages is that out of the box you can only host a single domain for a repository with GitHub Pages. This is a problem for me because I have a number of domains I was hosting at WordPress that I wanted to point at my GitHub Pages.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Official Docs and the limitation
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;So officially, GitHub pages doesn&amp;rsquo;t support multiple domains. The docs here &lt;a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/troubleshooting-custom-domains-and-github-pages#custom-domain-names-that-are-unsupported" target="_blank" rel="noreferrer"&gt;https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/troubleshooting-custom-domains-and-github-pages#custom-domain-names-that-are-unsupported&lt;/a&gt; state:&lt;/p&gt;</description><content:encoded>&lt;p&gt;Something I found out after moving from WordPress to GitHub Pages is that out of the box you can only host a single domain for a repository with GitHub Pages. This is a problem for me because I have a number of domains I was hosting at WordPress that I wanted to point at my GitHub Pages.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Official Docs and the limitation
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;So officially, GitHub pages doesn&amp;rsquo;t support multiple domains. The docs here &lt;a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/troubleshooting-custom-domains-and-github-pages#custom-domain-names-that-are-unsupported" target="_blank" rel="noreferrer"&gt;https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/troubleshooting-custom-domains-and-github-pages#custom-domain-names-that-are-unsupported&lt;/a&gt; state:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make sure your site does not&lt;/strong&gt;&lt;/p&gt;

 



&lt;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &gt;Use more than one apex domain. For example, both &lt;code&gt;*example.com*&lt;/code&gt; and &lt;code&gt;*anotherexample.com*&lt;/code&gt;.
Use more than one www subdomain. For example, both &lt;code&gt;*www.example.com*&lt;/code&gt; and &lt;code&gt;*www.anotherexample.com*&lt;/code&gt;.
Use both an apex domain and custom subdomain. For example, both &lt;code&gt;*example.com*&lt;/code&gt; and &lt;code&gt;*docs.example.com*&lt;/code&gt;.&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;This means for example, I can setup &lt;a href="https://www.chris-ayers.com" target="_blank" rel="noreferrer"&gt;www.chris-ayers.com&lt;/a&gt; and &lt;a href="https://chris-ayers.com" target="_blank" rel="noreferrer"&gt;chris-ayers.com&lt;/a&gt; for my repository and nothing else.
If I want another domain to point to it, like &lt;a href="https://chrisayers.me" target="_blank" rel="noreferrer"&gt;chrisayers.me&lt;/a&gt;, I would probably need to fork the repository and setup the new repository for the additional domain.&lt;/p&gt;
&lt;p&gt;So how do we solve this? There has to be another way. There is! We can use Cloudflare to redirect the additional domains to the GitHub Pages domain. It seems this has been an issue for everyone since 2018. I found a few articles that helped me figure this out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://olney.ai/category/2018/07/30/ai.html" target="_blank" rel="noreferrer"&gt;https://olney.ai/category/2018/07/30/ai.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@desfocado/how-to-get-multiple-domains-pointing-to-github-pages-using-cloudflare-41fcb20ed10" target="_blank" rel="noreferrer"&gt;https://medium.com/@desfocado/how-to-get-multiple-domains-pointing-to-github-pages-using-cloudflare-41fcb20ed10&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Cloudflare
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Cloudflare is a DNS provider that has a free tier. It also has a feature called Page Rules that allows you to setup custom rules for your domain. This is what we will use to redirect the additional domains to the GitHub Pages domain.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Setup
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;First, we need to setup Cloudflare. This is a pretty simple process. You just need to create an account and add your domain. You can do this by following the steps here: &lt;a href="https://developers.cloudflare.com/fundamentals/get-started/setup/" target="_blank" rel="noreferrer"&gt;https://developers.cloudflare.com/fundamentals/get-started/setup/&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an account (if you don&amp;rsquo;t already have one)&lt;/li&gt;
&lt;li&gt;Add your domain&lt;/li&gt;
&lt;li&gt;I chose the free plan for each of my domains&lt;/li&gt;
&lt;li&gt;I reviewed all the entries and made sure they were correct (I removed most of them)&lt;/li&gt;
&lt;li&gt;I updated the DNS nameservers for my domain to the ones provided by Cloudflare&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I decided that my primary domain would be &lt;a href="https://chris-ayers.com" target="_blank" rel="noreferrer"&gt;https://chris-ayers.com&lt;/a&gt;. As part of the normal GitHub Pages DNS setup, you add the domain name to GitHub Pages then add A records to your DNS domain. The directions are &lt;a href="https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site#configuring-an-apex-domain" target="_blank" rel="noreferrer"&gt;in the GitHub docs&lt;/a&gt;. I&amp;rsquo;ve added the needed A records to my domain on CloudFlare.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1277"
 height="552"
 src="/images/chrisayerscom-dns_hu_ad99154f5f449a0b.png"
 srcset="/images/chrisayerscom-dns_hu_ad99154f5f449a0b.png 800w,/images/chrisayerscom-dns_hu_81022b66bbfa11ed.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/chrisayerscom-dns.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Next I need to setup my other domains. For each domain, I added it to Cloudflare. I then setup 3 CNAME entries. Let&amp;rsquo;s look at my configuration for chrisayers.dev as an example. I setup the following CNAME entries:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1260"
 height="503"
 src="/images/chrisayersdev-cnames_hu_e7bb0029f791b278.png"
 srcset="/images/chrisayersdev-cnames_hu_e7bb0029f791b278.png 800w,/images/chrisayersdev-cnames_hu_9300b0d798f7eeab.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/chrisayersdev-cnames.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;With this configuration, I&amp;rsquo;m saying that pretty much everything should point to &lt;a href="https://chris-ayers.com" target="_blank" rel="noreferrer"&gt;https://chris-ayers.com&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Type&lt;/th&gt;
 &lt;th style="text-align: center"&gt;Name&lt;/th&gt;
 &lt;th style="text-align: right"&gt;Content&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CNAME&lt;/td&gt;
 &lt;td style="text-align: center"&gt;*&lt;/td&gt;
 &lt;td style="text-align: right"&gt;chris-ayers.com&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CNAME&lt;/td&gt;
 &lt;td style="text-align: center"&gt;chrisayers.dev&lt;/td&gt;
 &lt;td style="text-align: right"&gt;chris-ayers.com&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CNAME&lt;/td&gt;
 &lt;td style="text-align: center"&gt;www&lt;/td&gt;
 &lt;td style="text-align: right"&gt;chris-ayers.com&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Now I need to setup the Page Rules. These are main thing that makes it all work.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Cloudflare Page Rules
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;This is where it gets a little more complicated. Continuing with my example before, adding chrisayers.dev, I setup the following Page Rules:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1657"
 height="819"
 src="/images/chrisayersdev-page-rules1_hu_f71e9cd4f54d79f.png"
 srcset="/images/chrisayersdev-page-rules1_hu_f71e9cd4f54d79f.png 800w,/images/chrisayersdev-page-rules1_hu_a940e84922c7323a.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/chrisayersdev-page-rules1.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1181"
 height="868"
 src="/images/chrisayersdev-page-rules2_hu_67864118879813d1.png"
 srcset="/images/chrisayersdev-page-rules2_hu_67864118879813d1.png 800w,/images/chrisayersdev-page-rules2_hu_d87639b211443f8.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/chrisayersdev-page-rules2.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The main thing is to note the special characters (*) and variables in the rules.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Field&lt;/th&gt;
 &lt;th style="text-align: center"&gt;Value&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;URL (required)&lt;/td&gt;
 &lt;td style="text-align: center"&gt;&lt;code&gt;*chrisayers.dev/*&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Forwarding URL&lt;/td&gt;
 &lt;td style="text-align: center"&gt;&lt;code&gt;301 - Permanent Redirect&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Destination URL&lt;/td&gt;
 &lt;td style="text-align: center"&gt;&lt;code&gt;https://chris-ayers.com/$2&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;After a little bit, everything should redirect to the GitHub Pages domain and work. I was fiddling a lot for the first domain or two, and they took a little while to start working. I&amp;rsquo;m not sure if it was just a propagation issue or what. But after a few hours, everything started working.&lt;/p&gt;
&lt;p&gt;The other domains I did (I have around 10) were pretty quick. I think it was just the first ones that took a while because I was trying to figure out the process.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m pretty happy with the results. I was able to get all my domains to point to my GitHub Pages site. I&amp;rsquo;m not sure if this is the best way to do it, but it works. I&amp;rsquo;m sure there are other ways to do it, but this is the way I figured out. I hope this helps someone else out there.&lt;/p&gt;</content:encoded></item><item><title>Customizing the Jekyll Theme</title><link>https://chris-ayers.com/posts/customizing-the-jekyll-theme/</link><pubDate>Fri, 23 Dec 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/customizing-the-jekyll-theme/</guid><description>&lt;p&gt;I haven&amp;rsquo;t done a lot with jekyll in the past, but I&amp;rsquo;m a big fan of Markdown everything. For me that usually means I&amp;rsquo;m taking notes in Markdown &lt;a href="https://obsidian.md" target="_blank" rel="noreferrer"&gt;Obsidian&lt;/a&gt;, doing diagrams in mermaid in Azure DevOps or &lt;a href="https://mermaid.live/" target="_blank" rel="noreferrer"&gt;https://mermaid.live/&lt;/a&gt;. I&amp;rsquo;ve even started turning my talk slides into Markdown with a tool called &lt;a href="https://marp.app/" target="_blank" rel="noreferrer"&gt;MARP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Understanding when I use standard Markdown or some sort of templating language (jekyll uses Liquid) has been fun. I&amp;rsquo;ll do something in HTML or Markdown, then find out that Jekyll or my theme already has helpers to render that (like gists, videos, and figures). Sometimes rendering more advanced things takes a little tweaking of Jekyll and the theme.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I haven&amp;rsquo;t done a lot with jekyll in the past, but I&amp;rsquo;m a big fan of Markdown everything. For me that usually means I&amp;rsquo;m taking notes in Markdown &lt;a href="https://obsidian.md" target="_blank" rel="noreferrer"&gt;Obsidian&lt;/a&gt;, doing diagrams in mermaid in Azure DevOps or &lt;a href="https://mermaid.live/" target="_blank" rel="noreferrer"&gt;https://mermaid.live/&lt;/a&gt;. I&amp;rsquo;ve even started turning my talk slides into Markdown with a tool called &lt;a href="https://marp.app/" target="_blank" rel="noreferrer"&gt;MARP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Understanding when I use standard Markdown or some sort of templating language (jekyll uses Liquid) has been fun. I&amp;rsquo;ll do something in HTML or Markdown, then find out that Jekyll or my theme already has helpers to render that (like gists, videos, and figures). Sometimes rendering more advanced things takes a little tweaking of Jekyll and the theme.&lt;/p&gt;
&lt;p&gt;Lets take Mermaid for example. If I want to render mermaid, it won&amp;rsquo;t work out of the box with jekyll or my theme. I need to tweak things and initialize things manually. If I want to render a graph, this won&amp;rsquo;t work out of the box.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;&amp;#39;&amp;#39;mermaid
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; graph TD;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A--&amp;gt;B;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A--&amp;gt;C;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; B--&amp;gt;D;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; C--&amp;gt;D;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;So to make it work, I had to override the base theme. To start, I looked at the themes layouts. They live in the _layout folder of the theme &lt;a href="https://github.com/mmistakes/minimal-mistakes/tree/master/_layouts" target="_blank" rel="noreferrer"&gt;on GitHub&lt;/a&gt;. These are the base structure of a page and most reference either Archive or Default.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="726"
 height="384"
 src="/images/minimal-mistakes-layout-posts_hu_b400dffca355beac.png"
 srcset="/images/minimal-mistakes-layout-posts_hu_b400dffca355beac.png 800w,/images/minimal-mistakes-layout-posts_hu_7cfa67af8038a2ea.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/minimal-mistakes-layout-posts.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="727"
 height="372"
 src="/images/minimal-mistakes-layout-archive_hu_6ad5cedbe1748e9c.png"
 srcset="/images/minimal-mistakes-layout-archive_hu_6ad5cedbe1748e9c.png 800w,/images/minimal-mistakes-layout-archive_hu_81d2c2b06b8c010e.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/minimal-mistakes-layout-archive.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Fortunately Archive also refers to Default so I can focus my changes there if I want to override it.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1138"
 height="1459"
 src="/images/minimal-mistakes-layout-default_hu_86766bbf8bd25e23.png"
 srcset="/images/minimal-mistakes-layout-default_hu_86766bbf8bd25e23.png 800w,/images/minimal-mistakes-layout-default_hu_720ec00cb20743d3.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/minimal-mistakes-layout-default.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Looking at this file, there are a few includes defined. One of which is the footer/custom.html and one is scripts.html. I looked through the scripts.html and it has a lot of logic I&amp;rsquo;d prefer not to duplicate. I&amp;rsquo;m already using the header-custom file to define some &lt;code&gt;css/scripts&lt;/code&gt;. I&amp;rsquo;ve found Mermaid needs its init to run in the body, so i&amp;rsquo;ll probably want to leverage the footer/custom.html for this. It&amp;rsquo;s an empty file in the theme, making it any easy choice to override.&lt;/p&gt;
&lt;p&gt;I created a footer folder in my local project _includes and a custom.html file. I added some simple logic to only add the mermaid logic if I added a &lt;code&gt;mermaid:true&lt;/code&gt; flag at the top of the page.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1173"
 height="426"
 src="/images/custom-footer_hu_1fd753b28c8e0ebe.png"
 srcset="/images/custom-footer_hu_1fd753b28c8e0ebe.png 800w,/images/custom-footer_hu_88e28ab887988030.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/custom-footer.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;So lets set that!&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1118"
 height="451"
 src="/images/mermaid-frontmatter_hu_93312badfff8cae5.png"
 srcset="/images/mermaid-frontmatter_hu_93312badfff8cae5.png 800w,/images/mermaid-frontmatter_hu_bbb9d944a7d1c338.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/mermaid-frontmatter.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;At this point, everything should work and embedded mermaid in my Markdown posts renders properly!&lt;/p&gt;
&lt;pre class="not-prose mermaid"&gt;
graph TD;
 A--&gt;B;
 A--&gt;C;
 B--&gt;D;
 C--&gt;D;
&lt;/pre&gt;

&lt;p&gt;I&amp;rsquo;ve done a few other minor customizations so far. Like adding some custom CSS and some JavaScript for app insights. Both of those involved a similar process of adding &lt;code&gt;head/custom.html&lt;/code&gt; and &lt;code&gt;assets/css/custom.css&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I used a few different sources to get things to work for me. Please check those out as well to see if they can help you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://it-journey.dev/docs/jekyll-diagram-with-mermaid" target="_blank" rel="noreferrer"&gt;https://it-journey.dev/docs/jekyll-diagram-with-mermaid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jasonbellamy/jekyll-mermaid" target="_blank" rel="noreferrer"&gt;https://github.com/jasonbellamy/jekyll-mermaid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jojozhuang.github.io/tutorial/jekyll-diagram-with-mermaid" target="_blank" rel="noreferrer"&gt;https://jojozhuang.github.io/tutorial/jekyll-diagram-with-mermaid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kitian616/jekyll-TeXt-theme/blob/master/_includes/markdown-enhancements/mermaid.html" target="_blank" rel="noreferrer"&gt;https://github.com/kitian616/jekyll-TeXt-theme/blob/master/_includes/markdown-enhancements/mermaid.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/53883747/how-to-make-github-pages-markdown-support-mermaid-diagram" target="_blank" rel="noreferrer"&gt;https://stackoverflow.com/questions/53883747/how-to-make-github-pages-markdown-support-mermaid-diagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mtrienis/jekyll-mermaid-blog" target="_blank" rel="noreferrer"&gt;https://github.com/mtrienis/jekyll-mermaid-blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks for following along on this journey. I hope this helps those starting their Jekyll journey or hosting on GitHub Pages.&lt;/p&gt;</content:encoded></item><item><title>Migrating from WordPress to GitHub Pages</title><link>https://chris-ayers.com/posts/migrating-from-wordpress-to-github-pages/</link><pubDate>Tue, 20 Dec 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/migrating-from-wordpress-to-github-pages/</guid><description>&lt;p&gt;I&amp;rsquo;ve been hosting on WordPress for a while. I wanted something that worked pretty well and was easy to work with. I picked a decent theme, added some plugins, pointed my domains and was up and running. I would work on blogs in Markdown, and then paste the txt into a Markdown. I could upload a few images and move them around in a wysiwyg.&lt;/p&gt;
&lt;p&gt;Lately, I&amp;rsquo;ve been doing a lot more in Markdown. All my conference talks were in PowerPoint but I&amp;rsquo;ve started switching over to &lt;a href="https://chris-ayers.com/2023/03/26/marp-create-presentations-with-markdown" target="_blank" rel="noreferrer"&gt;Markdown slides using MARP&lt;/a&gt;. I should probably do a post on MARP sometime (&lt;a href="https://chris-ayers.com/2023/03/26/marp-create-presentations-with-markdown" target="_blank" rel="noreferrer"&gt;I did :-)&lt;/a&gt; ). I wanted to reduce my overhead of WordPress Hosting and get back into more direct styling and coding of my theme. I decided to switch my hosting to Jekyll on GitHub Pages.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve been hosting on WordPress for a while. I wanted something that worked pretty well and was easy to work with. I picked a decent theme, added some plugins, pointed my domains and was up and running. I would work on blogs in Markdown, and then paste the txt into a Markdown. I could upload a few images and move them around in a wysiwyg.&lt;/p&gt;
&lt;p&gt;Lately, I&amp;rsquo;ve been doing a lot more in Markdown. All my conference talks were in PowerPoint but I&amp;rsquo;ve started switching over to &lt;a href="https://chris-ayers.com/2023/03/26/marp-create-presentations-with-markdown" target="_blank" rel="noreferrer"&gt;Markdown slides using MARP&lt;/a&gt;. I should probably do a post on MARP sometime (&lt;a href="https://chris-ayers.com/2023/03/26/marp-create-presentations-with-markdown" target="_blank" rel="noreferrer"&gt;I did :-)&lt;/a&gt; ). I wanted to reduce my overhead of WordPress Hosting and get back into more direct styling and coding of my theme. I decided to switch my hosting to Jekyll on GitHub Pages.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Setting Up GitHub Pages
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;There are great docs on how to setup Jekyll with GitHub Pages.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll" target="_blank" rel="noreferrer"&gt;https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;First this is I setup a repository to host my page. My username is codebytes, so I made a repository for codebytes.github.io.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site#creating-a-repository-for-your-site" target="_blank" rel="noreferrer"&gt;https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site#creating-a-repository-for-your-site&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Knowing that I&amp;rsquo;m going to be using this repository for a Jekyll based site, I setup a dev container so that I could quickly have all the tools setup and configured.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;a href="https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/creating-a-github-pages-site-with-jekyll" target="_blank" rel="noreferrer"&gt;https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/creating-a-github-pages-site-with-jekyll&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Setting Up Jekyll
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I used the Jekyll theme called &amp;ldquo;Minimal Mistakes&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;a href="https://mademistakes.com/work/jekyll-themes/minimal-mistakes/" target="_blank" rel="noreferrer"&gt;https://mademistakes.com/work/jekyll-themes/minimal-mistakes/&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;I used the Jekyll-import. Because of the plan I was using on WordPress.com, I couldn&amp;rsquo;t install custom extensions. That eliminated many export/import options. There is a free option for importing things into Jekyll-import using a Wordpress.xml export file.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;a href="https://import.jekyllrb.com/docs/wordpressdotcom" target="_blank" rel="noreferrer"&gt;https://import.jekyllrb.com/docs/wordpressdotcom&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Since I&amp;rsquo;m using a devcontainer for my local blog, I setup Jekyll-import and its dependencies. I added the dependencies to the local gemfile and ran bundle install.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hpricot&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;open_uri_redirections&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;safe_yaml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;jekyll-import&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bundle install&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;On Wordpress.com I exported all my posts via the export functionality.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="576"
 height="1172"
 src="/images/wordpress-export-tools_hu_cd2adf580d63f82f.png"
 srcset="/images/wordpress-export-tools_hu_cd2adf580d63f82f.png 800w,/images/wordpress-export-tools_hu_121444b858e63bfd.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/wordpress-export-tools.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I then Exported everything which downloads as a single file.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1191"
 height="717"
 src="/images/wordpress-export_hu_43765d07e1cb5600.png"
 srcset="/images/wordpress-export_hu_43765d07e1cb5600.png 800w,/images/wordpress-export_hu_c1965fbe5bf4f841.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/wordpress-export.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This provides an download that&amp;rsquo;s a ZIP file with an xml file. I ran the import command:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bundle &lt;span class="nb"&gt;exec&lt;/span&gt; jekyll import wordpressdotcom --source wordpress.export.xml --assets_folder assets/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I imported all my posts from Wordpress.com (about 20). It also imported all my images, but they had many duplicates. Some image URLs had both normal URLs (&amp;quot;/blah/image.jpg&amp;quot;) but some had other URLs (&amp;quot;/blah/image.jpg@60px&amp;quot;). After this import process, I touched up each post and image.&lt;/p&gt;
&lt;p&gt;For each post I had to resolve the front matter at the top of each post. Imported it looked like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;layout: post
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;title: &amp;#39;ARM - Part 1: Azure Resource Manager&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;date: 2019-05-11 23:53:57.000000000 +00:00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;type: post
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;parent_id: &amp;#39;0&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;published: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;password: &amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;status: publish
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;categories:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; DevOps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; arm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; arm templates
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; azure
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; infrastructure
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; Microsoft
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; PowerShell
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; scripts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;meta:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _wpas_skip_571772: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _publicize_done_21771511: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; publicize_linkedin_url: &amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _publicize_job_id: &amp;#39;30717764168&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; timeline_notification: &amp;#39;1557633304&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _publicize_done_external: a:2:{s:7:&amp;#34;twitter&amp;#34;;a:1:{i:22482724;s:60:&amp;#34;https://twitter.com/Chris_L_Ayers/status/1127421911250677761&amp;#34;;}s:8:&amp;#34;facebook&amp;#34;;a:1:{i:22482726;s:52:&amp;#34;https://facebook.com/482199522523634_482213102522276&amp;#34;;}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _wpas_done_22482724: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; publicize_twitter_user: Chris_L_Ayers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _publicize_done_21771513: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _wpas_done_22482728: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _publicize_done_21801094: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; _wpas_done_22482726: &amp;#39;1&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;author:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; login: chrislayers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; email: EMAIL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; display_name: Chris Ayers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; first_name: Chris
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; last_name: Ayers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;permalink: &amp;#39;/2019/05/11/arm-azure-resource-manager/&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After cleaning it up the front matter is much clearer.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;title: &amp;#39;ARM - Part 1: Azure Resource Manager&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;date: 2019-05-11 23:53:57.000000000 +00:00
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;type: post
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;categories:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; DevOps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; arm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; arm templates
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; azure
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; infrastructure
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; Microsoft
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; PowerShell
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;-&lt;/span&gt; scripts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;permalink: &amp;#39;/2019/05/11/arm-azure-resource-manager/&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I similarly had to clean up some of the HTML in the imported posts. Mainly I removed a bunch of the commented HTML and converted it all to Markdown.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- wp:heading {&amp;#34;level&amp;#34;:3} --&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;The Journey Begins&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- /wp:heading --&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;That got cleaned up to and looking good in with Markdown preview:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;### The Journey Begins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Finally, I reviewed each image and cleaned up the references.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- wp:image {&amp;#34;id&amp;#34;:151} --&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;figure&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;wp-block-image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/images/newresourcegroup-3.png&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;wp-image-151&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c"&gt;&amp;lt;!-- /wp:image --&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The theme &lt;a href="https://mmistakes.github.io/minimal-mistakes/docs/helpers/#figure" target="_blank" rel="noreferrer"&gt;Minimal Mistakes&lt;/a&gt; has great helpers for figures and images. I converted the HTML to Markdown using the figure helper like this:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-md" data-lang="md"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="936"
 height="697"
 src="/images/newresourcegroup-3_hu_5146520a2e9784c4.png"
 srcset="/images/newresourcegroup-3_hu_5146520a2e9784c4.png 800w,/images/newresourcegroup-3_hu_314fca093561f753.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/newresourcegroup-3.png"
 /&gt;
 
 
 &lt;/figure&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

 



&lt;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &gt;&lt;strong&gt;Update:&lt;/strong&gt; I didn&amp;rsquo;t realize this was rendering wrong, I had to turn off liquid rendering with raw/endraw tags, or you could replace &lt;code&gt;{&lt;/code&gt; with &lt;code&gt;&amp;amp;#123;&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt; with &lt;code&gt;&amp;amp;#125;&lt;/code&gt; to get the same effect.&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;I spent a few hours, fixed all the front matter and naming, converted all HTML to Markdown, then removed and renamed old images.&lt;/p&gt;
&lt;p&gt;I was able to test locally by running running &lt;code&gt;jekyll serve&lt;/code&gt; and checking how everything looked in a browser.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bundle &lt;span class="nb"&gt;exec&lt;/span&gt; jekyll serve&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As part of setting up things with GitHub Pages, there is also a workflow that helps build and release the jeykll site to GitHub pages.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll follow this up with another post on some of my customizations for monitoring and dns. Out of the box, GitHub pages only allow 1 custom dns domain. There are a few ways to host multiple domains on a single GitHub Pages site (check out &lt;a href="https://chrislayers.com" target="_blank" rel="noreferrer"&gt;chrislayers.com&lt;/a&gt;, &lt;a href="https://chrisayers.me" target="_blank" rel="noreferrer"&gt;chrisayers.me&lt;/a&gt;, or &lt;a href="https://chris-ayers.com" target="_blank" rel="noreferrer"&gt;chris-ayers.com&lt;/a&gt;).&lt;/p&gt;</content:encoded></item><item><title>Validating .NET Configuration</title><link>https://chris-ayers.com/posts/dotnet-configuration/</link><pubDate>Sat, 03 Dec 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/dotnet-configuration/</guid><description>&lt;figure&gt;
 &lt;img class="my-0 rounded-md float-left m-4" src="/images/logos/csadvent%20logo.png" alt="C# Advent" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://www.csadvent.christmas/" target="_blank" rel="noreferrer"&gt;C# Advent Calendar 2022&lt;/a&gt;. I really want to thank &lt;a href="https://twitter.com/mgroves" target="_blank" rel="noreferrer"&gt;Matthew D. Groves&lt;/a&gt; and &lt;a href="https://twitter.com/_calvinallen" target="_blank" rel="noreferrer"&gt;Calvin Allen&lt;/a&gt; for helping set this up! Look for #csadvent on Twitter! Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;re done here&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the great things about the configuration system in .NET is the type safety, dependency injection, and model binding. Something we can take advantage of is to validate our configuration on startup and fail if it doesn&amp;rsquo;t pass validation. Having that fast failure is awesome when working with containers and applications that have liveness and readiness probes.&lt;/p&gt;</description><content:encoded>
&lt;figure&gt;
 &lt;img class="my-0 rounded-md float-left m-4" src="/images/logos/csadvent%20logo.png" alt="C# Advent" /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://www.csadvent.christmas/" target="_blank" rel="noreferrer"&gt;C# Advent Calendar 2022&lt;/a&gt;. I really want to thank &lt;a href="https://twitter.com/mgroves" target="_blank" rel="noreferrer"&gt;Matthew D. Groves&lt;/a&gt; and &lt;a href="https://twitter.com/_calvinallen" target="_blank" rel="noreferrer"&gt;Calvin Allen&lt;/a&gt; for helping set this up! Look for #csadvent on Twitter! Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;re done here&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the great things about the configuration system in .NET is the type safety, dependency injection, and model binding. Something we can take advantage of is to validate our configuration on startup and fail if it doesn&amp;rsquo;t pass validation. Having that fast failure is awesome when working with containers and applications that have liveness and readiness probes.&lt;/p&gt;
&lt;p&gt;I have some examples of the amazing configuration system in my &lt;a href="https://github.com/Codebytes/dotnet-configuration-in-depth" target="_blank" rel="noreferrer"&gt;GitHub repository&lt;/a&gt; I use for my &lt;a href="https://github.com/Codebytes/dotnet-configuration-in-depth" target="_blank" rel="noreferrer"&gt;.NET Configuration in Depth&lt;/a&gt; conference talk. Feel free to take a look at the samples found there.&lt;/p&gt;
&lt;p&gt;But let&amp;rsquo;s get to the topic at hand. How do we validate our configuration in .NET?&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with some configuration we&amp;rsquo;ll be wanting to validate and actually fail on startup if its not there.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;WebhookUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://example.com/event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;DisplayName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DevOps&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And let&amp;rsquo;s have a strongly typed class to hold that configuration.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebHookSettings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;WebhookUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now how do we &lt;em&gt;KNOW&lt;/em&gt; our URL is valid? We can add some validation to our application pretty easily leveraging Data Annotations! This lets us mark fields as required as well as doing other validation. There are a ton of build in attributes, like StringLength, Range, EmailAddress, and more. You can find some of them listed in the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-7.0" target="_blank" rel="noreferrer"&gt;documentation&lt;/a&gt; if you&amp;rsquo;re curious.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s extend our class to add that basic validation.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebHookSettings&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; [Required, Url]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;WebhookUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; [Required]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DisplayName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;For my example, I&amp;rsquo;m using minimal APIs, and I can enable validation using just a few lines of code to invoke a few extension methods.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.ComponentModel.DataAnnotations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WebApplication&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebHookSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidateDataAnnotations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidateOnStart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This will not only validate the DataAnnotations on my configuration class, it will also run that validation on startup.&lt;/p&gt;
&lt;p&gt;We can see what happens if I mangle my configuration and try to start my application. With this configuration below&amp;hellip;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;WebhookUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;DisplayName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DevOps&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Running the application fails with this error.&lt;/p&gt;

 



&lt;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &gt;Unhandled exception. Microsoft.Extensions.Options.OptionsValidationException: DataAnnotation validation failed for &amp;lsquo;WebHookSettings&amp;rsquo; members: &amp;lsquo;WebhookUrl&amp;rsquo; with the error: &amp;lsquo;The WebhookUrl field is required.&amp;rsquo;.&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;Now its complaining about the field being blank while required. What if we populate the URL field with a bad value that isn&amp;rsquo;t a Url?&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;WebhookUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;BADVALUE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;DisplayName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DevOps&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We we run the app with this value, we get a different validation error.&lt;/p&gt;

 



&lt;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &gt;Unhandled exception. Microsoft.Extensions.Options.OptionsValidationException: DataAnnotation validation failed for &amp;lsquo;WebHookSettings&amp;rsquo; members: &amp;lsquo;WebhookUrl&amp;rsquo; with the error: &amp;lsquo;The WebhookUrl field is not a valid fully-qualified HTTP, HTTPS, or FTP URL.&amp;rsquo;.&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;We can take this a step further, with additional custom validation if we choose. Let&amp;rsquo;s reset our configuration to something reasonable, but insecure..&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;WebhookUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http://example.com/event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;DisplayName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DevOps&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;How do we validate we&amp;rsquo;re always using https? Let&amp;rsquo;s add custom validation to our application.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebHookSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindConfiguration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidateDataAnnotations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webHookSettings&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;webHookSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebhookUrl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;WebHookUrl must start with https://&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidateOnStart&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now we can validate the normal DataAnnotations as well as any custom validation logic we would like to include on startup!&lt;/p&gt;

 



&lt;div
 
 class="flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900"
 
 &gt;
 &lt;span
 
 class="text-primary-400 pe-3 flex items-center"
 
 &gt;
 &lt;span class="relative block icon"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"&gt;&lt;path fill="currentColor" d="M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;

 &lt;span
 
 class="dark:text-neutral-300"
 
 &gt;Unhandled exception. Microsoft.Extensions.Options.OptionsValidationException: WebHookUrl must start with https://&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;If we fix everything with our validation, like so&amp;hellip;&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;WebHook&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;WebhookUrl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://example.com/event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;DisplayName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DevOps&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Enabled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The application starts successfully!&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ dotnet run
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Building...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info: Microsoft.Hosting.Lifetime&lt;span class="o"&gt;[&lt;/span&gt;14&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Now listening on: https://localhost:7090
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info: Microsoft.Hosting.Lifetime&lt;span class="o"&gt;[&lt;/span&gt;14&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Now listening on: http://localhost:5268
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info: Microsoft.Hosting.Lifetime&lt;span class="o"&gt;[&lt;/span&gt;0&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Application started. Press Ctrl+C to shut down.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;info: Microsoft.Hosting.Lifetime&lt;span class="o"&gt;[&lt;/span&gt;0&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Hosting environment: Development&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As you build your applications, think about adding validation to ensure your applications have a valid configuration at startup. Having runtime errors due to configuration is an annoying and sometimes hard problem to troubleshoot.&lt;/p&gt;
&lt;p&gt;Please dive into the amazing documentation on the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration" target="_blank" rel="noreferrer"&gt;.NET Configuration system&lt;/a&gt; and look into the &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/options" target="_blank" rel="noreferrer"&gt;Options Pattern documentation&lt;/a&gt; as well!&lt;/p&gt;</content:encoded></item><item><title>Tools for working with Kubernetes</title><link>https://chris-ayers.com/posts/tools-for-working-with-kubernetes/</link><pubDate>Thu, 17 Nov 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/tools-for-working-with-kubernetes/</guid><description>&lt;p&gt;I&amp;rsquo;ve been in a number of internal and external calls where tooling to help work with Kubernetes keeps coming up. I thought I would share some of these cool tools in case you weren&amp;rsquo;t aware of them.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Tools
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;K9S&lt;/li&gt;
&lt;li&gt;kubectx and kubens&lt;/li&gt;
&lt;li&gt;fzf&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;K9S
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;K9S is a terminal based UI for interacting and managing Kubernetes Clusters. You can find k9s at &lt;a href="https://github.com/derailed/k9s" target="_blank" rel="noreferrer"&gt;https://github.com/derailed/k9s&lt;/a&gt; or their site &lt;a href="https://k9scli.io/" target="_blank" rel="noreferrer"&gt;https://k9scli.io/&lt;/a&gt;.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve been in a number of internal and external calls where tooling to help work with Kubernetes keeps coming up. I thought I would share some of these cool tools in case you weren&amp;rsquo;t aware of them.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Tools
 
 
 
 
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;K9S&lt;/li&gt;
&lt;li&gt;kubectx and kubens&lt;/li&gt;
&lt;li&gt;fzf&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class="relative group"&gt;K9S
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;K9S is a terminal based UI for interacting and managing Kubernetes Clusters. You can find k9s at &lt;a href="https://github.com/derailed/k9s" target="_blank" rel="noreferrer"&gt;https://github.com/derailed/k9s&lt;/a&gt; or their site &lt;a href="https://k9scli.io/" target="_blank" rel="noreferrer"&gt;https://k9scli.io/&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt="k9s main screen"
 width="2520"
 height="1320"
 src="/images/k9s-main_hu_fdf963229ff2580b.png"
 srcset="/images/k9s-main_hu_fdf963229ff2580b.png 800w,/images/k9s-main_hu_e82a4b9266b51a19.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/k9s-main.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Kubectx and Kubens
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;These tools are amazing at quickly switching context and namespaces while working with kubernetes.
You can find k9s at &lt;a href="https://github.com/ahmetb/kubectx" target="_blank" rel="noreferrer"&gt;https://github.com/ahmetb/kubectx&lt;/a&gt; or the author&amp;rsquo;s site &lt;a href="https://ahmet.im/blog/kubectx/" target="_blank" rel="noreferrer"&gt;https://ahmet.im/blog/kubectx/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;kubectx can show you the multiple contexts available, switch between them quickly, and create aliases.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1367"
 height="472"
 src="/images/kubectx-demo_hu_7cbb5f1c9427d25a.gif"
 srcset="/images/kubectx-demo_hu_7cbb5f1c9427d25a.gif 800w,/images/kubectx-demo_hu_b7e7f56ae4304651.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/kubectx-demo.gif"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;kubens can show you the multiple namespaces available, switch between them quickly, and create aliases.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1381"
 height="568"
 src="/images/kubens-demo_hu_4877f79d07a82004.gif"
 srcset="/images/kubens-demo_hu_4877f79d07a82004.gif 800w,/images/kubens-demo_hu_3ecc09123c1cbc35.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/kubens-demo.gif"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;fzf
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;fzf is a different type of tool. This makes your other tools better. It works on a variety of platforms but what is allows you to do is get an interactive filter. kubectx and kubens will show the list and let you pick what you want instead of runnig multiple commands.
You can leverage fzf to pick files to edit more quickly.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="942"
 height="540"
 src="/images/fzf_hu_e58f91699d482bd9.gif"
 srcset="/images/fzf_hu_e58f91699d482bd9.gif 800w,/images/fzf_hu_c2a41206848fef90.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/fzf.gif"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;It works with lots of things, like vi.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="736"
 height="332"
 src="/images/fzf-vi_hu_890b6e7a1e362555.gif"
 srcset="/images/fzf-vi_hu_890b6e7a1e362555.gif 800w,/images/fzf-vi_hu_980573427c54271c.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/fzf-vi.gif"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;If you work daily with Kubernetes you might already know about these tools. If you haven&amp;rsquo;t tried them, give them an install and play around.&lt;/p&gt;</content:encoded></item><item><title>Presenting Best Practices - Part 1</title><link>https://chris-ayers.com/posts/presenting-best-practices-part-1/</link><pubDate>Sun, 02 Jan 2022 00:00:00 +0000</pubDate><guid>https://chris-ayers.com/posts/presenting-best-practices-part-1/</guid><description>&lt;!-- textlint-disable terminology --&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="3000"
 height="2000"
 src="/images/51382972555_0ceb282aee_o_hu_666148da832244d4.jpg"
 srcset="/images/51382972555_0ceb282aee_o_hu_666148da832244d4.jpg 800w,/images/51382972555_0ceb282aee_o_hu_c75901511c63da78.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/51382972555_0ceb282aee_o.jpg"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Presenting and speaking are skills that require practice to hone. I was a consultant for many years presenting to clients and customers of all levels and sizes. In addition, I started speaking and presenting at meetups, user groups, and conferences. Over the years, I practiced, I read, and I gave a lot of presentations. I&amp;rsquo;d like to share some of the learnings and best practices I&amp;rsquo;ve found in that time. I plan multiple posts, starting with Preparation. I&amp;rsquo;ll have more on slide design, and presentation tips.&lt;/p&gt;</description><content:encoded>&lt;!-- textlint-disable terminology --&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="3000"
 height="2000"
 src="/images/51382972555_0ceb282aee_o_hu_666148da832244d4.jpg"
 srcset="/images/51382972555_0ceb282aee_o_hu_666148da832244d4.jpg 800w,/images/51382972555_0ceb282aee_o_hu_c75901511c63da78.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/51382972555_0ceb282aee_o.jpg"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Presenting and speaking are skills that require practice to hone. I was a consultant for many years presenting to clients and customers of all levels and sizes. In addition, I started speaking and presenting at meetups, user groups, and conferences. Over the years, I practiced, I read, and I gave a lot of presentations. I&amp;rsquo;d like to share some of the learnings and best practices I&amp;rsquo;ve found in that time. I plan multiple posts, starting with Preparation. I&amp;rsquo;ll have more on slide design, and presentation tips.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Preparation
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;If you are building a presentation for a conference or work, it is tempting to just dive in and put everything on a few slides. But you need to prepare by understanding the audience and the message. Presentations should &lt;em&gt;&lt;strong&gt;support&lt;/strong&gt;&lt;/em&gt; your communication, not &lt;em&gt;&lt;strong&gt;BE&lt;/strong&gt;&lt;/em&gt; your communication. It is important to know not only WHAT you want to say, but WHO you are saying it to. It all starts with knowing your audience.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Know Your Audience
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Who are you presenting to: Engineers, Sales, Managers, C-Suites (CEO, CIO, CTO, CFO)? Each group has a set of unique needs and wants. What questions or areas are they going to be interested in? If it is a diverse audience, you will still need to be able to communicate with all of them. &lt;strong&gt;You should always keep your audience in mind when building content and presenting.&lt;/strong&gt;&lt;/p&gt;

&lt;h4 class="relative group"&gt;Why are they here?
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Why are they here? What do &lt;em&gt;they&lt;/em&gt; think they&amp;rsquo;re going to get out of this presentation? An engineer might be here for a technical presentation, a salesperson might be here to learn features to sell a product, a manager might be here to help a team stay on track, the C-Suite might be here to understand costs or timelines. Showing value early keeps people engaged. &lt;strong&gt;One of the first things you should do is tell your audience what&amp;rsquo;s in it for them.&lt;/strong&gt;&lt;/p&gt;

&lt;h4 class="relative group"&gt;Familiarity with the Content
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;How familiar are they with the content? Are these team members you have been collaborating with? Are they new to the topic and here for updates? The level of familiarity will determine the use of terms, acronyms, and jargon. Using jargon with a general audience can cause confusion and create a barrier to communication. If you use jargon, it can be helpful to define it. &lt;strong&gt;If people are having difficulty following the terms and jargon, they will lose interest and stop listening.&lt;/strong&gt;&lt;/p&gt;

&lt;h4 class="relative group"&gt;What is the value?
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;What is the value? Is this a collaboration meeting with Engineers working on an issue or problem? A status report to management on the progress of implementation? &lt;strong&gt;What are the attendees looking to get from this presentation?&lt;/strong&gt; The core message might be the same for different audiences, but how you present that message and the value the audience gets from it might be different.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Know your message
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;So, you know who you are presenting to and a high-level idea of what you are trying to convey. Now you need to know your message in depth. What are the points you are trying to get across?&lt;/p&gt;

&lt;h4 class="relative group"&gt;Intent
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;What is the intent of the presentation? Is it to inform, to persuade, or to instruct the audience? Each of these types of presentations has a different flow, style, and outcome.&lt;/p&gt;

&lt;h5 class="relative group"&gt;Informational
 
 
 
 
&lt;/h5&gt;
&lt;p&gt;For instance, an informational presentation aims to inform the audience about a specific topic. Think of it as a written report presented verbally. In business, these are the most common types of presentations. The goal is to share information, with an emphasis on clear and concise communication. &lt;strong&gt;You should limit your presentation to 3-4 key messages.&lt;/strong&gt; Any more than that can be too much information and overload the audience.&lt;/p&gt;

&lt;h5 class="relative group"&gt;Persuasive
 
 
 
 
&lt;/h5&gt;
&lt;p&gt;A persuasive speech aims to move, motivate, or change the audience. You want the audience to perform a certain action or convince them to adopt the belief or opinion of the speaker. This might be a sales pitch, a recommendation for an architectural approach, or adopting a new practice. Persuasion has been studied by experts and they have observed what works and what doesn&amp;rsquo;t.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cFdCzN7RYbw?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;


&lt;h5 class="relative group"&gt;Instructional
 
 
 
 
&lt;/h5&gt;
&lt;p&gt;If it is an instructional presentation, how much detail do you need to put into the presentation? A fun example is this great YouTube video, &lt;a href="https://youtu.be/cDA3_5982h8" target="_blank" rel="noreferrer"&gt;Exact Instructions Challenge - THIS is why my kids hate me. Josh Darnit&lt;/a&gt;, where a father is trying to follow the exact instructions for how to make a peanut butter and jelly sandwich. &lt;strong&gt;Finding the right level of detail in the instructions is important and knowing your audience can help.&lt;/strong&gt;&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/cDA3_5982h8?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;


&lt;h4 class="relative group"&gt;Level of Content
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;What is the level of content? This can be difficult because it is hard to know what others know. Let&amp;rsquo;s say you are presenting to beginner engineers. &lt;a href="https://hbr.org/2006/12/the-curse-of-knowledge" target="_blank" rel="noreferrer"&gt;The Curse of Knowledge&lt;/a&gt; means that you might struggle, because you intuitively assume that things that are obvious to you are also obvious to the engineers. Finding that right balance is important.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I realize this post was light in specifics, but knowing your audience is the #1 best practice for presenting. If you know the audience, the next recommendation is knowing your goal or intent of the presentation. These might seem like basic recommendations, but they can turn an average presenter into a great presenter.&lt;/p&gt;
&lt;p&gt;Until Next Time.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Recommendations
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;I would like to give a few recommendations that will go deeper into many of these topics. These books are great additions for thinking about not only content, but how to present it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docsfordevelopers.com/" target="_blank" rel="noreferrer"&gt;Docs for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.duarte.com/books/slideology/" target="_blank" rel="noreferrer"&gt;slide:ology: The Art and Science of Creating Great Presentations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.presentationzen.com/" target="_blank" rel="noreferrer"&gt;Presentation Zen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottberkun.com/the-books/confessions-of-a-public-speaker/" target="_blank" rel="noreferrer"&gt;Confessions of a Public Speaker by Scott Berkun&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Time to follow my Dream - I'm joining Microsoft!!!!</title><link>https://chris-ayers.com/posts/time-to-follow-my-dream-im-joining-microsoft/</link><pubDate>Sun, 10 Oct 2021 11:02:22 +0000</pubDate><guid>https://chris-ayers.com/posts/time-to-follow-my-dream-im-joining-microsoft/</guid><description>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="761"
 height="183"
 src="/images/logos/mslogo_hu_bc180cb4dea2b2c.png"
 srcset="/images/logos/mslogo_hu_bc180cb4dea2b2c.png 800w,/images/logos/mslogo_hu_994b0971d33b4765.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/mslogo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m so excited to announce that I&amp;rsquo;m joining Microsoft! I&amp;rsquo;ll be joining the Fast Track for Azure - Apps team as a Senior Customer Engineer. My first day will be October 25th, the 20th anniversary of the release of Windows XP. It&amp;rsquo;s been quite a journey to get here.&lt;/p&gt;</description><content:encoded>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="761"
 height="183"
 src="/images/logos/mslogo_hu_bc180cb4dea2b2c.png"
 srcset="/images/logos/mslogo_hu_bc180cb4dea2b2c.png 800w,/images/logos/mslogo_hu_994b0971d33b4765.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/mslogo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m so excited to announce that I&amp;rsquo;m joining Microsoft! I&amp;rsquo;ll be joining the Fast Track for Azure - Apps team as a Senior Customer Engineer. My first day will be October 25th, the 20th anniversary of the release of Windows XP. It&amp;rsquo;s been quite a journey to get here.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been enthralled with technology since my parents brought home our first computer in the mid 1980&amp;rsquo;s. I&amp;rsquo;ve had lots of different roles over the years, though it feels like the last 5-6 years really started me on the trajectory to Microsoft.&lt;/p&gt;
&lt;p&gt;The change for me started with joining a consulting firm. I got more exposure to different solutions and technologies. I started attending MeetUps, User Groups, and conferences. I started speaking and blogging. I really found a passion for sharing with others.&lt;/p&gt;
&lt;p&gt;While on a long-term project, I met Esteban Garcia, someone I now consider a mentor. He was helping setup our DevOps pipelines, and I was impressed by how he helped everyone. After a few years I joined him at Nebbia Technology, his small 12 person consulting company focused on DevOps and Azure. Esteban had a phenomenal team with an amazing culture that I was able to join and learn from.&lt;/p&gt;
&lt;p&gt;Nebbia Technology was acquired by New Signature, a 500 employee consulting company focused on the Microsoft ecosystem. Esteban continued to lead our team as it grew into the Intelligent Cloud (Azure, DevOps, App Mod) team of New Signature. As our team grew, I talked with Esteban about growing as a leader. He helped motivate me, guide me, and offered feedback on how to be a better consultant and team member. I became our DevOps Capability Lead, doing pre-sales, building technical offerings and standards.&lt;/p&gt;
&lt;p&gt;New Signature was acquired by Cognizant in 2020, along with 10th Magnitude (another consulting firm), forming the new Cognizant Microsoft Business Group. As Esteban moved to another role in New Signature, they needed a replacement to lead the team. This was a time of amazing growth for me. With Esteban&amp;rsquo;s help, I transitioned into the Regional Lead for Intelligent Cloud.&lt;/p&gt;
&lt;p&gt;The last year has been a huge change for me. I broadened my knowledge in a lot of areas, and got a lot better at presenting. I focused on being a good leader for my team. Some of the team came with me from Nebbia, some from New Signature, and some I hired, but we came together. Culture has always been the key for me: caring for family, transparency, honesty, seeing beyond the work, and growth. I&amp;rsquo;ve made sure the team has opportunities to learn, grow, and pursue their dreams. I consider all of the friends.&lt;/p&gt;
&lt;p&gt;I will be available to mentor them even as I follow my dream. This role takes me back into delivery. I get to help customers as they adopt azure, educating them, and gathering their feedback. I get to work with the Azure product teams. I also get to keep blogging, creating documentation, and samples that can help everyone. All the things I love to do, it feels like such a good fit for me.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;M. GOING. TO. MICROSOFT. I can&amp;rsquo;t stop smiling.&lt;/p&gt;
&lt;p&gt;And it wouldn&amp;rsquo;t be possible without my family, my friends, my mentor, and my team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thank you all.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>Containers and VMs: What is the difference?</title><link>https://chris-ayers.com/posts/containers-and-vms-what-is-the-difference/</link><pubDate>Mon, 16 Aug 2021 12:01:41 +0000</pubDate><guid>https://chris-ayers.com/posts/containers-and-vms-what-is-the-difference/</guid><description>&lt;p&gt;Containers are a very big topic right now, but they also cause a lot of confusion for people. Before we discuss containers, containerization, and container orchestration; we should address the question of how containers differ from virtual machines (VMs).&lt;/p&gt;
&lt;p&gt;Both are built on the concept of Virtualization. Virtualization is the process of creating a virtual computing environment as opposed to a physical environment. Both technologies have their uses, and even today many solutions leverage both VMs and containers, sometimes leveraging VMs to host containers.&lt;/p&gt;</description><content:encoded>&lt;p&gt;Containers are a very big topic right now, but they also cause a lot of confusion for people. Before we discuss containers, containerization, and container orchestration; we should address the question of how containers differ from virtual machines (VMs).&lt;/p&gt;
&lt;p&gt;Both are built on the concept of Virtualization. Virtualization is the process of creating a virtual computing environment as opposed to a physical environment. Both technologies have their uses, and even today many solutions leverage both VMs and containers, sometimes leveraging VMs to host containers.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What are VMs?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;A virtual machine (VM) is virtual infrastructure with its own virtual CPU, memory, network interface (NIC), and storage. A host machine runs VMs. The VMs that run on the host are called guests. The resources of the host are managed by a hypervisor.&lt;/p&gt;
&lt;p&gt;The hypervisor is software that creates and runs VMs. The hypervisor gives each virtual machine the resources that have been allocated and manages the scheduling of VM resources against the physical resources. VMs are isolated from the rest of the system, and multiple VMs can exist on a single host. Because they are isolated, VMs can run different operating systems like Linux or Windows.&lt;/p&gt;
&lt;p&gt;Workloads or applications running on a VM contain the entire operating system (Linux, Windows, &amp;hellip;) as well as all the services, dependencies, and libraries needed to run and administer applications or workloads. Because VM images contain the entire operating system, they can range in size from hundred of megabytes up to several gigabytes. Starting a VM or application can also take several seconds to minutes&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1538"
 height="825"
 src="/images/containers-vms_hu_5aaab1181decb351.png"
 srcset="/images/containers-vms_hu_5aaab1181decb351.png 800w,/images/containers-vms_hu_88cd38ba19284ace.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/containers-vms.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Advantages&lt;/th&gt;
 &lt;th&gt;Disadvantages&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;VMs support diverse OS requirements for multiple applications on a single infrastructure&lt;/td&gt;
 &lt;td&gt;VM image size results in longer backup or migration durations between platforms&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VMs replicate comprehensive computing environments, easing portability and migration between on-premises and cloud platforms&lt;/td&gt;
 &lt;td&gt;Duplicate copies of files (OS or libraries) are common among multiple VMs on a system&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;VMs provide superior isolation and security across systems&lt;/td&gt;
 &lt;td&gt;Limited VM support on a physical server compared to containers, due to full server encapsulation&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;A robust VM ecosystem and marketplace exists, featuring industry leaders&lt;/td&gt;
 &lt;td&gt;VM start-up times can be lengthy, as the OS and kernel need to fully initialize&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 class="relative group"&gt;What are Containers?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Containers are lightweight, isolated, packages of software. The containers bundle libraries, configuration, scripts, and application binaries. There is a standard for containers images, The Open Container Initiative - OCI, for allowing interoperability of different container engines. Containers run on top of an OS and a Container Engine, like Docker, CRI-O, or LXD. The Container Engine pulls images from a container registry and runs applications.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1538"
 height="825"
 src="/images/containers-containers_hu_e13bf408e2922609.png"
 srcset="/images/containers-containers_hu_e13bf408e2922609.png 800w,/images/containers-containers_hu_31184ca86114d82b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/containers-containers.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Advantages&lt;/th&gt;
 &lt;th&gt;Disadvantages&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Containers are lightweight with images in megabytes, compared to VMs in gigabytes&lt;/td&gt;
 &lt;td&gt;Steeper learning curve for containers&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Containers offer high portability across on-premises and cloud environments&lt;/td&gt;
 &lt;td&gt;Containers require a uniform OS, limiting OS diversity or version mixing&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Reduced IT resources needed for container deployment, operation, and management&lt;/td&gt;
 &lt;td&gt;Potential security concerns due to shared OS in containers compared to VMs&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Rapid container start-up times in seconds&lt;/td&gt;
 &lt;td&gt;Containers are a newer technology with an evolving ecosystem&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 class="relative group"&gt;Next Steps
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;While VMs emulate entire machines, Containers are great for packaging applications and their dependencies. Next time we&amp;rsquo;ll look at the parts of a container image, how they are defined, and how they are run.&lt;/p&gt;</content:encoded></item><item><title>Scripting Winget</title><link>https://chris-ayers.com/posts/scripting-winget/</link><pubDate>Sun, 01 Aug 2021 10:21:12 +0000</pubDate><guid>https://chris-ayers.com/posts/scripting-winget/</guid><description>&lt;p&gt;When I reset my PC or setup a new test machine, I always have to download a lot of software. In the past, I&amp;rsquo;ve used Chocolatey, Boxstarter, or just installed everything by hand.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve played with winget, as part of the Windows Insider program. It was first announced in 2020 but was highlighted during Build 2021. With the release of Windows 11, I&amp;rsquo;ve setup machines a few times and wanted to automate the process using the new &lt;code&gt;winget&lt;/code&gt; command.&lt;/p&gt;</description><content:encoded>&lt;p&gt;When I reset my PC or setup a new test machine, I always have to download a lot of software. In the past, I&amp;rsquo;ve used Chocolatey, Boxstarter, or just installed everything by hand.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve played with winget, as part of the Windows Insider program. It was first announced in 2020 but was highlighted during Build 2021. With the release of Windows 11, I&amp;rsquo;ve setup machines a few times and wanted to automate the process using the new &lt;code&gt;winget&lt;/code&gt; command.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What is Winget?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;WinGet is the command-line tool for the Windows Package Manager, found &lt;a href="https://github.com/microsoft/winget-cli" target="_blank" rel="noreferrer"&gt;on GitHub&lt;/a&gt;. For a while, the only way to install it was to be a Windows Insider. I&amp;rsquo;ve also been able to use &lt;code&gt;winget&lt;/code&gt; after fully updating a Windows 10 machine. You can also install it directly from GitHub.&lt;/p&gt;

&lt;h3 class="relative group"&gt;What can Winget do?
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Winget allows you to easily search for and install software. You can upgrade and remove software as well.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="700"
 height="662"
 src="/images/winget_hu_71906886d7b5858.png"
 srcset="/images/winget_hu_71906886d7b5858.png 800w,/images/winget_hu_ce2ee921c93fb4e3.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/winget.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h4 class="relative group"&gt;Searching for Packages
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Let&amp;rsquo;s say i&amp;rsquo;m not sure what packages there are. I can run &lt;code&gt;winget search&lt;/code&gt; and get a full list of available packages, or I can filter that by searching for a package name or partial name.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1168"
 height="453"
 src="/images/winget-search-1_hu_499e25e90b92aa77.png"
 srcset="/images/winget-search-1_hu_499e25e90b92aa77.png 800w,/images/winget-search-1_hu_19ed3b422661e2d4.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/winget-search-1.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h4 class="relative group"&gt;Showing Package information
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;You can then show more details about a package, including publisher, URL, and hash.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="700"
 height="415"
 src="/images/winget-show_hu_942a58ccd86eb8be.png"
 srcset="/images/winget-show_hu_942a58ccd86eb8be.png 800w,/images/winget-show_hu_c5db4dbc2e0d5ae5.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/winget-show.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h4 class="relative group"&gt;Installing, Uninstalling and Updating Packages
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Installing packages is very easy, typically I just &lt;code&gt;winget install X&lt;/code&gt;. This are options for using monikers (vscode) or full IDs (Microsoft.VSCode) as well.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="628"
 height="453"
 src="/images/winget-install_hu_95d66788940f7c4b.png"
 srcset="/images/winget-install_hu_95d66788940f7c4b.png 800w,/images/winget-install_hu_d12b3a4b44f2f461.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/winget-install.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;For some apps there might be popups or gui installers. I usually use &lt;code&gt;winget install --silent X&lt;/code&gt; when installing multiple apps. It prevents the popups on most installers.&lt;/p&gt;
&lt;p&gt;Similarly, upgrading and uninstalling packages is done through the &lt;code&gt;winget upgrade X&lt;/code&gt; and &lt;code&gt;winget uninstall X&lt;/code&gt; commands with similar options to the install command.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Older Method
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;I originally started writing this a few months ago, before its 1.0 release. Since that time things have gotten much easier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you update Windows 10 (1807 or higher) with all the normal patches, and you update the store Apps (including App Installer), you get winget&lt;/li&gt;
&lt;li&gt;winget added an import/export set of commands, allowing you to quickly install a series of apps on a second machine (or the same machine) and it will detect those already installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please keep reading though if you find scripting and automation interesting. The script I walk through below can be found at &lt;a href="https://gist.github.com/Codebytes/29bf18015f6e93fca9421df73c6e512c" target="_blank" rel="noreferrer"&gt;Gist: Codebytes/DevMachineSetup.ps1&lt;/a&gt;.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Scripting the install
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;While scripting the install, I ran into a few issues. For older versions of Windows, the latest versions from GitHub would not install due to missing dependencies. I also had to install the &lt;a href="https://docs.microsoft.com/en-us/troubleshoot/cpp/c-runtime-packages-desktop-bridge#how-to-install-and-update-desktop-framework-packages" target="_blank" rel="noreferrer"&gt;VC++ v14 Desktop Framework Package&lt;/a&gt;. Both of those steps are handled by the setup script below.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#Install WinGet&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#Based on this gist: https://gist.github.com/crutkas/6c2096eae387e544bd05cde246f23901&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$hasPackageManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Get-AppPackage&lt;/span&gt; &lt;span class="n"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Microsoft.DesktopAppInstaller&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nv"&gt;$hasPackageManager&lt;/span&gt; &lt;span class="o"&gt;-or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$hasPackageManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;Version&lt;/span&gt; &lt;span class="o"&gt;-lt&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;version&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.10.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Installing winget Dependencies&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Add-AppxPackage&lt;/span&gt; &lt;span class="n"&gt;-Path&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$releases_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://api.github.com/repos/microsoft/winget-cli/releases/latest&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Net.ServicePointManager&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;SecurityProtocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Net.SecurityProtocolType&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Tls12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$releases&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Invoke-RestMethod&lt;/span&gt; &lt;span class="n"&gt;-uri&lt;/span&gt; &lt;span class="nv"&gt;$releases_url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$latestRelease&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$releases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;assets&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Where &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;browser_download_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;EndsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;msixbundle&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Select &lt;/span&gt;&lt;span class="n"&gt;-First&lt;/span&gt; &lt;span class="mf"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Installing winget from &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$latestRelease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;browser_download_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Add-AppxPackage&lt;/span&gt; &lt;span class="n"&gt;-Path&lt;/span&gt; &lt;span class="nv"&gt;$latestRelease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;browser_download_url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;winget already installed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After getting it installed, I added some configuration to support the windows store. This is an experimental setting documented on GitHub: &lt;a href="https://github.com/microsoft/winget-cli/blob/master/doc/Settings.md" target="_blank" rel="noreferrer"&gt;https://github.com/microsoft/winget-cli/blob/master/doc/Settings.md&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#Configure WinGet&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Configuring winget&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#winget config path from: https://github.com/microsoft/winget-cli/blob/master/doc/Settings.md#file-location&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$settingsPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$env:LOCALAPPDATA&lt;/span&gt;&lt;span class="s2"&gt;\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$settingsJson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sh"&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; // For documentation on these settings, see: https://aka.ms/winget-settings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; &amp;#34;experimentalFeatures&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; &amp;#34;experimentalMSStore&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; &amp;#34;@;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sh"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$settingsJson&lt;/span&gt;&lt;span class="sh"&gt; | Out-File &lt;/span&gt;&lt;span class="nv"&gt;$settingsPath&lt;/span&gt;&lt;span class="sh"&gt; -Encoding utf8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Finally I built up a list of packages to install. I&amp;rsquo;m using the &lt;code&gt;winget&lt;/code&gt; command to install the packages. I originally had a simple list of packages, but added the optional source parameter to support installing from the Windows Store. Winget also doesn&amp;rsquo;t short circuit the install if the package is already installed, so I added a check for that. Winget today doesn&amp;rsquo;t output PowerShell objects or easily parseable output, so I just joined all the lines into 1 string and checked the contents.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#Install New apps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$apps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.AzureCLI&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.PowerShell&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.VisualStudioCode&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.WindowsTerminal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;msstore&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.AzureStorageExplorer&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.PowerToys&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Git.Git&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Docker.DockerDesktop&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.dotnet&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vm"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GitHub.cli&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;Foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$apps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#check if the app is already installed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$listApp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;winget&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="o"&gt;-exact&lt;/span&gt; &lt;span class="n"&gt;-q&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(![&lt;/span&gt;&lt;span class="no"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$listApp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="py"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Write-host&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Installing:&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;source&lt;/span&gt; &lt;span class="o"&gt;-ne&lt;/span&gt; &lt;span class="vm"&gt;$null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;winget&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="o"&gt;-exact&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-silent&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-source&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;source&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-accept-package-agreements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;winget&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="o"&gt;-exact&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-silent&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;-accept-package-agreements&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Write-host&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Skipping Install of &amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="py"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Removing Packages
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;We can also remove unused windows applications that are installed by default. I combined some of the script found here as well: &lt;a href="https://www.cloudappie.nl/uninstall-windows-store-apps-powershell/" target="_blank" rel="noreferrer"&gt;Uninstalling windows store apps using PowerShell&lt;/a&gt;. I don&amp;rsquo;t usually use the 3D printing apps, or the mixed reality stuff so they can go. And I don&amp;rsquo;t care about the solitaire collection.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;#Remove Apps&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Removing Apps&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$apps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;*3DPrint*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Microsoft.MixedReality.Portal&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;Foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$apps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Write-host&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Uninstalling:&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Get-AppxPackage&lt;/span&gt; &lt;span class="n"&gt;-allusers&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Remove-AppxPackage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Running the script
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The script can be found at &lt;a href="https://gist.github.com/Codebytes/29bf18015f6e93fca9421df73c6e512c" target="_blank" rel="noreferrer"&gt;Gist: Codebytes/DevMachineSetup.ps1&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I learned how to run PowerShell from a gist from this great article: &lt;a href="https://code.adonline.id.au/run-a-powershell-script-directly-from-github-gist/" target="_blank" rel="noreferrer"&gt;Run a PowerShell script directly from GitHub Gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can get and run the entire script on a new machine by invoking the following command.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;PowerShell&lt;/span&gt; &lt;span class="n"&gt;-NoProfile&lt;/span&gt; &lt;span class="n"&gt;-ExecutionPolicy&lt;/span&gt; &lt;span class="n"&gt;Bypass&lt;/span&gt; &lt;span class="n"&gt;-Command&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;iex ((New-Object System.Net.WebClient).DownloadString(&amp;#39;https://gist.githubusercontent.com/Codebytes/29bf18015f6e93fca9421df73c6e512c/raw/&amp;#39;))&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote&gt;&lt;p&gt;Note: After running this, you might need to restart your terminal or reboot the computer.&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded></item><item><title>Clearing NuGet Caches</title><link>https://chris-ayers.com/posts/clearing-nuget-caches/</link><pubDate>Mon, 05 Jul 2021 20:29:11 +0000</pubDate><guid>https://chris-ayers.com/posts/clearing-nuget-caches/</guid><description>&lt;h2 class="relative group"&gt;What is NuGet?
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="444"
 height="135"
 src="/images/logos/nuget-logo_hu_17a44a0e1ed91fdb.png"
 srcset="/images/logos/nuget-logo_hu_17a44a0e1ed91fdb.png 800w,/images/logos/nuget-logo_hu_25ac8baef05b95b7.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/nuget-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;NuGet is an essential packaging tool used in the .NET ecosystem. NuGet is how packages for .NET are created, hosted, and consumed, along with the tools for each of those roles. For many, NuGet is used through Visual Studio to install and manage packages.&lt;br&gt;
The dotnet CLI also provides functionality for adding packages, updating packages, and creating packages.&lt;/p&gt;</description><content:encoded>
&lt;h2 class="relative group"&gt;What is NuGet?
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="444"
 height="135"
 src="/images/logos/nuget-logo_hu_17a44a0e1ed91fdb.png"
 srcset="/images/logos/nuget-logo_hu_17a44a0e1ed91fdb.png 800w,/images/logos/nuget-logo_hu_25ac8baef05b95b7.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/nuget-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;NuGet is an essential packaging tool used in the .NET ecosystem. NuGet is how packages for .NET are created, hosted, and consumed, along with the tools for each of those roles. For many, NuGet is used through Visual Studio to install and manage packages.&lt;br&gt;
The dotnet CLI also provides functionality for adding packages, updating packages, and creating packages.&lt;/p&gt;
&lt;p&gt;NuGet has changed over the years, originally downloading packages into a project folder where they could be checked in. Today, we don&amp;rsquo;t typically check packages into our source control. This is where caching comes in.&lt;/p&gt;

&lt;h2 class="relative group"&gt;NuGet Caches
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;NuGet manages several folders outside of your project structure when installing, updating, or restoring packages. The locations of the folders vary by platform but the use case is the same. Microsoft has a detailed document on the caches: &lt;a href="https://docs.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders" target="_blank" rel="noreferrer"&gt;Managing the Global Packages and Cache Folders&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Name&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;global‑packages&lt;/td&gt;
 &lt;td&gt;This is where NuGet installs any downloaded package.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;http‑cache&lt;/td&gt;
 &lt;td&gt;The Visual Studio Package Manager (NuGet 3.x+) and the dotnet tool store copies of downloaded packages in this cache (saved as .dat files), organized into subfolders for each package source.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;temp&lt;/td&gt;
 &lt;td&gt;A folder where NuGet stores temporary files during its various operations.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;plugins-cache&lt;/td&gt;
 &lt;td&gt;A folder where NuGet stores the results from the operation claims request.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Viewing the cache locations can be done with the dotnet CLI or the nuget cli:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Display locals for all folders: global-packages, http cache, temp and plugins cache&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals all --list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals all -list&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cmd" data-lang="cmd"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http-cache: C:\Users\user1\AppData\Local\NuGet\v3-cache
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;global-packages: C:\Users\user1\.nuget\packages\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;temp: C:\Users\user1\AppData\Local\Temp\NuGetScratch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;plugins-cache: C:\Users\user1\AppData\Local\NuGet\plugins-cache&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Why do we want to Clear the Caches?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;There are a few reasons you might want to clear caches.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maybe you&amp;rsquo;re seeing strange behavior in your builds. I&amp;rsquo;ve cleared all my caches to make sure I&amp;rsquo;m downloading the same versions as the build agent&lt;/li&gt;
&lt;li&gt;Upgrading versions of .NET. I&amp;rsquo;ve had errors (and warnings) from restoring packages into a new version of .NET that were restored in previous versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Clearing Caches
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;To clear the caches, you can pick the method that works best for you.&lt;/p&gt;
&lt;p&gt;In Visual Studio 2017 and beyond, there is a button in the NuGet section of the options dialog (Tools-&amp;gt;Options):&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="744"
 height="434"
 src="/images/visual-studio-options_hu_2017f1414f5e2e8e.png"
 srcset="/images/visual-studio-options_hu_2017f1414f5e2e8e.png 800w,/images/visual-studio-options_hu_fe9d3c23ce38f974.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/visual-studio-options.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Using the dotnet or nuget CLI, you can clear specific caches or all caches. The commands are:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Clear the 3.x+ cache (use either command)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals http-cache --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals http-cache -clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Clear the 2.x cache (NuGet CLI 3.5 and earlier only)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals packages-cache -clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Clear the global packages folder (use either command)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals global-packages --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals global-packages -clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Clear the temporary cache (use either command)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals temp --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals temp -clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Clear the plugins cache (use either command)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals plugins-cache --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals plugins-cache -clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Clear all caches (use either command)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dotnet nuget locals all --clear
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nuget locals all -clear&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="736"
 height="250"
 src="/images/command-output_hu_3caf6bf602bafc80.png"
 srcset="/images/command-output_hu_3caf6bf602bafc80.png 800w,/images/command-output_hu_a586ef4e77e45d1c.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/command-output.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h3 class="relative group"&gt;Installing NuGet.exe
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;NuGet.exe is not installed by default on Windows. Visual Studio 2017 and above include nuget package manager functionality via the NuGet Package Manager (GUI and console), but not nuget.exe.&lt;/p&gt;
&lt;p&gt;If you install newer versions of the DotNet CLI, it does include &lt;code&gt;dotnet nuget&lt;/code&gt; funcationality but not nuget.exe.&lt;/p&gt;
&lt;p&gt;The NuGet.exe doesn&amp;rsquo;t have an installer, and the instructions on the site usually involve downloading the exe and adding it to your path.&lt;/p&gt;
&lt;p&gt;There are other ways to install NuGet.exe.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;winget install Microsoft.NuGet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;choco install nuget.commandline&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve got a script that downloads it and adds it to your path, you can find it here: &lt;a href="https://gist.github.com/Codebytes/1ae354e736c88adef5b6f802597e3101" target="_blank" rel="noreferrer"&gt;Nuget-Install.ps1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In conclusion, understanding and managing NuGet caches is essential for maintaining a healthy development environment, especially when dealing with build issues or upgrading .NET versions. Clearing caches can help resolve strange behavior in your builds and ensure consistency with build agents. With the methods outlined in this post, you can easily clear specific or all caches using Visual Studio, the dotnet CLI, or the nuget CLI. By mastering these cache management techniques, you&amp;rsquo;ll be well-equipped to tackle any cache-related challenges that come your way, ultimately contributing to a more efficient and reliable development process.&lt;/p&gt;</content:encoded></item><item><title>DevOps - The Stakeholders</title><link>https://chris-ayers.com/posts/devops-the-stakeholders/</link><pubDate>Sat, 05 Jun 2021 16:55:36 +0000</pubDate><guid>https://chris-ayers.com/posts/devops-the-stakeholders/</guid><description>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1183"
 height="946"
 src="/images/devops_hu_3f835c23508b0c7.png"
 srcset="/images/devops_hu_3f835c23508b0c7.png 800w,/images/devops_hu_29041d8ff8f6976b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/devops.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;When I talk about DevOps, I usually don&amp;rsquo;t focus on the things like pipelines or automation. While these topics and activities can be &lt;em&gt;part&lt;/em&gt; of DevOps, there is so much more to it. I&amp;rsquo;m sure this will be a multipart blog series so lets get started.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Definition
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s make sure we are all on the same page. The definition of DevOps that I love and subscribe to was coined by &lt;a href="https://www.donovanbrown.com/post/what-is-devops" target="_blank" rel="noreferrer"&gt;Donovan Brown&lt;/a&gt; at Microsoft.&lt;/p&gt;</description><content:encoded>
&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1183"
 height="946"
 src="/images/devops_hu_3f835c23508b0c7.png"
 srcset="/images/devops_hu_3f835c23508b0c7.png 800w,/images/devops_hu_29041d8ff8f6976b.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/devops.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;When I talk about DevOps, I usually don&amp;rsquo;t focus on the things like pipelines or automation. While these topics and activities can be &lt;em&gt;part&lt;/em&gt; of DevOps, there is so much more to it. I&amp;rsquo;m sure this will be a multipart blog series so lets get started.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Definition
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s make sure we are all on the same page. The definition of DevOps that I love and subscribe to was coined by &lt;a href="https://www.donovanbrown.com/post/what-is-devops" target="_blank" rel="noreferrer"&gt;Donovan Brown&lt;/a&gt; at Microsoft.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DevOps is the union of people, process, and products to enable continuous delivery of value to our end users.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This definition and the thought process behind it really drives how I was taught about DevOps and try to practice it today. DevOps is a culture or mindset that really should drive how value is delivered at an organization. DevOps should not be a Developer only or &amp;ldquo;DevOps&amp;rdquo; only activity, this mindset should permeate the organization. DevOps is not about pipelines, automation, or a job title.&lt;br&gt;
DevOps is about continuous delivery of value to our end users. End users don&amp;rsquo;t care about story points. End users don&amp;rsquo;t care about tests passing in QA. End users care about value, which could mean anything like features, stability, or performance.&lt;/p&gt;

&lt;h2 class="relative group"&gt;The Stakeholders
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;So where do we start? With Stakeholders. For culture and mindset shifts, buy in from all levels is important. This really is &lt;a href="https://chris-ayers.com/2020/02/27/shared-focus/" target="_blank" rel="noreferrer"&gt;&amp;ldquo;The First Way&amp;rdquo;&lt;/a&gt; from the Phoenix Project and Lean Methodology. We need to think of how the whole system (the organization) delivers value to our end users.&lt;/p&gt;
&lt;p&gt;Stakeholders collaborate and cooperate in the development, delivery, and maintenance of software and value With a DevOps mindset and culture. Stakeholders and leaders can influence and encourage the mindset needed for organizations to succeed. The Shared Goals need to be understood by everyone, with the end result clearly communicated. &lt;em&gt;Silos&lt;/em&gt; hurt the delivery of value by limiting communication and collaboration. Historically where there were silos, there are walls slowing things down.&lt;/p&gt;
&lt;p&gt;This type of collaboration usually requires change. Change takes time. Culture takes time. A piece of software or a job title won&amp;rsquo;t be a silver bullet. But after helping multiple organizations of all sizes embrace the DevOps culture and mindset, one of the key things to keep in mind is patience.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll be back to dive more into this topic.&lt;/p&gt;</content:encoded></item><item><title>Shared Focus - Using The First Way with DevOps</title><link>https://chris-ayers.com/posts/shared-focus/</link><pubDate>Thu, 27 Feb 2020 08:00:09 +0000</pubDate><guid>https://chris-ayers.com/posts/shared-focus/</guid><description>&lt;p&gt;A common issue I see when discussing DevOps with teams or organizations is the presence of Organizational Silos. Organizational Silos are made up of all types of people. Sometimes its a job type, like developers, qa, or infrastructure. Sometimes its a department, like accounting, or hr. Whatever the composition of these silos, they usually impact organizational performance and the ability to deliver value to end users.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1200"
 height="900"
 src="/images/silos_hu_5b1c52fdaf3f889b.jpg"
 srcset="/images/silos_hu_5b1c52fdaf3f889b.jpg 800w,/images/silos_hu_61898f8bdaab078f.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/silos.jpg"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This happens over time, with members of the silo identifying with each other, viewing those not in the silos as outsiders. Depending on the business, the silos can lose trust in the business overall and tighten ranks around their silo. The silos can turn into walled fortresses. When the silos get in the way, the silos are more focused on their own success than the success of the organization.&lt;/p&gt;</description><content:encoded>&lt;p&gt;A common issue I see when discussing DevOps with teams or organizations is the presence of Organizational Silos. Organizational Silos are made up of all types of people. Sometimes its a job type, like developers, qa, or infrastructure. Sometimes its a department, like accounting, or hr. Whatever the composition of these silos, they usually impact organizational performance and the ability to deliver value to end users.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1200"
 height="900"
 src="/images/silos_hu_5b1c52fdaf3f889b.jpg"
 srcset="/images/silos_hu_5b1c52fdaf3f889b.jpg 800w,/images/silos_hu_61898f8bdaab078f.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/silos.jpg"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;This happens over time, with members of the silo identifying with each other, viewing those not in the silos as outsiders. Depending on the business, the silos can lose trust in the business overall and tighten ranks around their silo. The silos can turn into walled fortresses. When the silos get in the way, the silos are more focused on their own success than the success of the organization.&lt;/p&gt;
&lt;p&gt;This can also happen with leaders. Leaders trying to turn their areas into fiefdoms. Leaders concerned about their power, responsibilities, or bragging rights. Either way, like the silos, situations like this can lead to groups concerned about their success, instead of the success of the organization.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Common Goals
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;To really succeed, to improve and to grow as an organization, everyone needs to share some common goals and shared focus across the organization. How do we bring value to users? Sure, Dept A can release X feature or function, but if it depends on Dept B to work, it brings no value. If developers can write lots of code, does that benefit the user if it isn&amp;rsquo;t tested? If that code doesn&amp;rsquo;t have resources to run on? If you&amp;rsquo;re writing code no-one wants? Or systems are built and configured but there&amp;rsquo;s nothing to run.&lt;/p&gt;
&lt;p&gt;When everyone is working towards common goals and have a shared focus, the whole is greater than the sum of the parts. I was introduced to some of these ideas as &amp;ldquo;The First Way&amp;rdquo;. I read about this in &lt;a href="https://itrevolution.com/book/the-phoenix-project/" target="_blank" rel="noreferrer"&gt;&amp;ldquo;The Phoenix Project: A Novel about IT, DevOps, and Helping Your Business Win&amp;rdquo; by Gene Kim&lt;/a&gt; and &amp;ldquo;&lt;a href="https://itrevolution.com/book/the-devops-handbook/" target="_blank" rel="noreferrer"&gt;The DevOps Handbook&lt;/a&gt;&amp;rdquo;. The ways were also in a blog by Gene Kim called &amp;ldquo;&lt;a href="https://itrevolution.com/the-three-ways-principles-underpinning-devops/" target="_blank" rel="noreferrer"&gt;The Three Ways&lt;/a&gt;&amp;rdquo;. If you haven&amp;rsquo;t read either of these books, please go read The Phoenix Project.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1709"
 height="2560"
 src="/images/books/the-phoenix-project_hu_236dc3892ecc27b.jpg"
 srcset="/images/books/the-phoenix-project_hu_236dc3892ecc27b.jpg 800w,/images/books/the-phoenix-project_hu_4e71e5db42eea6bf.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/books/the-phoenix-project.jpg"
 /&gt;
 
 
 &lt;/figure&gt;</content:encoded></item><item><title>How Do You Boil the Ocean?</title><link>https://chris-ayers.com/posts/how-do-you-boil-the-ocean/</link><pubDate>Wed, 26 Feb 2020 00:46:31 +0000</pubDate><guid>https://chris-ayers.com/posts/how-do-you-boil-the-ocean/</guid><description>&lt;p&gt;This is a phrase I end up using a lot while talking with clients. I used to use a different phrase about elephants but moved away from that language to be more respectful. Let&amp;rsquo;s start with what it means, at least in the context I use it, and why this phrase is so useful when talking about Devops.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="2048"
 height="1536"
 src="/images/sunrise_at_ocean_hu_e0b135622efb20f3.jpg"
 srcset="/images/sunrise_at_ocean_hu_e0b135622efb20f3.jpg 800w,/images/sunrise_at_ocean_hu_2134eb433a165cda.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/sunrise_at_ocean.jpg"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;The Meaning
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve seen and heard a few meanings and usages of the phrase, &amp;ldquo;Boil(ing) the Ocean&amp;rdquo;. I&amp;rsquo;ve heard many negative contexts like: to undertake an impossible task or project. I usually use it in this context, Here is a big task with lots to do, how do you get started and make progress?&lt;/p&gt;</description><content:encoded>&lt;p&gt;This is a phrase I end up using a lot while talking with clients. I used to use a different phrase about elephants but moved away from that language to be more respectful. Let&amp;rsquo;s start with what it means, at least in the context I use it, and why this phrase is so useful when talking about Devops.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="2048"
 height="1536"
 src="/images/sunrise_at_ocean_hu_e0b135622efb20f3.jpg"
 srcset="/images/sunrise_at_ocean_hu_e0b135622efb20f3.jpg 800w,/images/sunrise_at_ocean_hu_2134eb433a165cda.jpg 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/sunrise_at_ocean.jpg"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;The Meaning
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve seen and heard a few meanings and usages of the phrase, &amp;ldquo;Boil(ing) the Ocean&amp;rdquo;. I&amp;rsquo;ve heard many negative contexts like: to undertake an impossible task or project. I usually use it in this context, Here is a big task with lots to do, how do you get started and make progress?&lt;/p&gt;

&lt;h2 class="relative group"&gt;Applied to DevOps
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;So how, and why do I apply this strange phrase to DevOps? Because as I work with clients, educating them and enabling them, they start getting excited. Depending on where they are in their DevOps journey, they might have a bigger or smaller list of things they want help with. Maybe its Infrastructure as Code (IaC), and a pipeline to help release it consistently and repeatedly. Maybe they are using a centralized version control system and are interested in moving to git and picking a branching strategy to work more effectively. When they release those cool new features, do they have monitoring and observability in place to know if and how their users are using those features? I could go on for a while, there are so many things that can impact an organization&amp;rsquo;s software delivery performance. And like &amp;ldquo;boiling the ocean&amp;rdquo; it seems like that can be an impossible task.&lt;/p&gt;
&lt;p&gt;So how do you &amp;ldquo;boil the ocean&amp;rdquo;? One cup at a time. Iteratively make progress. Progress over perfection. Don&amp;rsquo;t &lt;em&gt;choose&lt;/em&gt; to do nothing because its &lt;strong&gt;not&lt;/strong&gt; perfect. Make an improvement, any improvement. Pay attention to the results, take that feedback and make another improvement. Just like a pot of water, the ocean won&amp;rsquo;t go from cold to boiling, it takes time.&lt;/p&gt;</content:encoded></item><item><title>Snake Oil DevOps - BEWARE!</title><link>https://chris-ayers.com/posts/snake-oil-devops-beware/</link><pubDate>Mon, 24 Feb 2020 20:06:49 +0000</pubDate><guid>https://chris-ayers.com/posts/snake-oil-devops-beware/</guid><description>&lt;p&gt;As a DevOps Consultant a lot of what I do is spent on People and Processes. If you remember the definition of DevOps that I love is from &lt;a href="http://donovanbrown.com/post/what-is-devops" target="_blank" rel="noreferrer"&gt;Donovan Brown&lt;/a&gt;, &amp;ldquo;DevOps is the union of &lt;strong&gt;People&lt;/strong&gt;, &lt;strong&gt;Processes&lt;/strong&gt;, and &lt;strong&gt;Products&lt;/strong&gt; to &lt;em&gt;continuously deliver &lt;strong&gt;value&lt;/strong&gt;&lt;/em&gt; to our end users&amp;rdquo;. I want to keep reiterating this, &lt;em&gt;continuously deliver value to our end users&lt;/em&gt;. I bring this up because my job as a DevOps Consultant is to delivering value to my end users. But not all of us do. My amazing coworker (&lt;a href="https://twitter.com/_s_hari" target="_blank" rel="noreferrer"&gt;@_s_hari&lt;/a&gt;) and I have discussions about this quite a bit. As far as I know, he coined the term and gave me his blessing to blog about it.&lt;/p&gt;</description><content:encoded>&lt;p&gt;As a DevOps Consultant a lot of what I do is spent on People and Processes. If you remember the definition of DevOps that I love is from &lt;a href="http://donovanbrown.com/post/what-is-devops" target="_blank" rel="noreferrer"&gt;Donovan Brown&lt;/a&gt;, &amp;ldquo;DevOps is the union of &lt;strong&gt;People&lt;/strong&gt;, &lt;strong&gt;Processes&lt;/strong&gt;, and &lt;strong&gt;Products&lt;/strong&gt; to &lt;em&gt;continuously deliver &lt;strong&gt;value&lt;/strong&gt;&lt;/em&gt; to our end users&amp;rdquo;. I want to keep reiterating this, &lt;em&gt;continuously deliver value to our end users&lt;/em&gt;. I bring this up because my job as a DevOps Consultant is to delivering value to my end users. But not all of us do. My amazing coworker (&lt;a href="https://twitter.com/_s_hari" target="_blank" rel="noreferrer"&gt;@_s_hari&lt;/a&gt;) and I have discussions about this quite a bit. As far as I know, he coined the term and gave me his blessing to blog about it.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="297"
 height="349"
 src="/images/devops-pie_hu_278e0c18166466a1.png"
 srcset="/images/devops-pie_hu_278e0c18166466a1.png 800w,/images/devops-pie_hu_98404f4940d17e8.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/devops-pie.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;A lot of what I do is focused on enablement. I don&amp;rsquo;t want to come in and setup 2 pipelines for a user, or just do board customizations on 3 board. I want to educate my end users. I want them to learn and apply it after I&amp;rsquo;m gone. I want to see the lightbulb go off, and the gears turning. I want them to continue on their journey and call me back at the next roadblock miles down the road, not the same one 5 feet away. I might focusing on training, or streamlining the workflow. Whatever is going to help them the most at that point in time.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="220"
 height="335"
 src="/images/snake-oil_hu_c5b8872eb48d8995.png"
 srcset="/images/snake-oil_hu_c5b8872eb48d8995.png 800w,/images/snake-oil_hu_8d800312fbd7e2fa.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/snake-oil.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Snake Oil&lt;/p&gt;
&lt;p&gt;But, like days of old, there are those trying to make a buck at any cost. We come across them or hear about them from customers time to time. People and Processes aren&amp;rsquo;t part of the discussion. Instead the discussion centers on the tool. Snake Oil, The secret sauce, the magic technology, the silver bullet, the tool that will solve all your problems.&lt;/p&gt;
&lt;p&gt;Have you ever heard any of theses statements/promises/lies?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you buy this list of tools, you&amp;rsquo;ll be/have/do DevOps.&lt;/li&gt;
&lt;li&gt;You can&amp;rsquo;t do DevOps without X tool.&lt;/li&gt;
&lt;li&gt;You need to do Containers to do DevOps&lt;/li&gt;
&lt;li&gt;Jira/Service Now/GitLab/Azure DevOps/GitHub/Excel/Post-its is the only way to track work&lt;/li&gt;
&lt;li&gt;If you buy this monitoring tool all your bugs will get caught&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice the lack of discussion about the company/business/team. What the business, what is important to the end users? How do you work? What are the processes to take a new idea, let the team work on it, and get that code into production? How do you know its running OK in production? Do you know if the end user uses your new feature?&lt;/p&gt;
&lt;p&gt;When someone just focuses on the tool, they don&amp;rsquo;t care about helping you to improve, your journey, or the actual outcome. They want to continuously deliver the value of your &lt;em&gt;wallet&lt;/em&gt; into theirs.&lt;/p&gt;</content:encoded></item><item><title>What is DevOps?</title><link>https://chris-ayers.com/posts/what-is-devops/</link><pubDate>Sun, 23 Feb 2020 18:41:35 +0000</pubDate><guid>https://chris-ayers.com/posts/what-is-devops/</guid><description>&lt;h2 class="relative group"&gt;What &lt;em&gt;Isn&amp;rsquo;t&lt;/em&gt; DevOps?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Before I define DevOps, let&amp;rsquo;s get started with what DevOps isn&amp;rsquo;t. DevOps isn&amp;rsquo;t just a title, or a guy, or a department. DevOps isn&amp;rsquo;t just automating everying, and isn&amp;rsquo;t just logging everything. DevOps isn&amp;rsquo;t dozens of alerts every day, and isn&amp;rsquo;t an on-call rotation. DevOps isn&amp;rsquo;t agile or small releases. DevOps is a mindset.&lt;/p&gt;</description><content:encoded>
&lt;h2 class="relative group"&gt;What &lt;em&gt;Isn&amp;rsquo;t&lt;/em&gt; DevOps?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Before I define DevOps, let&amp;rsquo;s get started with what DevOps isn&amp;rsquo;t. DevOps isn&amp;rsquo;t just a title, or a guy, or a department. DevOps isn&amp;rsquo;t just automating everying, and isn&amp;rsquo;t just logging everything. DevOps isn&amp;rsquo;t dozens of alerts every day, and isn&amp;rsquo;t an on-call rotation. DevOps isn&amp;rsquo;t agile or small releases. DevOps is a mindset.&lt;/p&gt;

&lt;h2 class="relative group"&gt;What &lt;em&gt;Is&lt;/em&gt; DevOps?
 
 
 
 
&lt;/h2&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1527"
 height="850"
 src="/images/whatisdevops_hu_7f90570f5ae3528a.png"
 srcset="/images/whatisdevops_hu_7f90570f5ae3528a.png 800w,/images/whatisdevops_hu_6b4c540420feed19.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/whatisdevops.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;The best definition I&amp;rsquo;ve seen is from &lt;a href="http://donovanbrown.com/post/what-is-devops" target="_blank" rel="noreferrer"&gt;Donovan Brown&lt;/a&gt;, &amp;ldquo;DevOps is the union of &lt;strong&gt;People&lt;/strong&gt;, &lt;strong&gt;Processes&lt;/strong&gt;, and &lt;strong&gt;Products&lt;/strong&gt; to &lt;em&gt;continuously deliver &lt;strong&gt;value&lt;/strong&gt;&lt;/em&gt; to our end users&amp;rdquo;. Donovan breaks down his word choice and what it means to him. To me, DevOps is a mindset that should be adopted company wide. Because of delivering value to our end users, you need a Production-First mindset. It&amp;rsquo;s not about delivering story points, delivering user stories, or deploying to qa.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Half Measures
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Have you ever been at a company that &amp;ldquo;Bought the Agile&amp;rdquo;? Doing Standups and Sprints but not understanding the why behind it. Agile is a promise of more work from fewer people, right? Have you ever heard,&amp;ldquo;Why are we doing retrospectives, that&amp;rsquo;s not a feature&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;DevOps can be the same way. Perform a number of &lt;em&gt;actions&lt;/em&gt; and &lt;em&gt;activities&lt;/em&gt; but not adopt the &lt;em&gt;mindset&lt;/em&gt;. &lt;strong&gt;Why&lt;/strong&gt; are we automating things? &lt;strong&gt;Why&lt;/strong&gt; and &lt;strong&gt;what&lt;/strong&gt; should we log or alert on? &lt;strong&gt;How&lt;/strong&gt; do we use this data or improve our processes and products?&lt;/p&gt;</content:encoded></item><item><title>Some Tools to Help Present Git</title><link>https://chris-ayers.com/posts/some-tools-to-help-present-git/</link><pubDate>Sun, 23 Feb 2020 00:27:33 +0000</pubDate><guid>https://chris-ayers.com/posts/some-tools-to-help-present-git/</guid><description>&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="588"
 height="200"
 src="/images/logos/git-logo_hu_c9f36b789d4b30a3.png"
 srcset="/images/logos/git-logo_hu_c9f36b789d4b30a3.png 800w,/images/logos/git-logo_hu_c60da8252a135c76.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/git-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m presenting soon on Advanced Git. I feel a lot of Developers and DevOps engineers know enough git to the job, but sometimes that&amp;rsquo;s it. I want to help people be more comfortable with the git command-line, and help alleviate some fear or hesitation in dealing with git edge cases.&lt;/p&gt;
&lt;p&gt;While researching things, I came across a few neat tools I&amp;rsquo;m using to help describe things.&lt;/p&gt;</description><content:encoded>
&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="588"
 height="200"
 src="/images/logos/git-logo_hu_c9f36b789d4b30a3.png"
 srcset="/images/logos/git-logo_hu_c9f36b789d4b30a3.png 800w,/images/logos/git-logo_hu_c60da8252a135c76.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/git-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m presenting soon on Advanced Git. I feel a lot of Developers and DevOps engineers know enough git to the job, but sometimes that&amp;rsquo;s it. I want to help people be more comfortable with the git command-line, and help alleviate some fear or hesitation in dealing with git edge cases.&lt;/p&gt;
&lt;p&gt;While researching things, I came across a few neat tools I&amp;rsquo;m using to help describe things.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitgraphjs.com/" target="_blank" rel="noreferrer"&gt;GitGraphJs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Microsoft/Terminal" target="_blank" rel="noreferrer"&gt;Windows Terminal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/JanDeDobbeleer/oh-my-posh" target="_blank" rel="noreferrer"&gt;Oh My Posh&lt;/a&gt;/&lt;a href="https://ohmybash.github.io/" target="_blank" rel="noreferrer"&gt;ohmybash&lt;/a&gt;/&lt;a href="https://github.com/ohmyzsh/ohmyzsh" target="_blank" rel="noreferrer"&gt;ohmyzsh&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Git GUIs
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fork.dev/" target="_blank" rel="noreferrer"&gt;Fork&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gitkraken.com/" target="_blank" rel="noreferrer"&gt;GitKraken&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;&lt;a href="https://gitgraphjs.com/" target="_blank" rel="noreferrer"&gt;GitGraphJS&lt;/a&gt;
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;What is great about this tool is that I can draw git commit graphs pretty easily to describe what is happening as you execute commands. It helps create great graphs like this:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="834"
 height="661"
 src="/images/gitgraphjs_hu_8de1e8c393116570.png"
 srcset="/images/gitgraphjs_hu_8de1e8c393116570.png 800w,/images/gitgraphjs_hu_1b04f3d84cdb68a5.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/gitgraphjs.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;ve got a project where i can have each example laid out for discussion, and because its JavaScript, I can change things and show the result easily. This tools is great for slides and lecturing, but I also like having people put hands on keyboards and &lt;em&gt;try&lt;/em&gt; things.&lt;/p&gt;

&lt;h2 class="relative group"&gt;&lt;a href="https://github.com/Microsoft/Terminal" target="_blank" rel="noreferrer"&gt;Windows Terminal&lt;/a&gt;
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I do a lot of development on windows. CMD.exe isn&amp;rsquo;t the best experience to use or present with. The same can be said of regular powershell. I&amp;rsquo;ve leveraged git bash in the past, it works pretty well. But I have completely fallen in love with the Windows Terminal. Multiple profiles (for bash, wsl, powershell, cmd), a great font, and nice scalability, I&amp;rsquo;ve finally found a nice shell on windows.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1668"
 height="879"
 src="/images/terminal-panes_hu_59b6c4b03c09624f.gif"
 srcset="/images/terminal-panes_hu_59b6c4b03c09624f.gif 800w,/images/terminal-panes_hu_176ab8153d60d103.gif 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/terminal-panes.gif"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Oh My _______
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;If you aren&amp;rsquo;t familiar with &lt;a href="https://github.com/JanDeDobbeleer/oh-my-posh" target="_blank" rel="noreferrer"&gt;Oh My Posh (powershell)&lt;/a&gt;, &lt;a href="https://ohmybash.github.io/" target="_blank" rel="noreferrer"&gt;Oh My Bash (bash)&lt;/a&gt;, or &lt;a href="https://github.com/ohmyzsh/ohmyzsh" target="_blank" rel="noreferrer"&gt;Oh My Zsh (zsh)&lt;/a&gt; please pay attention. These tools add some awareness to your shell experience. If you &amp;lsquo;cd&amp;rsquo; into git folders, it can show the branch, how far ahead or behind you are, and many other details. It gives you situational awareness about where you are and your current state. There are also many themes to tweak and customize your command-line.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="923"
 height="769"
 src="/images/oh-my-x_hu_28ba3ddb710b7055.png"
 srcset="/images/oh-my-x_hu_28ba3ddb710b7055.png 800w,/images/oh-my-x_hu_ef131b02b148b054.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/oh-my-x.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;h2 class="relative group"&gt;Git GUIs
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t use git GUIs in my day to day workflow. I typically interact with git via a command-line. But there are some times when a gui is very helpful. Complex merges and rebases can be made clearer via gui, as well as a clear visualization of the git history. Don&amp;rsquo;t get me wrong, I can do that via command-line:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git log --pretty&lt;span class="o"&gt;=&lt;/span&gt;oneline --graph --decorate --all&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="590"
 height="406"
 src="/images/git-log_hu_7515ef37c5adfb76.png"
 srcset="/images/git-log_hu_7515ef37c5adfb76.png 800w,/images/git-log_hu_c2d27511848a6482.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/git-log.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;But these tools make it much cleaner, clearer, and prettier.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="700"
 height="682"
 src="/images/git-kraken_hu_5c8b00e46fb7ca05.png"
 srcset="/images/git-kraken_hu_5c8b00e46fb7ca05.png 800w,/images/git-kraken_hu_3a30524b897c32c7.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/git-kraken.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;I&amp;rsquo;m a big fan of tools that help me share ideas more effectively and these are a few that I think will help me explain git state.&lt;/p&gt;</content:encoded></item><item><title>WSL2, Docker, and Time</title><link>https://chris-ayers.com/posts/wsl2-docker-and-time/</link><pubDate>Thu, 23 Jan 2020 11:42:30 +0000</pubDate><guid>https://chris-ayers.com/posts/wsl2-docker-and-time/</guid><description>&lt;p&gt;I&amp;rsquo;m running on a Windows Insider Slow build so that I can leverage &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl2-index" target="_blank" rel="noreferrer"&gt;WSL 2&lt;/a&gt;, the Windows Subsystem for Linux v 2. Its pretty incredible, because there&amp;rsquo;s now a Linux kernel inside Windows. Ubuntu is fast, its a wonderful development experience all my favorite linux tools. I can&amp;rsquo;t wait for this to be out of preview this year and in the mainstream windows releases.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also using the latest version of Docker Desktop, with WSL2 support. What this means is that instead of using Hyper-V to run a Moby Linux VM, &lt;a href="https://docs.docker.com/docker-for-windows/wsl-tech-preview/" target="_blank" rel="noreferrer"&gt;docker runs directly on WSL2&lt;/a&gt;. It also has built-in Kubernetes support.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;m running on a Windows Insider Slow build so that I can leverage &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/wsl2-index" target="_blank" rel="noreferrer"&gt;WSL 2&lt;/a&gt;, the Windows Subsystem for Linux v 2. Its pretty incredible, because there&amp;rsquo;s now a Linux kernel inside Windows. Ubuntu is fast, its a wonderful development experience all my favorite linux tools. I can&amp;rsquo;t wait for this to be out of preview this year and in the mainstream windows releases.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also using the latest version of Docker Desktop, with WSL2 support. What this means is that instead of using Hyper-V to run a Moby Linux VM, &lt;a href="https://docs.docker.com/docker-for-windows/wsl-tech-preview/" target="_blank" rel="noreferrer"&gt;docker runs directly on WSL2&lt;/a&gt;. It also has built-in Kubernetes support.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="286"
 height="237"
 src="/images/logos/docker-logo_hu_b1f33bd62ae332a2.png"
 srcset="/images/logos/docker-logo_hu_b1f33bd62ae332a2.png 800w,/images/logos/docker-logo_hu_fa66ac60e5d22ca6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/logos/docker-logo.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;If you haven&amp;rsquo;t guessed, I&amp;rsquo;ve been doing some container development. The experience so far as been great, with &lt;a href="https://code.visualstudio.com/" target="_blank" rel="noreferrer"&gt;VS Code&lt;/a&gt; and the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack" target="_blank" rel="noreferrer"&gt;Remote Development Extension&lt;/a&gt; pack. I recently ran into some issues though. Here are some examples from bug posts opened about it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;While updating Ubuntu: E: Release file for &lt;a href="http://security.ubuntu.com/ubuntu/dists/bionic-security/InRelease" target="_blank" rel="noreferrer"&gt;http://security.ubuntu.com/ubuntu/dists/bionic-security/InRelease&lt;/a&gt; is not valid yet (invalid for another 14h 47min 54s). Updates for this repository will not be applied.&lt;/li&gt;
&lt;li&gt;Getting metadata from plugin failed with error: invalid_grant: Invalid JWT: Token must be a short-lived token (60 minutes) and in a reasonable timeframe. Check your iat and exp values and use a clock with skew to account for clock differences between systems.&lt;/li&gt;
&lt;li&gt;kubectl x509 certificate has expired or is not yet valid #1152&lt;/li&gt;
&lt;li&gt;Az storage blob copy start failed #9995&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I tried resolving the issue by syncing my time clock and restarting but I kept having issues. I figured it out today. Turns out, WSL2 has a issue (being worked on) with skewed clocks. Even though my Windows time was correct, the time in my WSL was off. It was not syncing with local system time and was probably based on the first time I powered it up.&lt;/p&gt;
&lt;p&gt;There are a few open issues on GitHub regarding the problem, so I know they&amp;rsquo;re aware and working on a fix.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time not synced in WSL2 - causing TLS issues - &lt;a href="https://github.com/microsoft/WSL/issues/4149" target="_blank" rel="noreferrer"&gt;https://github.com/microsoft/WSL/issues/4149&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;system date is not same with windows (WSL 2) - &lt;a href="https://github.com/microsoft/WSL/issues/4245" target="_blank" rel="noreferrer"&gt;https://github.com/microsoft/WSL/issues/4245&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;WSL2: Clock skewed? - &lt;a href="https://github.com/microsoft/WSL/issues/4677" target="_blank" rel="noreferrer"&gt;https://github.com/microsoft/WSL/issues/4677&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For me i first fixed the symtom, then followed the recommendation and actually solved the problem.&lt;br&gt;
Treating the symptom:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo ntpdate pool.ntp.org&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;But the actual recommendation from the team I used is:&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo hwclock -s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;I hope this helps someone until this goes GA. Thanks for your time!&lt;/p&gt;</content:encoded></item><item><title>Dependency Injection, Architecture, and Testing</title><link>https://chris-ayers.com/posts/dependency-injection-architecture-and-testing/</link><pubDate>Sun, 01 Dec 2019 23:27:53 +0000</pubDate><guid>https://chris-ayers.com/posts/dependency-injection-architecture-and-testing/</guid><description>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://crosscuttingconcerns.com/The-Third-Annual-csharp-Advent" target="_blank" rel="noreferrer"&gt;Third Annual C# Advent&lt;/a&gt;. Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;re done here&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dependency Injection, or DI, is a Software Architecture Design Pattern. DI is something that comes up during discussions on SOLID, IoC (Inversion of Control), testing, and refactoring. I want to speak on each of these briefly because DI touches all of these. But before I really dive into things, I want to define what a dependency is. A &lt;em&gt;dependency&lt;/em&gt; is any object that another object requires. So all of those classes, services, and libraries that we use to build our applications are dependencies.&lt;/p&gt;</description><content:encoded>&lt;p&gt;&lt;strong&gt;This blog was posted as part of the &lt;a href="https://crosscuttingconcerns.com/The-Third-Annual-csharp-Advent" target="_blank" rel="noreferrer"&gt;Third Annual C# Advent&lt;/a&gt;. Make sure to check out everyone else&amp;rsquo;s work when you&amp;rsquo;re done here&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dependency Injection, or DI, is a Software Architecture Design Pattern. DI is something that comes up during discussions on SOLID, IoC (Inversion of Control), testing, and refactoring. I want to speak on each of these briefly because DI touches all of these. But before I really dive into things, I want to define what a dependency is. A &lt;em&gt;dependency&lt;/em&gt; is any object that another object requires. So all of those classes, services, and libraries that we use to build our applications are dependencies.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Dependency Injection - What is it?
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;At its heart, Dependency Injection is right there in the name, you inject your dependencies. I want all the dependencies for my class called out in the constructor, parameter list, or somewhere I can easily see them. Without Dependency Injection, any time I need a class, service, database connection, I just new it up. This can make code untestable, hard to use safely, and very brittle. Lets see what this looks like in a small example. Imagine the hidden dependencies and hard to test logic in a bigger class.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ClientRepository&lt;/span&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ClientService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//allocated in constructor but can&amp;#39;t test or use a different repository without a code change&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ClientRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//hidden dependency to the delete method, newed up here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;audit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AuditService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//a hidden static dependency, very hard to test with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EmailProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendDeleteConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Untestable
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;I said not using DI would make the code untestable. Why? There are a lot of reasons in this example. I&amp;rsquo;ll go through them one at a time.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Isolation / Coupling
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;When you are writing unit tests, you want to isolate a class for testing. Some people call it the &amp;ldquo;System Under Test&amp;rdquo; (SUT) or &amp;ldquo;Class Under Test&amp;rdquo; (CUT). How can we isolate just the logic in ClientService from the logic in its dependencies? Our service is tightly coupled to the repository, the audit service and the email provider. I can&amp;rsquo;t easily put in a fake/mocked email provider or audit service to test what my class is doing. This type of code pushes a lot of developers to skip unit tests and just rely on integration tests.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Database
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Another issue is I/O, like files, databases, network connections. In this example, I&amp;rsquo;m using the Repository pattern to wrap/abstract my database. This could be a file, mysql, or Azure CosmosDB, I don&amp;rsquo;t care. But, in this case, it&amp;rsquo;s getting created in the constructor of my service. When I want to unit test my business logic, it will be touching the database. More of an integration test than a unit test. Unit tests should be &lt;em&gt;fast&lt;/em&gt;. &lt;strong&gt;Ludicrous&lt;/strong&gt; speed &lt;em&gt;fast&lt;/em&gt;. Sure I could create a local or testing database, but again, that is integration, not unit testing.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Statics
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Statics are always a problem because statics can&amp;rsquo;t be mocked or faked. Another issue is that its hard to tell side effects of those classes and methods. When I&amp;rsquo;m reviewing a class or method, I usually check the constructor, parameters, or variables at the top to see what a piece of code depends on. Static methods can be buried inside a block of code and be missed. It means that it can be a hidden dependency.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Interfaces
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I feel it is very important to have interfaces define the contracts between layers and systems. Having &lt;em&gt;FOO&lt;/em&gt; depend on &lt;em&gt;BAR&lt;/em&gt; means that &lt;em&gt;FOO&lt;/em&gt; might change every time we modify the implementation of &lt;em&gt;BAR&lt;/em&gt;. If &lt;em&gt;FOO&lt;/em&gt; depends on &lt;em&gt;IBAR&lt;/em&gt;, the API contract of &lt;em&gt;BAR&lt;/em&gt;, we can have multiple implementations, change the implementation or mock it. The interface insulates &lt;em&gt;FOO&lt;/em&gt; from the changes in &lt;em&gt;BAR&lt;/em&gt;. In addition to reducing coupling between modules, it will help with testability and refactoring down the road.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Types of Dependency Injection
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at a few types of dependency injection. The type I usually prefer, and my default, is Constructor Injection. This lists all the dependencies for a class in its constructor. There are times where this isn&amp;rsquo;t a good idea, when you have the expensive creation of dependencies that are only used in infrequent edge cases in the class. In those instances, Parameter Injection works well, injecting the dependency as a parameter into the methods that need it. Finally there is property injection which I dislike. I&amp;rsquo;ve seen people use it because of tutorials online, but I &lt;em&gt;&lt;strong&gt;HIGHLY&lt;/strong&gt;&lt;/em&gt; recommend avoiding this if possible.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Constructor Injection
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;This involves moving dependencies to the constructor where possible. This is a lot of times the place where people end up, but if you&amp;rsquo;re refactoring older code, you won&amp;rsquo;t get here right away. Notice I moved from having private variables of the concrete classes to the interfaces. Also notice, looking at the class constructor, you know exactly what this class depends on.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClientService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IClientRepository&lt;/span&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAuditService&lt;/span&gt; &lt;span class="n"&gt;_auditService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IEmailProvider&lt;/span&gt; &lt;span class="n"&gt;_emailProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ClientService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IClientRepository&lt;/span&gt; &lt;span class="n"&gt;clientRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAuditService&lt;/span&gt; &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IEmailProvider&lt;/span&gt; &lt;span class="n"&gt;emailProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_auditService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_emailProvider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;emailProvider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_auditService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_emailProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendDeleteConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Parameter Injection
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;This involves moving dependencies to where they are used. Looking at the interface to the service or the methods, you can see what dependencies are needed for some of the methods. Like I mentioned before, if there are some dependencies that are really expensive to create or maybe incur some type of cost, you can limit their creation to only when they are used.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClientService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IClientRepository&lt;/span&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ClientService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IClientRepository&lt;/span&gt; &lt;span class="n"&gt;clientRepository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAuditService&lt;/span&gt; &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IEmailProvider&lt;/span&gt; &lt;span class="n"&gt;emailProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;emailProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendDeleteConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 class="relative group"&gt;Property Injection
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Use property injection only for optional dependencies. That means your service should be able to properly work without these dependencies provided. After you instantiate the class, you need to assign the properties with objects. This can cause confusion because you can&amp;rsquo;t always tell what a classes true dependencies are just looking at the constructor. It can also cause confusion during testing.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClientService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IClientService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEmailProvider&lt;/span&gt; &lt;span class="n"&gt;EmailProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IClientRepository&lt;/span&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAuditService&lt;/span&gt; &lt;span class="n"&gt;_auditService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ClientService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IClientRepository&lt;/span&gt; &lt;span class="n"&gt;clientRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAuditService&lt;/span&gt; &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_auditService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auditService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_auditService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LogDelete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_clientRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;EmailProvider&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;SendDeleteConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;DI Containers
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Having all our dependencies in the constructor is nice, but &lt;em&gt;who&lt;/em&gt; is going to new up our services? With most DI frameworks, you register your interfaces and services at program startup with a DI container, and everything is wired up for you. You can control the wiring process. Should service A be a singleton? Should service B be instantiated on every call? Dotnet core has built-in support for DI containers. And before dotnet core, the .Net Framework had a lot of different libraries and frameworks to help with dependency injection. You might have heard of Ninject, Autofac, Castle, or Unity. Dotnet core has some &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.0" target="_blank" rel="noreferrer"&gt;good documentation&lt;/a&gt; around their implementation, but if you want to you something else you can.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IMyDependency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyDependency&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOperationTransient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOperationScoped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOperationSingleton&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Testing
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I just briefly wanted to discuss testing. Using some of the techniques I&amp;rsquo;ve discussed, testing services and classes is much easier. By having interfaces for all my major services, constructor injection in place, and a mocking framework, I can isolate classes for testing.&lt;/p&gt;
&lt;div class="highlight-wrapper"&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;BookService&lt;/span&gt; &lt;span class="n"&gt;_bookService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IBookRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_mockBookRepository&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IBookEntityDomainAdapter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_mockBookEntityDomainAdapter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; [TestInitialize]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;TestInitialize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mockBookRepository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IBookRepository&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mockBookEntityDomainAdapter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IBookEntityDomainAdapter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_bookService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BookService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_mockBookRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_mockBookEntityDomainAdapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt; [TestMethod]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="n"&gt;GetAll_ItemsInRepository_ReturnsSameNumberOfItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//arrange&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sampleData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Enumerable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BookEntity&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_mockBookRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sampleData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//act&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_bookService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;//assert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AreEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sampleData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 class="relative group"&gt;Where to Go from here - Refactoring
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Taking legacy code, code without tests (essentially the same thing), or code written without dependency injection and refactoring it into testable, decoupled code is not as easy as installing a nuget package. If you are just getting started and you have a ton of new ServiceX() and ServiceY.action() sprinkled throughout your code, code review the docs at: &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.0" target="_blank" rel="noreferrer"&gt;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.0&lt;/a&gt;. Have interfaces for your classes. When you have dependencies, instantiate them in your constructor if you can&amp;rsquo;t setup DI, it will help with readability and maintainability.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Conclusion
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Dependency Injection is a pattern that everyone should be using. It makes the code more readable, maintainable, and testable. I know a lot of people think that this is something we&amp;rsquo;ve solved and everyone uses, but we&amp;rsquo;re not. Too many people aren&amp;rsquo;t setting up DI, they aren&amp;rsquo;t using interfaces, they aren&amp;rsquo;t registering dependencies. We need to keep discussing this because people are constantly starting out, learning, and growing in this field.&lt;/p&gt;
&lt;p&gt;There is some sample code at: &lt;a href="https://github.com/Codebytes/DependencyInjectionDemo" target="_blank" rel="noreferrer"&gt;https://github.com/Codebytes/DependencyInjectionDemo&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>Presenting with VS Code - Screencast mode</title><link>https://chris-ayers.com/posts/presenting-with-vs-code-screencast-mode/</link><pubDate>Sun, 06 Oct 2019 11:58:28 +0000</pubDate><guid>https://chris-ayers.com/posts/presenting-with-vs-code-screencast-mode/</guid><description>&lt;p&gt;I have been starting to speak and present a lot more, and was looking into great tools like &lt;a href="http://carnackeys.com/" target="_blank" rel="noreferrer"&gt;Carnac&lt;/a&gt; and &lt;a href="https://github.com/AxDSan/KeyPose" target="_blank" rel="noreferrer"&gt;KeyPosé&lt;/a&gt;. But I just found out today about a feature I didn&amp;rsquo;t know existed inside Visual Studio Code, &lt;a href="https://code.visualstudio.com/updates/v1_31#_screencast-mode" target="_blank" rel="noreferrer"&gt;Screencast mode&lt;/a&gt;. This was introduced in January 2019. How did I miss it?&lt;/p&gt;
&lt;p&gt;To enable and use Screencast mode, Open the command palette, Ctrl + Shift + P.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="605"
 height="64"
 src="/images/enable-screencast-mode_hu_81f471ce1598993a.png"
 srcset="/images/enable-screencast-mode_hu_81f471ce1598993a.png 800w,/images/enable-screencast-mode_hu_f93d01099f25b623.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/enable-screencast-mode.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1905"
 height="1015"
 src="/images/screencast-unconfigured_hu_2ab6c8fe03faafc9.png"
 srcset="/images/screencast-unconfigured_hu_2ab6c8fe03faafc9.png 800w,/images/screencast-unconfigured_hu_c573165e172d4cc6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/screencast-unconfigured.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;When first enabled, Screencast Mode is not what I wanted. It shows &lt;em&gt;EVERY&lt;/em&gt; keypress. It also shows a little higher on the screen that I prefer. It also puts a little red circle everywhere I click the mouse, which is nice. Let&amp;rsquo;s configure it and see if we can clean up some of that. Open the command palette again (Ctrl + Shift + P) and go to the user settings.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I have been starting to speak and present a lot more, and was looking into great tools like &lt;a href="http://carnackeys.com/" target="_blank" rel="noreferrer"&gt;Carnac&lt;/a&gt; and &lt;a href="https://github.com/AxDSan/KeyPose" target="_blank" rel="noreferrer"&gt;KeyPosé&lt;/a&gt;. But I just found out today about a feature I didn&amp;rsquo;t know existed inside Visual Studio Code, &lt;a href="https://code.visualstudio.com/updates/v1_31#_screencast-mode" target="_blank" rel="noreferrer"&gt;Screencast mode&lt;/a&gt;. This was introduced in January 2019. How did I miss it?&lt;/p&gt;
&lt;p&gt;To enable and use Screencast mode, Open the command palette, Ctrl + Shift + P.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="605"
 height="64"
 src="/images/enable-screencast-mode_hu_81f471ce1598993a.png"
 srcset="/images/enable-screencast-mode_hu_81f471ce1598993a.png 800w,/images/enable-screencast-mode_hu_f93d01099f25b623.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/enable-screencast-mode.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1905"
 height="1015"
 src="/images/screencast-unconfigured_hu_2ab6c8fe03faafc9.png"
 srcset="/images/screencast-unconfigured_hu_2ab6c8fe03faafc9.png 800w,/images/screencast-unconfigured_hu_c573165e172d4cc6.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/screencast-unconfigured.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;When first enabled, Screencast Mode is not what I wanted. It shows &lt;em&gt;EVERY&lt;/em&gt; keypress. It also shows a little higher on the screen that I prefer. It also puts a little red circle everywhere I click the mouse, which is nice. Let&amp;rsquo;s configure it and see if we can clean up some of that. Open the command palette again (Ctrl + Shift + P) and go to the user settings.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="612"
 height="73"
 src="/images/open-user-settings_hu_adda61d4a3473bc9.png"
 srcset="/images/open-user-settings_hu_adda61d4a3473bc9.png 800w,/images/open-user-settings_hu_51434132148dfb12.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/open-user-settings.png"
 /&gt;
 
 
 &lt;/figure&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="999"
 height="283"
 src="/images/screencast-settings_hu_6c348518682bf88a.png"
 srcset="/images/screencast-settings_hu_6c348518682bf88a.png 800w,/images/screencast-settings_hu_62614ee6d80d5ed5.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/screencast-settings.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;There aren&amp;rsquo;t a lot of options right now, but I only want to see keyboard shortcuts.. Ctrl+C, Ctrl+V, Alt+Shift+F. So I&amp;rsquo;ll check that, and I want it a little lower on the screen, so I lowered the default from 20 down to 5. Let&amp;rsquo;s see the finished product:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1900"
 height="1014"
 src="/images/screencast-configured_hu_a9ec228f7a32d79a.png"
 srcset="/images/screencast-configured_hu_a9ec228f7a32d79a.png 800w,/images/screencast-configured_hu_45ae5fd5ccd84851.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/screencast-configured.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Awesome. I can now go on a mac, linux, or windows machine and have a consistent experience with sharing my keypresses and mouse clicks. If you present, live code, or give demos, this is a great feature to enable and I encourage you to do so. Seeing all the keyboard shortcuts people use is a great thing to learn.&lt;/p&gt;</content:encoded></item><item><title>RESTful API Versioning</title><link>https://chris-ayers.com/posts/restful-api-versioning/</link><pubDate>Mon, 02 Sep 2019 22:26:57 +0000</pubDate><guid>https://chris-ayers.com/posts/restful-api-versioning/</guid><description>&lt;p&gt;I&amp;rsquo;ve been a developer for a long time, writing APIs and clients to consume them. When an API is around long enough, it needs to change. I&amp;rsquo;ve versioned APIs in the past using a number of different techniques. Some successful, some painful. Now I realize this discussion is like the VI/Emacs conflict, the Tab/Space wars, and the Spanish Inquisition, but it is a good topic to look at. There are a few main styles when it comes to API versioning:&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve been a developer for a long time, writing APIs and clients to consume them. When an API is around long enough, it needs to change. I&amp;rsquo;ve versioned APIs in the past using a number of different techniques. Some successful, some painful. Now I realize this discussion is like the VI/Emacs conflict, the Tab/Space wars, and the Spanish Inquisition, but it is a good topic to look at. There are a few main styles when it comes to API versioning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;li&gt;Query Parameter&lt;/li&gt;
&lt;li&gt;Accept header&lt;/li&gt;
&lt;li&gt;Custom request header&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;URL
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;This is where a lot of people start, you might have seen it out there. You can easily see the version of the API you are calling. And a lot of times behind the scenes, the base API points to the latest version. The issue a number of people have with this, myself included, is that a URL in RESTful APIs should represent an entity, not a version of an entity. For example, /api/v1/users/27 and /api/v2/users/27 are not two different users. As you add new versions, you break permalinks to resources. And you&amp;rsquo;re tying the URL of a resource, not to a version of it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/api/users -&amp;gt; /api/v2/users
/api/v1/users
/api/v2/users
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 class="relative group"&gt;Query Parameter
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Another way people can version APIs is by Query Parameter. This keeps the URL consistent, but adds an extra parameter. This is fine by my book, you&amp;rsquo;re adding extra info to the &lt;em&gt;request&lt;/em&gt;, not the &lt;strong&gt;resource&lt;/strong&gt;. It can change per request, and doesn&amp;rsquo;t change the permalink or URL of the resource.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/api/users -&amp;gt; /api/users?api-version=1.0
/api/users?api-version=1.0
/api/users?api-version=2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 class="relative group"&gt;Accept Header
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The third way I&amp;rsquo;ve seen API versioning is an accept header on the http(s) request. Accept headers describe how you&amp;rsquo;ld like to receive the data. Being part of the http headers, this can be taken into effect during caching, and if not specified explicitly can return the latest version. I like this method quite a bit, the client is telling the server how it wants to receive the data, the format &lt;em&gt;and&lt;/em&gt; the version.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET api/users HTTP/1.1
host: localhost
accept: application/json;v=2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 class="relative group"&gt;Custom Header
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;The last way I&amp;rsquo;ve seen is to define a custom http header. Just adding the requested version as a header to the request. This isn&amp;rsquo;t as easily compatible with clients as the other methods.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;api-version: 1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 class="relative group"&gt;Implementation
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In the past, I&amp;rsquo;ve had to roll my own. I started with URL versioning. As I worked with RESTful APIs, I realized there was a better way and started moving towards either the Query parameter or an Accept Header. There are some pretty good libraries that will handle a lot of this for you today.&lt;/p&gt;
&lt;p&gt;Microsoft has a library that works with .Net Framework 4.5 and .Net Core. It&amp;rsquo;s called ASPNet API Versioning, &lt;a href="https://github.com/microsoft/aspnet-api-versioning" target="_blank" rel="noreferrer"&gt;https://github.com/microsoft/aspnet-api-versioning&lt;/a&gt; . They also released libraries to work with &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-2.2" target="_blank" rel="noreferrer"&gt;Swagger&lt;/a&gt; for API documentation. Honestly, its pretty nice and supports a number of methods for versioning that you can customize pretty well. This follows the &lt;a href="https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#12-versioning" target="_blank" rel="noreferrer"&gt;Microsoft REST API Guidelines&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are some more references to check out at &lt;a href="http://apistylebook.com/design/topics/versioning" target="_blank" rel="noreferrer"&gt;http://apistylebook.com/design/topics/versioning&lt;/a&gt;&lt;/p&gt;</content:encoded></item><item><title>ARM - Part 3: Hook up the Pipes</title><link>https://chris-ayers.com/posts/arm-part-3-hook-up-the-pipes/</link><pubDate>Tue, 28 May 2019 18:30:31 +0000</pubDate><guid>https://chris-ayers.com/posts/arm-part-3-hook-up-the-pipes/</guid><description>&lt;p&gt;I&amp;rsquo;ve got a template straight from Microsoft. I want this wired into a CI/CD pipeline to I can play around and get quick feedback. I&amp;rsquo;m going to use Azure DevOps to help make all this possible. Let&amp;rsquo;s get those templates into a repository to get started. New repository, initialize it, add new files.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="936"
 height="697"
 src="/images/repoimport_hu_a231c6fac9274541.png"
 srcset="/images/repoimport_hu_a231c6fac9274541.png 800w,/images/repoimport_hu_e9cbdb89dbe45e9d.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/repoimport.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Next, I&amp;rsquo;m going to create a new resource group to play around with my web app resources.&lt;/p&gt;</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve got a template straight from Microsoft. I want this wired into a CI/CD pipeline to I can play around and get quick feedback. I&amp;rsquo;m going to use Azure DevOps to help make all this possible. Let&amp;rsquo;s get those templates into a repository to get started. New repository, initialize it, add new files.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="936"
 height="697"
 src="/images/repoimport_hu_a231c6fac9274541.png"
 srcset="/images/repoimport_hu_a231c6fac9274541.png 800w,/images/repoimport_hu_e9cbdb89dbe45e9d.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/repoimport.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Next, I&amp;rsquo;m going to create a new resource group to play around with my web app resources.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="936"
 height="697"
 src="/images/newresourcegroup-3_hu_5146520a2e9784c4.png"
 srcset="/images/newresourcegroup-3_hu_5146520a2e9784c4.png 800w,/images/newresourcegroup-3_hu_314fca093561f753.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/newresourcegroup-3.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Now we need to make sure DevOps has permission to create and update resources in Azure. This can be done in a few different ways as described here: &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops" target="_blank" rel="noreferrer"&gt;https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to setup a service connection from DevOps to Azure. In Azure Devops, I&amp;rsquo;m going to go to my project settings:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1171"
 height="872"
 src="/images/projectsettings_hu_1850d1630a3e2272.png"
 srcset="/images/projectsettings_hu_1850d1630a3e2272.png 800w,/images/projectsettings_hu_89b12d7cda1cb283.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/projectsettings.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;And go to Service Connections:&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1171"
 height="872"
 src="/images/serviceconnections_hu_bdba44ab4829564f.png"
 srcset="/images/serviceconnections_hu_bdba44ab4829564f.png 800w,/images/serviceconnections_hu_974108d53a095e9a.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/serviceconnections.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;We&amp;rsquo;ll create a new Azure Resource Manager service connection.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1171"
 height="872"
 src="/images/serviceconnectionarm_hu_35d4bdc7e4b97cb7.png"
 srcset="/images/serviceconnectionarm_hu_35d4bdc7e4b97cb7.png 800w,/images/serviceconnectionarm_hu_113bd6c0cc361c42.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/serviceconnectionarm.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;We just need to pick the right Subscription, Sign in, and point it at our resource group. There are more complex setups, and we&amp;rsquo;ll get into those in the future, but for now we&amp;rsquo;ll just point it at that resource group I created earlier. Now for this to work properly, you have to be able to grant rights to the connector.&lt;/p&gt;

&lt;figure&gt;
 &lt;img
 class="my-0 rounded-md"
 loading="lazy"
 decoding="async"
 fetchpriority="auto"
 alt=""
 width="1171"
 height="872"
 src="/images/serviceconnectionarm2-4_hu_c3a43706458d9f8c.png"
 srcset="/images/serviceconnectionarm2-4_hu_c3a43706458d9f8c.png 800w,/images/serviceconnectionarm2-4_hu_2f0b824f12395cd9.png 1280w"
 sizes="(min-width: 768px) 50vw, 65vw"
 data-zoom-src="/images/serviceconnectionarm2-4.png"
 /&gt;
 
 
 &lt;/figure&gt;
&lt;p&gt;Now we&amp;rsquo;ve got a connector to deploy to Azure. We&amp;rsquo;ll finish this up next time.&lt;/p&gt;</content:encoded></item><item><title>ARM - Part 2: Azure Quickstart Templates</title><link>https://chris-ayers.com/posts/arm-part-2-azure-quickstart-templates/</link><pubDate>Mon, 13 May 2019 20:59:03 +0000</pubDate><guid>https://chris-ayers.com/posts/arm-part-2-azure-quickstart-templates/</guid><description>&lt;h2 class="relative group"&gt;Time to Dive in
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m one of those guys that likes to learn by doing. Reading the documentation is great, and I do that a lot. But for me to really &lt;em&gt;grok&lt;/em&gt; something, I need to play with it, run it, and probably blow it up.&lt;/p&gt;
&lt;p&gt;If you missed &lt;a href="http://chris-ayers.com/arm-azure-resource-manager" target="_blank" rel="noreferrer"&gt;part 1&lt;/a&gt;, read along and come back. I need a WebApp setup for my sample project. I realized I can do it a few ways. Some of the ways are very manual, some are repeatable, but one stood out to me.&lt;/p&gt;</description><content:encoded>
&lt;h2 class="relative group"&gt;Time to Dive in
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m one of those guys that likes to learn by doing. Reading the documentation is great, and I do that a lot. But for me to really &lt;em&gt;grok&lt;/em&gt; something, I need to play with it, run it, and probably blow it up.&lt;/p&gt;
&lt;p&gt;If you missed &lt;a href="http://chris-ayers.com/arm-azure-resource-manager" target="_blank" rel="noreferrer"&gt;part 1&lt;/a&gt;, read along and come back. I need a WebApp setup for my sample project. I realized I can do it a few ways. Some of the ways are very manual, some are repeatable, but one stood out to me.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create the resources in the Azure Portal&lt;/li&gt;
&lt;li&gt;Create the resources in Visual Studio when I right-click Publish&lt;/li&gt;
&lt;li&gt;Create the resources via a Powershell script&lt;/li&gt;
&lt;li&gt;Create the resources via the Azure CLI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create an ARM Template, create the resources on deploy, or via the CLI&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 class="relative group"&gt;Let&amp;rsquo;s Write an ARM Template
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I know what I want to do, write an ARM Template. Do I have any idea what I&amp;rsquo;m doing or where to start at this point? &lt;strong&gt;No.&lt;/strong&gt; Fortunately for me, Microsoft provides a &lt;strong&gt;BUNCH&lt;/strong&gt; of resources to get you started. There is an &lt;a href="https://azure.microsoft.com/en-us/resources/templates/" target="_blank" rel="noreferrer"&gt;entire searchable quickstart page&lt;/a&gt; with examples (backed by a &lt;a href="https://www.github.com/Azure/azure-quickstart-templates" target="_blank" rel="noreferrer"&gt;GitHub repository&lt;/a&gt;) as well the &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates" target="_blank" rel="noreferrer"&gt;Microsoft Docs on ARM Templates&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Well, I found one in the quickstart: &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/quickstart-arm-template?pivots=platform-windows" target="_blank" rel="noreferrer"&gt;https://learn.microsoft.com/en-us/azure/app-service/quickstart-arm-template?pivots=platform-windows&lt;/a&gt;. From this quickstart, I can click Deploy to Azure, fill out some information and I&amp;rsquo;ve got an App Service Plan and a Web App. &lt;strong&gt;This is so cool.&lt;/strong&gt; Now to tear this thing apart and figure out what it is.&lt;/p&gt;

&lt;h2 class="relative group"&gt;OK, Lets play with an ARM Template
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://github.com/Azure/azure-quickstart-templates/tree/master/quickstarts/microsoft.web/webapp-basic-windows" target="_blank" rel="noreferrer"&gt;GitHub repository&lt;/a&gt; it looks like there are 2 or 3 files that make up the ARM Template.&lt;/p&gt;

&lt;h3 class="relative group"&gt;Dissection
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;Lets dissect &lt;a href="https://github.com/Azure/azure-quickstart-templates/blob/master/quickstarts/microsoft.web/webapp-basic-windows/azuredeploy.json" target="_blank" rel="noreferrer"&gt;azuredeploy.json&lt;/a&gt;. Schema, version, blah&amp;hellip;&lt;/p&gt;

&lt;h4 class="relative group"&gt;Parameters
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;My eye is drawn to the Parameters section. I recognize some of these from when I deployed to Azure. Getting a feel for parameters I see you can have metadata describing them, types, lengths, and default values. This gives me lots of ideas and options.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Variables
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;This isn&amp;rsquo;t your mama&amp;rsquo;s json. That looks like a function to me! Looks like somebody got their code in my json. I like it. A quick google later and I see a huge list of &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions" target="_blank" rel="noreferrer"&gt;template functions&lt;/a&gt; in Microsoft&amp;rsquo;s documentation. I&amp;rsquo;ll come back to that. In the mean time, those variables look a lot like the name of the webapp and AppServicePlan. I can work with this.&lt;/p&gt;

&lt;h4 class="relative group"&gt;Resources
 
 
 
 
&lt;/h4&gt;
&lt;p&gt;Meat and potatoes. Jackpot. This looks like where the resources are defined. Only a few properties on each, an apiVer, type, name, location, sku, and a few others. I also notice one has a &amp;ldquo;dependsOn&amp;rdquo; property, I bet I can build up what depends on what and it will do everything in the right order.&lt;/p&gt;

&lt;h2 class="relative group"&gt;Next Steps
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve got an ARM Template now that works. I&amp;rsquo;ve poked at it a little. Time to make this thing &lt;em&gt;fly&lt;/em&gt;. Lets setup an Azure repository on DevOps and see about trying to deploy this thing and create my webapp on release. I want to be able to check in a change and have it create/update my resources in azure. Next time, we start the feedback loop.&lt;/p&gt;</content:encoded></item><item><title>ARM - Part 1: Azure Resource Manager</title><link>https://chris-ayers.com/posts/arm-azure-resource-manager/</link><pubDate>Sat, 11 May 2019 23:53:57 +0000</pubDate><guid>https://chris-ayers.com/posts/arm-azure-resource-manager/</guid><description>&lt;h2 class="relative group"&gt;The Journey Begins
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been an azure developer for years. Originally I worked with &amp;ldquo;Classic Mode&amp;rdquo; and Cloud Services. Then I moved to ARM and Web Apps. Lately I&amp;rsquo;ve been doing DevOps but I only recently started working with ARM Termplates. First, let&amp;rsquo;s dive into a little history.&lt;/p&gt;

&lt;h2 class="relative group"&gt;History
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Azure has grown and changed since it was first introduced. Originally, it was a research project called, &amp;ldquo;Project Red Dog&amp;rdquo;. Azure has been commercially available since 2010. For four years, there was a limited way to interact with Azure, ASM the Azure Service Manager.&lt;/p&gt;</description><content:encoded>
&lt;h2 class="relative group"&gt;The Journey Begins
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been an azure developer for years. Originally I worked with &amp;ldquo;Classic Mode&amp;rdquo; and Cloud Services. Then I moved to ARM and Web Apps. Lately I&amp;rsquo;ve been doing DevOps but I only recently started working with ARM Termplates. First, let&amp;rsquo;s dive into a little history.&lt;/p&gt;

&lt;h2 class="relative group"&gt;History
 
 
 
 
&lt;/h2&gt;
&lt;p&gt;Azure has grown and changed since it was first introduced. Originally, it was a research project called, &amp;ldquo;Project Red Dog&amp;rdquo;. Azure has been commercially available since 2010. For four years, there was a limited way to interact with Azure, ASM the Azure Service Manager.&lt;/p&gt;

&lt;h3 class="relative group"&gt;The Old Thing (Classic Mode) - ASM Azure Service Manager
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;The big problem with ASM was that all the resources were disconnected and separate. If you had multiple things you were creating, you had to keep track of them, create them in the right order, and make sure you deleted them all when things were removed. You were also limited in capabilities, like policies, tagging, and security. I remember the deployment scripts that you would write to check if things existed, poll when they got created, and you would have to keep tweaking to get it to run properly. Powershell was the language and script of choice.&lt;/p&gt;

&lt;h3 class="relative group"&gt;The New Thing - ARM Azure Resource Manager
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;ARM was released in 2014. ARM is a great change. Instead of writing scripts to poll and make sure things were created, you can deploy and manage resources as a group. It opens the door for repeated deployments and templates. It lets you define the deployment order and apply tags and policies. You can still write Powershell, the but there are new modules for ARM (Azure-RM*). But you can also use Azure CLI, Rest APIs and client SDKs. I will definitely be diving into the Azure CLI in the future. Right now, the part I&amp;rsquo;m loving, is that I can define my infrastructure as code.&lt;/p&gt;

&lt;h3 class="relative group"&gt;ARM Templates
 
 
 
 
&lt;/h3&gt;
&lt;p&gt;ARM Templates are amazing. I plan on diving deep into this over the next few blog posts. But what is awesome is that I can have my project source code and the infrastructure it runs on checked into the same repository. Those files can then be built and deployed together. I can continuously deploy my environment and the deployment can apply the differences as needed. Microsoft has some great documentation on &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authoring-templates" target="_blank" rel="noreferrer"&gt;ARM Templates&lt;/a&gt;. They also have a great quickstart resource: &lt;a href="https://azure.microsoft.com/en-us/resources/templates/" target="_blank" rel="noreferrer"&gt;Azure Quickstart Template&lt;/a&gt;. I&amp;rsquo;ll be diving into those topics next time in &lt;a href="https://chris-ayers.com/2019/05/13/arm-part-2-azure-quickstart-templates/" target="_blank" rel="noreferrer"&gt;part 2&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Blog Incoming</title><link>https://chris-ayers.com/posts/blog-incoming/</link><pubDate>Tue, 30 Apr 2019 21:26:33 +0000</pubDate><guid>https://chris-ayers.com/posts/blog-incoming/</guid><description>&lt;p&gt;Time to get back to it.&lt;/p&gt;</description><content:encoded>&lt;p&gt;Time to get back to it.&lt;/p&gt;</content:encoded></item></channel></rss>