Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
158422 stories
·
33 followers

Enabling the Application Insights Profiler

1 Share

Note: this is part 2 of a series on the Azure Monitor Profiler. Part 1 covered what the Profiler is and where to find the results - this post covers actually turning it on.

There are two ways to enable the profiler: through app settings on App Service, or by wiring it into your code directly. Which one you need depends on where your app runs and how much control you want over the setup.

Option 1: codeless enablement on App Service

If your app runs on App Service (Windows) and your Application Insights resource is in the same subscription, this is the easiest path - no code changes, no redeploy.

From the portal:

  • In your App Service instance, select Monitoring > Application Insights
  • Select Turn on Application Insights, then Enable
  • Scroll down to the .NET or .NET Core tab
  • Set Collection level to Recommended
  • Under Profiler and Code Optimizations, select On
  • Apply, then confirm with Yes

Or skip the portal entirely and set the app settings directly:

az webapp config appsettings set \ --resource-group myRG \ --name myWebApp \ --settings \ APPLICATIONINSIGHTS_CONNECTION_STRING="<your-connection-string>" \ APPINSIGHTS_PROFILERFEATURE_VERSION=1.0.0 \ DiagnosticServices_EXTENSION_VERSION=~3

Remark: if your Application Insights resource lives in a different subscription than your App Service, the portal wizard above won't be available to you, and you'll need to fall back to setting these app settings manually. Same three settings, just no guided flow.

Once the settings are in, the profiler runs as a continuous WebJob on the app. If you want to confirm it's actually running rather than waiting for traces to show up:

  • Go to WebJobs in the left menu 
  • Check the status of ApplicationInsightsProfiler3 - it should read Running. If it isn't, the WebJob logs are the first place to look.

Option 2: package-based setup in code

Not on App Service, or you want the Profiler configured as part of your app startup rather than as portal/infra config?

Install it as a package instead, using the Azure Monitor OpenTelemetry Distro, which is the current recommended path over the classic Application Insights SDK:

dotnet add package Azure.Monitor.OpenTelemetry.AspNetCore
dotnet add package Azure.Monitor.OpenTelemetry.Profiler --prerelease

Then enable it alongside your existing OpenTelemetry setup:

using Azure.Monitor.OpenTelemetry.AspNetCore;
using Azure.Monitor.OpenTelemetry.Profiler;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .UseAzureMonitor()          // Enable the Azure Monitor OpenTelemetry Distro
    .AddAzureMonitorProfiler(); // Add the Profiler

var app = builder.Build();
app.MapControllers();
app.Run();

UseAzureMonitor() reads the connection string from the APPLICATIONINSIGHTS_CONNECTION_STRING environment variable by default, or you can set it explicitly with UseAzureMonitor(o => o.ConnectionString = "...").

Remark: the Azure.Monitor.OpenTelemetry.Profiler package is still in prerelease, hence the --prerelease flag on the install. If you're already on the classic Microsoft.ApplicationInsights.AspNetCore SDK, note that its current major version is itself an OpenTelemetry-based wrapper under the hood - so if you're not ready to move to the Distro yet, AddServiceProfiler() from Microsoft.ApplicationInsights.Profiler.AspNetCore still works against it without adding the Distro package. The two aren't meant to be combined.

This gets you the same Profiler agent as option 1, just wired up via the OpenTelemetry-based SDK instead of the classic one - the difference is you're now explicit about it in code, which matters if you're running in containers, on Linux, or just prefer configuration-as-code over portal toggles.

Configuring the profiler once it's installed

AddAzureMonitorProfiler() with no arguments gets you the defaults, and the defaults are reasonable for getting started. But once you're running this in a real environment, you'll likely want to tune it - and there are three ways to do that, all pointing at the same underlying settings.

1. appsettings.json

All Profiler settings live under a ServiceProfiler section:

{
  "ServiceProfiler": {
    "Duration": "00:00:30",
    "InitialDelay": "00:00:03"
  }
}

2. Environment variables

Same settings, using __ (double underscore) to separate the section from the key - useful for container deployments where you're injecting config through the environment rather than a file:

export ServiceProfiler__Duration="00:00:30"

3. Directly in code

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .UseAzureMonitor()
    .AddAzureMonitorProfiler(options =>
    {
        options.Duration = TimeSpan.FromSeconds(30);
        options.InitialDelay = TimeSpan.FromSeconds(3);
    });

All three are equivalent - standard ASP.NET Core configuration binding applies, so pick whichever fits how you already manage config in your project.

Remark: don't confuse the ServiceProfiler section with the ApplicationInsights section. Your instrumentation key or connection string still goes under ApplicationInsights - ServiceProfiler is exclusively for tuning the Profiler agent itself.

The options worth knowing about:

Setting Default What it does
Duration 30 seconds How long a profiling session runs
InitialDelay 0 Delay before the first session starts after app startup
BufferSizeInMB 250 Circular buffer size for trace data - a 2-minute session usually produces under 200MB, so raise this if you extend Duration
CPUTriggerThreshold 80.0 CPU usage percentage that triggers a session if sustained for 30+ seconds
MemoryTriggerThreshold 80.0 Same idea, for memory usage
IsDisabled false Kill switch - flip this per-environment instead of removing the package, e.g. to keep the Profiler out of Development
UploadMode OnSuccess Never, OnSuccess, or Always - mainly useful for debugging the Profiler itself, not something you'd normally touch
PreserveTraceFile false Keep the local trace file after upload instead of deleting it
ConfigurationUpdateFrequency 5 seconds How often the agent polls the server for trigger/on-demand config changes

The two I'd actually reach for in practice are CPUTriggerThreshold / MemoryTriggerThreshold and IsDisabled. The trigger thresholds let the Profiler kick in automatically when something's actually under pressure, rather than relying purely on the random sampling window - which matters if your problem is intermittent. And IsDisabled bound to an environment variable is the cleanest way to keep the Profiler off in local development without maintaining a separate code path.

Remark: One constraint worth knowing before you start: codeless enablement on App Service currently only supports Windows. If you're on Linux App Service or containers, the package-based route in option 2 is your path.

What’s next?

Now that we have the profiling up-and-running, it’s time to interpret the results. But we'll leave that for the next post where I explain how to read a flame graph without guessing. We'll take one of the traces this setup produces and actually making sense of it.

Stay tuned!

More information

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

C# DateTimeOffset Formats: ISO 8601, RFC 3339, JSON & Unix Time

1 Share

This is a modern .NET companion to my original post about C# DateTime to RFC3339/ISO 8601.

Date and time become harder to work with as soon as a timestamp leaves your single-language application. In JSON, query strings, headers, logs, and file names, it becomes text or a number that another system has to interpret correctly.

In this article, we'll go through the practical formats for services, TypeScript/JavaScript frontends, logs, and files. For each one, we'll look at how to produce it from a DateTimeOffset and what survives when you parse it back.

In modern .NET code, use DateTimeOffset for timestamps. Unlike DateTime, it stores both the clock time and its UTC offset, which is the information most easily lost at a service boundary.

Every example below formats and parses the same timestamp, a summer afternoon in Central European Summer Time:

var timestamp = new DateTimeOffset(
    year: 2026, month: 7, day: 14,
    hour: 9, minute: 11, second: 30, millisecond: 123,
    offset: TimeSpan.FromHours(2));

DateTimeOffset Formats at a Glance

This overview shows what each format preserves when parsed back:

FormatExample outputParses back to DateTimeOffset
ISO 8601 / RFC 3339 with offset2026-07-14T09:11:30.123+02:00Yes, exactly
RFC 3339 in UTC2026-07-14T07:11:30.123ZYes, as UTC
System.Text.Json default2026-07-14T09:11:30.123+02:00Yes, exactly
.NET round-trip (O)2026-07-14T09:11:30.1230000+02:00Yes, exactly
Unix seconds1784013090Yes, as UTC, without ms
Unix milliseconds1784013090123Yes, as UTC
RFC 1123 (R)Tue, 14 Jul 2026 07:11:30 GMTYes, as UTC, without ms
Sortable (s)2026-07-14T09:11:30No, offset missing
Universal sortable (u)2026-07-14 07:11:30ZYes, as UTC, without ms
Compact UTC20260714T071130.123ZYes, as UTC
Date only2026-07-14No, time and offset missing
Time only09:11:30.123No, date and offset missing
Localized displayTuesday, July 14, 2026 9:11:30 AMNo, offset and ms missing

One of the first four rows is almost always the right answer for an API. Of the .NET standard specifiers, O preserves the offset and all seven fractional digits, R is useful for HTTP headers, s drops the offset, and u converts to UTC. The remaining formats deliberately discard information, which is useful when you intend it, but it's a bug when you don't.

UTC (Coordinated Universal Time) is the global reference time represented by the zero offset +00:00, often written as Z. Converting a timestamp to UTC preserves the exact instant and its available fractional precision in one unambiguous value, which makes it a reliable choice for storage and exchange between systems.

Format a DateTimeOffset as ISO 8601 and RFC 3339

ISO 8601 is a large standard that covers dates, times, UTC, local time with an offset, durations, intervals, and more. It's broad enough that "we use ISO 8601" isn't really a contract on its own. For timestamps exchanged between applications, RFC 3339 defines a much narrower profile of it, and that profile is what most APIs mean when they say ISO 8601.

If you have no other requirement, an RFC 3339 timestamp with an explicit offset is the format to send. It's unambiguous, and almost every language and framework can parse it. RFC 3339 timestamps also sort chronologically as text when they use the same offset representation and the same number of fractional digits.

Use one format for an explicit offset and another when the contract requires UTC with Z:

const string offsetFormat = "yyyy-MM-dd'T'HH:mm:ss.fffK";
const string utcFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'";

var withOffset = timestamp.ToString(offsetFormat, CultureInfo.InvariantCulture);
// 2026-07-14T09:11:30.123+02:00

var inUtc = timestamp.ToUniversalTime().ToString(utcFormat, CultureInfo.InvariantCulture);
// 2026-07-14T07:11:30.123Z

Lowercase fff requires exactly three fractional digits. Use FFFFFFF instead when trailing zeros and the decimal point may be omitted. For a DateTimeOffset, the K token is equivalent to zzz: both write the numeric offset, including +00:00 for UTC.

Only append Z after converting the clock time to UTC. Adding it directly to the example's 09:11:30 moves the timestamp two hours into the future without raising an error.

The offset format parses directly. Since the Z in utcFormat is a literal, the UTC format needs explicit parsing styles:

var utcStyles = DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal;

var parsedOffset = DateTimeOffset.ParseExact(
    withOffset, offsetFormat, CultureInfo.InvariantCulture, DateTimeStyles.None);
// 2026-07-14T09:11:30.1230000+02:00

var parsedUtc = DateTimeOffset.ParseExact(
    inUtc, utcFormat, CultureInfo.InvariantCulture, utcStyles);
// 2026-07-14T07:11:30.1230000+00:00

The offset says how far the clock was from UTC, not which time zone it was in. If the receiver needs the zone itself, send the IANA time zone ID separately. The DateTimeStyles documentation covers the UTC parsing flags used again below.

In the Basic Format for File Names and Logs

ISO 8601 also has a basic format without separators, which works where : isn't allowed or is inconvenient, such as file names, log identifiers, cache keys, and exported data. Fixed-width UTC values sort chronologically as plain text, which is often the real reason to use them:

const string fileFormat = "yyyyMMdd'T'HHmmss.fff'Z'";

var fileTimestamp = timestamp
    .ToUniversalTime()
    .ToString(fileFormat, CultureInfo.InvariantCulture);
// 20260714T071130.123Z

DateTimeOffset.ParseExact(fileTimestamp, fileFormat, CultureInfo.InvariantCulture, utcStyles);
// 2026-07-14T07:11:30.1230000+00:00

DateTimeOffset.Parse doesn't accept this basic format, and it has no format parameter. Use DateTimeOffset.ParseExact with fileFormat and keep the format string next to the code that reads these values back. Keep the field widths and the UTC suffix consistent if you rely on alphabetical sorting, since a single variable-width field breaks the ordering for every file in the directory.

DateTimeOffset in JSON and TypeScript/JavaScript

When a DateTimeOffset is part of a JSON request or response, you usually shouldn't call ToString at all. System.Text.Json reads and writes the extended ISO 8601 profile by default, which is also valid RFC 3339, and it's what ASP.NET Core uses for minimal APIs and controllers out of the box:

var json = JsonSerializer.Serialize(timestamp);
// "2026-07-14T09:11:30.123+02:00"

var parsed = JsonSerializer.Deserialize<DateTimeOffset>(json);
// 2026-07-14T09:11:30.1230000+02:00

The default output trims trailing zeros, so it writes a timestamp with whole seconds as 2026-07-14T09:11:30+02:00. System.Text.Json writes a DateTimeOffset in UTC with +00:00, but a DateTime with DateTimeKind.Utc with Z:

JsonSerializer.Serialize(timestamp.ToUniversalTime()); // "...T07:11:30.123+00:00"
JsonSerializer.Serialize(timestamp.UtcDateTime);       // "...T07:11:30.123Z"

Both are correct RFC 3339 and both parse the same way in .NET and in TypeScript/JavaScript. It only becomes a problem when something on the other side compares timestamp strings for equality, or when a test asserts on the exact text.

If the API contract requires UTC with Z and a fixed precision, add a converter once instead of formatting values by hand throughout the application:

public sealed class UtcDateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
    private const string Format = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'";

    public override DateTimeOffset Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTimeOffset.ParseExact(
            reader.GetString()!,
            Format,
            CultureInfo.InvariantCulture,
            DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
    }

    public override void Write(
        Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(
            value.ToUniversalTime().ToString(Format, CultureInfo.InvariantCulture));
    }
}

Because Read uses ParseExact, this converter accepts only UTC timestamps that end in Z and contain exactly three fractional digits. The default System.Text.Json DateTimeOffset format uses variable fractional precision and writes UTC as +00:00, so every client has to follow this custom contract too.

Register it once with options.Converters.Add(new UtcDateTimeOffsetJsonConverter()). Let the serializer own the wire format, and let a custom converter own any deviation from it.

Read the Value in TypeScript/JavaScript

A browser can read either the default JSON string or Unix milliseconds as the same instant:

const fromText = new Date("2026-07-14T09:11:30.123+02:00");
const fromUnix = new Date(1784013090123);

console.log(fromText.toISOString());                    // 2026-07-14T07:11:30.123Z
console.log(fromText.getTime());                        // 1784013090123
console.log(fromText.getTime() === fromUnix.getTime()); // true

A TypeScript/JavaScript Date stores milliseconds since the epoch, so it uses +02:00 to find the instant and then discards the offset. JSON.stringify writes the result in UTC with Z. If the frontend needs the sender's original offset, send it separately.

There are three traps worth knowing about at this boundary:

  • A date and time without an offset is read as the browser's local time. new Date("2026-07-14T09:11:30") means something different for every visitor, which is exactly what the s format produces.
  • A date-only string is read as UTC instead. new Date("2026-07-14") is midnight UTC, so the same string is inconsistent with the rule above. This is the classic reason a date shows up as the day before for users west of UTC.
  • Unix seconds passed straight to new Date land in January 1970. new Date(1784013090) is 1970-01-21T15:33:33.090Z, because the constructor expects milliseconds.

The ECMAScript Date Time String Format defines exactly three fractional digits. The seven digits produced by O therefore rely on engine-specific fallback parsing. Keep O for .NET-to-.NET text rather than a browser-facing wire format.

Convert a DateTimeOffset to and From Unix Time

Unix time, also called epoch time, counts from 1970-01-01T00:00:00Z and is useful when a contract expects a number. DateTimeOffset has built-in Unix time support in both directions:

var seconds = timestamp.ToUnixTimeSeconds();           // 1784013090
var milliseconds = timestamp.ToUnixTimeMilliseconds(); // 1784013090123

DateTimeOffset.FromUnixTimeSeconds(seconds);
// 2026-07-14T07:11:30.0000000+00:00

DateTimeOffset.FromUnixTimeMilliseconds(milliseconds);
// 2026-07-14T07:11:30.1230000+00:00

The original offset is gone when the value comes back as UTC. Seconds discard the fractional second, while milliseconds discard any finer 100-nanosecond ticks. Agree on the unit, because reading seconds as milliseconds produces a date in January 1970 rather than an error. JWT claims use seconds, while JavaScript Date uses milliseconds.

Calendar Values and Display

Sometimes the value is not a timestamp. Use DateOnly for birthdays and billing days, and TimeOnly for opening times and daily alarms:

var dateAtOffset = DateOnly.FromDateTime(timestamp.DateTime);    // 2026-07-14
var timeAtOffset = TimeOnly.FromDateTime(timestamp.DateTime);    // 09:11:30.123
JsonSerializer.Serialize(dateAtOffset); // "2026-07-14"
JsonSerializer.Serialize(timeAtOffset); // "09:11:30.1230000"

For display, standard formats such as d and F use the supplied CultureInfo:

var culture = CultureInfo.GetCultureInfo("sv-SE");

timestamp.ToString("d", culture); // 2026-07-14
timestamp.ToString("F", culture); // tisdag 14 juli 2026 09:11:30

Choose the relevant offset before extracting a calendar value, and keep display text as output only. A date near midnight may differ between the original offset and UTC. Culture changes the representation, not the time zone, which is a separate conversion using TimeZoneInfo. For a browser-side example using older tools, see Display Local DateTime with Moment.js in ASP.NET.

Parse Input Strings Exactly

When an API accepts more than one representation, TryParseExact validates the input against an array of formats and rejects everything else:

string[] acceptedFormats =
{
    "O",
    "yyyy-MM-dd'T'HH:mm:ss.fffK",
    "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK"
};

if (DateTimeOffset.TryParseExact(
    input, acceptedFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed))
{
    // 2026-07-14T09:11:30.1230000+02:00
}

The more flexible DateTimeOffset.TryParse handles a much wider range of input, which is convenient until it accepts something you did not intend. When the input has no offset, TryParse fills in the offset of the machine that runs it, so the same request parses differently on a developer's laptop and on a server in another region. Pass DateTimeStyles.AssumeUniversal when a missing offset must mean UTC, and use TryParseExact when the accepted formats are part of the contract.

Summary

Keep timestamps as DateTimeOffset inside the application and database, and format them only at system boundaries. SQL Server has a native datetimeoffset type, while strings lose validation, date arithmetic, and reliable sorting across offsets.

  • Text APIs and frontends: use the System.Text.Json default, or a documented RFC 3339 contract. Keep O for .NET-to-.NET text that needs all seven fractional digits.
  • Numeric contracts: use Unix time and agree on seconds or milliseconds.
  • Files and display: use fixed-width UTC for sortable names, and localized text produced from the typed timestamp for people.

Pick a format based on the offset and precision it preserves, parse the same format you write, and supply any missing offset explicitly. A wrong timestamp is still valid, which is why these mistakes rarely raise an error.

For the original DateTime and .NET Framework implementation, see the original article.

Read the whole story
alvinashcraft
16 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Discovered Stacks: One Place for All Your Infrastructure

1 Share

Today we’re launching Discovered Stacks: Pulumi Cloud now models your AWS CloudFormation stacks and Azure Resource Manager deployments as stacks, right alongside your Pulumi IaC stacks. And when you’re ready to bring them under Pulumi management, migration is built in, with every resource tracked until the code provably matches the cloud.

Why: your infrastructure doesn’t live in one tool

Almost nobody’s cloud estate is a single technology. There’s the CloudFormation that came with the AWS account, the ARM templates from the Azure team, the Terraform from an acquisition, and the Pulumi you’re standardizing on. Each tool has its own console, its own grouping, its own idea of state, and no single place shows you everything you run.

That fragmentation is also why migrations stall. Moving a stack to Pulumi has never been the hard part; knowing where you stand is. The tracking lives in a spreadsheet, the spreadsheet goes stale the day it’s written, and six months later nobody can say which of the 800 resources made it across and which were quietly forgotten.

Nothing gets lost

Discovered Stacks gives you confidence that your migration plan or governance efforts will include all resources. This catches a common failure mode where resources are missed by your existing migration scripts or automations. When Pulumi Insights scans your accounts, every CloudFormation stack and ARM deployment becomes a discovered stack, and every resource in it appears as a row with an explicit migration status: ready to migrate, requiring review before migration, or already migrated. Every status is computed from live state on both ends — what Pulumi manages and what the source tool reports — so it’s never a stale annotation someone forgot to update.

Each resource shows its origin type (AWS::S3::Bucket) next to its Pulumi type (aws:s3/bucket:Bucket), with the origin properties side by side with Pulumi’s view, so you can verify that Pulumi sees exactly what your source tool sees before you change anything. Decisions you make along the way (this resource was deleted, that policy is covered by its parent role) are recorded by marking the resource resolved: it stays visible to your whole team, deliberately handled rather than quietly forgotten. The spreadsheet is retired.

The Resources grid of a discovered CloudFormation stack in Pulumi Cloud: each row pairs the Pulumi type (aws:sns:Topic) with its origin type (AWS::SNS::Topic), a Managed By column reading CloudFormation, and a provider link out to the resource in the AWS console.

Migration on your terms

When you’re ready to migrate, the console is where you plan and build confidence. Migrate with Neo hands the job to Pulumi Neo, which imports the resources, reconciles the generated program, and opens a pull request for review. If you prefer local development, Generate Import Commands gives you the raw materials, and the same API lets your own agents drive the flow.

Two things hold regardless of the path. Progress is derived: a resource shows as migrated when it actually exists in the target Pulumi stack, not when someone checks a box. And the quality gate is a zero-diff pulumi preview — the migration is done when the code demonstrably matches your cloud.

Terraform stacks whose state you store in Pulumi Cloud get the same treatment through a new Migration tab, with statuses derived from the Terraform state.

Try it

Open the Stacks page in Pulumi Cloud, turn on Show Discovered Stacks, and your CloudFormation and ARM estates appear next to your IaC. From there:

We’d love to hear how it works on your estate — reach out through Pulumi feedback or your customer success team.

Read the whole story
alvinashcraft
25 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

🧠 MagenticBrain Is Now Supported in ElBruno.LocalLLMs

1 Share
MagenticBrain support hero

⚠ This blog post was created with the help of AI tools. Yes, I used a bit of magic from language models to organize my thoughts and automate the boring parts, but the geeky fun and the 🤖 in C# are 100% mine.

TL;DR


Why this model is important (and what it is designed for)

MagenticBrain is not just another general-purpose chat model. It is designed for agent orchestration: planning multi-step tasks, selecting tools, chaining tool calls across rounds, and deciding when to terminate with a final answer.

That design matters because many app scenarios need more than one prompt/one response:

  • file + web research workflows
  • iterative tool usage with state between turns
  • “do the task, then submit result” orchestration patterns

In short, MagenticBrain is built for agentic execution loops, which is why it is a strong fit for .NET + LocalChatClient + MagenticUI experiences.


When Microsoft Research introduced MagenticLite, MagenticBrain, and Fara1.5, they framed a practical path for local agentic workflows:

For this repo, this post marks the concrete implementation milestone: MagenticBrain is now a first-class supported model in ElBruno.LocalLLMs.


Why this matters for MagenticUI scenarios

The target is a clean .NET workflow where orchestration and model inference stay in the same stack:

  1. Use KnownModels.MagenticBrain in LocalChatClient.
  2. Keep tool-calling and multi-round logic in C#.
  3. Reuse the same model path for local multi-agent UX in MagenticUI-style applications.

This is exactly the scenario behind:

MagenticBrain architecture flow

Basic usage: MagenticBrain in C#

1. Install

dotnet add package ElBruno.LocalLLMs --version 0.20.4

2. Create a local MagenticBrain client

using ElBruno.LocalLLMs;
using Microsoft.Extensions.AI;
var options = new LocalLLMsOptions
{
Model = KnownModels.MagenticBrain,
EnsureModelDownloaded = true,
Temperature = 0.7f,
MaxSequenceLength = 32768
};
using var client = await LocalChatClient.CreateAsync(options);

3. Use it in an agentic loop with tools

var response = await client.GetResponseAsync(
[
new ChatMessage(ChatRole.System, "You are an agentic assistant. Use tools and call submit when done."),
new ChatMessage(ChatRole.User, "List project files and summarize README.")
],
new ChatOptions
{
Tools = tools
});
MagenticBrain agent round lifecycle

Repo samples you can run now

No extra sample project is required for this post: the existing MagenticBrain and MagenticUI samples already cover the runnable story.


Sample app screenshots (MagenticUIServer)

The following screenshots illustrate the sample app flow using the Magentic UI client and agent stream model:

MagenticUI sample connection and task submission
MagenticUI sample multi-round agent progress

You can run the sample from:


Relevant links

https://github.com/elbruno/ElBruno.MagenticUI

NuGet: https://www.nuget.org/packages/ElBruno.LocalLLMs

Repository: https://github.com/elbruno/ElBruno.LocalLLMs

Supported models reference:

https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/supported-models.md

Auto-download guide:

https://github.com/elbruno/ElBruno.LocalLLMs/blob/main/docs/auto-download.md

Official Microsoft announcement:

https://www.microsoft.com/en-us/research/blog/magenticlite-magenticbrain-fara1-5-an-agentic-experience-optimized-for-small-models/

Original MagenticBrain model:

https://huggingface.co/microsoft/MagenticBrain

Published ONNX package used by this repo:

https://huggingface.co/elbruno/MagenticBrain-onnx

Happy coding!

Greetings

El Bruno

More posts in my blog ElBruno.com.

More info in https://beacons.ai/elbruno




Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

How to promote a release from Development to Production With Argo CD and Octopus Deploy

1 Share

In vanilla Argo CD, "promoting to production" is really just editing a YAML file in a different folder and hoping you got it right. You bump an image tag in a production overlay, commit, and trust that what you just wrote matches what you verified in Development.

This is great until an auditor asks, "Who promoted this, and when?" or an incident traces back to a tag nobody meant to change.

Argo CD is excellent at keeping a cluster in sync with Git, but it has no concept of a release, i.e, no single, frozen artifact that moves from one environment to the next under policy.

In this guide, you will connect Argo CD to Octopus Deploy and turn promotion into a governed release, using the same immutable snapshot to move from Development to Production, gated by approval.

The Audit Stream and connection reuse the setup from our EKS connection walkthrough, so this article stays focused on promotion.

Why "promotion" is hard in vanilla Argo CD

Argo CD treats each Application as an independent unit. The dev install of your app and the production install are two separate Applications with no codified relationship between them. Nothing in Argo CD knows that "web in production" should receive exactly what "web in dev" was verified with.

Similarly, depending on your organization or team, promoting to an environment could mean a separate namespace or an entirely new cluster, both of which Octopus Deploy can handle.

That fragmented trail is slow and painful to reassemble at exactly the moments you need it most. Like when an auditor asks who promoted what and when, or when you are mid-incident trying to work out what changed.

Whereas, what you want is a single, frozen release that moves through environments under governance: verified once in Development, promoted unchanged to Production, with the who and when captured automatically.

That leaves two do-it-yourself options for promotion, and both are ad-hoc:

  • Hand-edit the image tag in each environment's overlay folder, commit, and let Argo sync. This is fast, but there is no record of intent, no gate, and nothing stopping a typo from shipping a different tag to Production than the one you tested.
  • Script a pull request per environment. This is more controlled, but now your promotion logic lives in CI YAML and shell, reinvented per team, drifting as the estate grows.

Whereas, what you want is a single, frozen release that moves through environments under governance: verified once in Development, promoted unchanged to Production, with the who and when captured automatically.

Prerequisites

This walkthrough builds on the cluster and Octopus connection from the EKS connection post. You do not need EKS specifically, but you do need these pieces in place before the promotion steps make sense:

  • An Octopus Deploy instance with the Argo CD integration (Octopus Cloud or self-hosted). This is where the project, lifecycle, and release live.
  • A Kubernetes cluster you can install into. A local kind cluster is enough. Because the Octopus gateway dials outbound, no ingress or public address is required.
  • Argo CD running in that cluster, connected to Octopus through the gateway. If you followed the EKS connection post, reuse that same cluster and its gateway connection. If you are starting fresh, the next section installs Argo CD and registers the gateway from scratch.
  • kubectl, helm, and the argocd CLI installed locally.
  • A Git repository for your manifests with Kustomize overlays per environment (the demo uses a public GitHub repo), plus a Git credential in Octopus that can push to it.

The architecture setup

For this demo, we're aiming for a single Kubernetes cluster with two namespaces that serve as environments, dev and production, each with its own Argo CD Application.

Octopus owns the release and promotion process, and Git remains the source of truth, while Argo CD applies manifests to the cluster.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/architecture.png" alt="How Octopus, Git, and Argo CD interact through commits." loading="lazy" }

::figcaption[Octopus commits the new image tag to the right overlay and triggers a sync through an in-cluster gateway. Argo CD pulls from Git and reconciles each namespace and Octopus never needs inbound access to your cluster.]

:::

The Octopus gateway is a small component you install in the cluster with Helm; it dials outbound to Octopus over gRPC, so nothing in your cluster needs a public address. That means this entire demo can run on a local kind cluster with no ingress.

For an in-depth look at the cluster and gateway connection, see the EKS connection post; here, we install Argo CD, register the gateway, and proceed to promotion.

Install Argo CD with a dedicated octopus account so the gateway has its own scoped identity rather than piggybacking on admin:

helm install argocd argo-cd \
 --repo https://argoproj.github.io/argo-helm \
  --create-namespace --namespace argocd --wait --timeout 10m \
 --values - << 'EOF'
configs:
  cm:
    accounts.octopus: apiKey
  rbac:
    policy.default: "role:readonly"
    policy.csv: |
      g, admin, role:admin
      p, octopus, applications, get, *, allow
      p, octopus, applications, sync, *, allow
      p, octopus, clusters, get, *, allow
      p, octopus, logs, get, */*, allow
EOF

With Argo CD running, register the instance in Octopus (Infrastructure, then Argo CD Instances, then Add Argo CD Instance), paste an auth token for the octopus account, and Octopus generates a Helm command for the gateway.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/register-argo-instance.png" alt="Register an Argo CD instance" loading="lazy" }

::figcaption[Registering the Argo CD instance. The service DNS name is the in-cluster address of the Argo CD API server.]

:::

Run the generated Helm command against your cluster, and Octopus confirms the connection: the gateway registers, connects to Octopus, and connects to Argo CD.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/install-gateway.png" alt="Install gateway" loading="lazy" }

::figcaption[The gateway bridges Octopus and Argo CD over an outbound connection. No inbound firewall rules required.]

:::

The gateway bridges Octopus and Argo CD over an outbound connection. No inbound firewall rules required.

Map the Applications with annotations

Octopus needs to know which Argo CD Applications belong to which project and environment. You declare that with two annotations on each Application manifest. No per-application configuration is needed in Octopus; the annotations handle the mapping.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: web-dev
  namespace: argocd
  annotations:
    argo.octopus.com/project: argo-web-promotion
    argo.octopus.com/environment: development
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/gitops-web-promotion
    targetRevision: main
    path: overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: dev
  syncPolicy:
    automated: { prune: true, selfHeal: true }
    syncOptions: [ CreateNamespace=true ]

The argo.octopus.com/project annotation ties the Application to the Octopus project, and argo.octopus.com/environment ties it to an Octopus environment. The production Application is identical except name: web-production, argo.octopus.com/environment: production, path: overlays/production, and namespace: production.

When Octopus deploys argo-web-promotion to Development, it now knows web-dev is the Application to update; when it deploys to Production, it updates web-production.

Both overlays are simple Kustomize folders that set the image tag. This is the field Octopus will rewrite:

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
resources:
 - ../../base
images:
 - name: nginx
    newTag: "1.27.0"

Building the Octopus project with a Dev to Production lifecycle

Create an Octopus project and give it a lifecycle with two phases, Development and Production. The lifecycle is what makes promotion ordered, which simply means a release must pass through Development before it can reach Production.

Then add the built-in Update Argo CD Application Image Tags step to the deployment process. For each Application matched by annotation, this step retrieves the Git location from the Application, updates the image tag in the manifests, commits the change, and triggers Argo CD to sync. Add a container image reference (the nginx image, from a Docker Hub feed) so the release knows which image to update and what version to pin.

To make the governance visible, add one more step before it: a Manual intervention step scoped to the Production environment only. That is your approval gate. It runs when promoting to Production and is skipped for Development, so it stays fast while Production stays governed.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/argo-web-promotion.png" alt="Argo web promotion" loading="lazy" }

::figcaption[Two steps, one governed process. The approval runs only for Production; the image-tag update runs for any environment.]

:::

Create a release and deploy to Development

Create a release in Octopus and select the image version to promote, for example nginx:1.27.2 (a bump from the 1.27.0 currently in the overlays). This release is a frozen snapshot of the process, variables, and package versions. Once created, it is immutable: the version that goes to Production later is the exact version you are about to verify in Development, not whatever happens to sit at Git HEAD.

Deploy the release to Development. Octopus commits the new tag to the dev overlay, and Argo CD syncs the dev namespace:

Credential 'gitops-web-promotion' will be used to access the repository
Committing directly to branch for changes in this environment
Cloning repository https://github.com/your-org/gitops-web-promotion

Within seconds, the dev Application is synced and Healthy on the new tag, while Production is untouched:

$ kubectl get deploy web -n dev -o jsonpath='{..image}'
nginx:1.27.2
$ kubectl get deploy web -n production -o jsonpath='{..image}'
nginx:1.27.0

That contrast is the whole point: the release moved dev to 1.27.2, and Production still runs 1.27.0 because nothing has promoted it there yet.

Promote the same release to Production

Now promote the same release to Production. Because the process has a Production-scoped approval step, the deployment pauses and waits for a human before it touches anything.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/deploy-to-production.png" alt="Promoting the release to Production with an approval step." loading="lazy" }

:::

Production promotion stops at the approval gate; the image update and sync below it are queued, not run.

Approve it, and the same flow runs against the production overlay: Octopus commits the tag to overlays/production, and Argo CD syncs the production namespace. Production now gets exactly what was verified in dev, not a freshly hand-edited value.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/promotion-result.png" alt="Promotion result" loading="lazy" }

::figcaption[Promotion complete. The same release 1.27.2 that ran in Development is now live in Production.]

:::

The project dashboard shows the end state at a glance: one release, both environments, both healthy, with the live status pulled from Argo CD.

:::figure

:img{ src="/blog/img/promote-release-with-argo-cd-and-octopus/dashboard.png" alt="Project dashboard" loading="lazy" }

::figcaption[Development at 9:06 PM, Production at 9:43 PM after approval. Same release, one predictable shape.]

:::

The governance you got for free

Taking a step back, there are a few things this approach has saved you from:

  • An immutable release snapshot. Release 1.27.2 pinned the exact image version. Production could only ever receive what dev verified.

  • A Git commit per environment. Each promotion is a commit in your history, attributable and reversible:

07d62d8  Octopus Deploy promoted image 1.27.2   (production overlay)
28e2ace  Octopus Deploy promoted image 1.27.2   (dev overlay)
dfdadc8  Initial GitOps repo
  • An approval record. Production promotion required a named human to take responsibility and proceed, captured in the deployment history.

  • One view of what is running where. The project dashboard shows every environment and the release it holds, with live health from Argo CD, and you can click into any deployment to see who promoted it and when. That single pane matters more as you scale because your Argo CD Applications might be spread across many instances in different clusters, regions, or accounts, and Octopus gives you one place to see and govern all of them instead of tab-hopping between Argo CD UIs.

None of this is captured by default in the hand-edited-overlay approach. Because the Octopus release is a standard, predictable object, the same governance and policy apply no matter what sits underneath

This ties into the core Platform Hub idea: a single deployment shape and consistent governance across every stack you run.

Going from overlay edits to audited releases

Promotion should not be a YAML edit you hope you got right. With Argo CD connected to Octopus, it becomes a release you can govern.

Argo CD keeps doing what it does best: reconciling Git with your cluster, while Octopus adds a release model and an audit trail to that flow.

The bigger idea here is Platform Hub, which offers you one place to see what is running where, and the same governance and audit across every environment, cluster, and Argo CD instance you run, not just the one in this walkthrough.

If you promote Argo CD deployments by hand today, that is the gap it closes. See how Platform Hub brings your GitOps deployments under one governed roof, read Manage releases and rollbacks with Argo CD for the release mechanics, and start for free!

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

How to Provision an Azure SQL Database

1 Share

Aside from spinning up a SQL Server instance container, the free Azure SQL Database is another great tool for learning SQL. You can even use it for low-traffic or lightweight app. See the documentations for the limits. I will not be responsible for your usage.

That said, let’s provision the database.

Provision an Azure SQL Database

Go to you Azure Portal and search for Azure SQL Database

On the upper left-hand of the UI, click on Create and select SQL database (Free offer).

Configuring an Azure SQL Database is pretty much intutive. For the Server option, use an existing SQL Database Server or create a new one.

Click Review + create to finish the setup.

Connect from VS Code on a MacBook

Go to your Resource Group and find your Azure SQL Database. Or, you can simply search for Azure SQL Database in the search bar again and that will take you to your databases.

Copy the Server name.

Now, open your VS Code (install the mssql extension if you haven’t already). Why VS Code? That’s because Mirosoft will never port SSMS to macOS. That’s why.

Create new connection. Look for the plug icon with the ‘+’ sign next to it.

For the Input type, Browse Azure wouldn’t work for me even if I already took care of the networking setting. Let me know in the comment if you made it work. Using Parameters worked for me.

Paste the Server name. Don’t forget to tick the Trust server certificate. Use SQL login and input the sa user and password that you set when you provisioned your SQL Server.

That should be it. Your Azure SQL Database is now ready to use.

The post How to Provision an Azure SQL Database first appeared on SQL, Code, Coffee, Etc..

The post How to Provision an Azure SQL Database appeared first on SQLServerCentral.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories