.NET · AI Foundry · Architecture · Azure CLI · Azure Cognitive Services · Azure Open Ai · azure-resource-manager-templates · C# · Copilot · Copilot Extension · Copilot-SDK · docker · Entra · GitHub · managed-identity · OAuth 2.0

Building AI Agents on Azure Container Apps with the GitHub Copilot SDK and Your Own Foundry Models

March 2026 · .NET 8+ · GitHub Copilot SDK (Technical Preview)

What if you could get the full agentic power of GitHub Copilot — tool calling, multi-turn sessions, streaming, automatic context compaction — but run it on your own infrastructure, with your own models, and no GitHub dependency at runtime?

That’s exactly what the GitHub Copilot SDK enables through its BYOK (Bring Your Own Key) mode. In this post, I’ll walk through how to stand up a .NET agent service on Azure Container Apps that uses an Azure AI Foundry-hosted model, authenticated via Managed Identity — no API keys, no GitHub tokens.

What is the GitHub Copilot SDK?

The GitHub Copilot SDK is a multi-platform SDK (C#/.NET, TypeScript, Python, Go) currently in technical preview. It gives you programmatic control over the Copilot CLI, which acts as a standalone agent runtime — think of it as the brains behind tool orchestration, session management, and model routing, packaged as a process your app talks to.

Key capabilities (official docs):

CapabilityDescription
Multi-turn conversationsStateful sessions with automatic context window management
Tool / function callingRegister C# methods as tools via Microsoft.Extensions.AI
StreamingLow-latency token-by-token responses
Infinite sessionsBackground context compaction so conversations never hit a token wall
Session hooksIntercept prompts, tool calls, errors — full lifecycle control
BYOKSwap GitHub-hosted models for OpenAI, Azure AI Foundry, Anthropic, or even local Ollama

The SDK communicates with the Copilot CLI over JSON-RPC (stdio or TCP). The CLI can run as a child process spawned by the SDK, or as a headless server that your app connects to over the network — the pattern we’ll use for Azure Container Apps.

📖 For a deeper feature walkthrough, see GitHub Copilot SDK for .NET: Complete Developer Guide by DevLeader and Building Custom AI Tooling with the GitHub Copilot SDK by Benjamin Abt.

Architecture Overview

Here’s the target architecture we’re building:

Key design choices:

  • The Copilot CLI runs as a sidecar container in headless mode — separate lifecycle from your app.
  • Your .NET API connects to the CLI over TCP (CliUrl = "localhost:4321").
  • Authentication to Azure AI Foundry uses Managed Identity via DefaultAzureCredential — no secrets stored anywhere.
  • In BYOK mode, no GitHub token or Copilot subscription is needed. The CLI is purely an agent runtime.

Step-by-Step Setup

Step 1: Create the .NET Project

dotnet new webapi -n CopilotAgent
cd CopilotAgent
dotnet add package GitHub.Copilot.SDK
dotnet add package Azure.Identity
dotnet add package Microsoft.Extensions.AI

Step 2: Build the Agent Service

Create a service that manages Copilot sessions with automatic token refresh:

// Services/CopilotAgentService.cs
using Azure.Identity;
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;
using System.ComponentModel;

public class CopilotAgentService : IAsyncDisposable
{
    private readonly CopilotClient _client;
    private readonly DefaultAzureCredential _credential = new();
    private readonly string _foundryUrl;
    private readonly string _model;

    private const string CognitiveServicesScope =
        "https://cognitiveservices.azure.com/.default";

    public CopilotAgentService(IConfiguration config)
    {
        _foundryUrl = config["AzureAIFoundry:Endpoint"]!.TrimEnd('/');
        _model = config["AzureAIFoundry:Model"] ?? "gpt-4.1";

        _client = new CopilotClient(new CopilotClientOptions
        {
            CliUrl = config["CopilotCli:Url"] ?? "localhost:4321",
            UseStdio = false,
        });
    }

    public async Task<string?> RunAgentAsync(string prompt, string sessionId)
    {
        // Get a fresh bearer token from Managed Identity
        var token = await _credential.GetTokenAsync(
            new Azure.Core.TokenRequestContext(
                new[] { CognitiveServicesScope }));

        await using var session = await _client.CreateSessionAsync(new SessionConfig
        {
            SessionId = sessionId,
            Model = _model,
            Provider = new ProviderConfig
            {
                Type = "openai",
                BaseUrl = $"{_foundryUrl}/openai/v1/",
                BearerToken = token.Token,
                WireApi = "responses",
            },
            // Register custom tools the agent can call
            Tools =
            [
                AIFunctionFactory.Create(
                    ([Description("Search query")] string query) =>
                        SearchInternalDocs(query),
                    "search_docs",
                    "Search internal documentation"),
            ],
        });

        var response = await session.SendAndWaitAsync(
            new MessageOptions { Prompt = prompt });

        return response?.Data.Content;
    }

    private static string SearchInternalDocs(string query)
    {
        // Your implementation here
        return $"Results for: {query}";
    }

    public async ValueTask DisposeAsync()
    {
        await _client.StopAsync();
    }
}

Step 3: Wire Up the API Endpoint

// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<CopilotAgentService>();

var app = builder.Build();

app.MapPost("/api/chat", async (
    CopilotAgentService agent,
    ChatRequest request) =>
{
    var sessionId = $"session-{request.UserId}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
    var result = await agent.RunAgentAsync(request.Message, sessionId);
    return Results.Ok(new { content = result });
});

app.Run();

record ChatRequest(string UserId, string Message);

Step 4: Configure appsettings.json

{
  "AzureAIFoundry": {
    "Endpoint": "https://your-resource.openai.azure.com",
    "Model": "gpt-4.1"
  },
  "CopilotCli": {
    "Url": "localhost:4321"
  }
}

Step 5: Create the Dockerfile

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
EXPOSE 8080
ENTRYPOINT ["dotnet", "CopilotAgent.dll"]

Step 6: Deploy Infrastructure with Bicep

Here are the key Bicep fragments for the Azure resources. These are the important pieces — wire them into your existing IaC as needed.

6a. Azure AI Foundry (Cognitive Services) Resource

// infra/ai-foundry.bicep

param location string = resourceGroup().location
param aiFoundryName string

resource aiFoundry 'Microsoft.CognitiveServices/accounts@2024-10-01' = {
  name: aiFoundryName
  location: location
  kind: 'OpenAI'
  sku: { name: 'S0' }
  properties: {
    customSubDomainName: aiFoundryName
    publicNetworkAccess: 'Enabled'
  }
}

// Deploy a model
resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = {
  parent: aiFoundry
  name: 'gpt-4-1'
  sku: {
    name: 'Standard'
    capacity: 30
  }
  properties: {
    model: {
      format: 'OpenAI'
      name: 'gpt-4.1'
    }
  }
}

output aiFoundryEndpoint string = aiFoundry.properties.endpoint
output aiFoundryId string = aiFoundry.id

6b. Container Apps Environment and App with Managed Identity

// infra/container-app.bicep

param location string = resourceGroup().location
param containerAppName string
param environmentName string
param aiFoundryEndpoint string
param aiFoundryId string
param acrLoginServer string
param acrName string

// Container Apps Environment
resource containerEnv 'Microsoft.App/managedEnvironments@2024-03-01' = {
  name: environmentName
  location: location
  properties: {
    appLogsConfiguration: {
      destination: 'azure-monitor'
    }
  }
}

// Azure Files storage for session state
resource sessionStorage 'Microsoft.App/managedEnvironments/storages@2024-03-01' = {
  parent: containerEnv
  name: 'session-state'
  properties: {
    azureFile: {
      accountName: storageAccount.name
      accountKey: storageAccount.listKeys().keys[0].value
      shareName: 'copilot-sessions'
      accessMode: 'ReadWrite'
    }
  }
}

// The Container App
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
  name: containerAppName
  location: location
  identity: {
    type: 'SystemAssigned'   // Managed Identity — no secrets needed
  }
  properties: {
    managedEnvironmentId: containerEnv.id
    configuration: {
      ingress: {
        external: true
        targetPort: 8080
      }
      registries: [
        {
          server: acrLoginServer
          identity: 'system'
        }
      ]
    }
    template: {
      // --- Main container: your .NET API ---
      containers: [
        {
          name: 'api'
          image: '${acrLoginServer}/copilot-agent:latest'
          resources: {
            cpu: json('0.5')
            memory: '1Gi'
          }
          env: [
            { name: 'AzureAIFoundry__Endpoint', value: aiFoundryEndpoint }
            { name: 'AzureAIFoundry__Model',    value: 'gpt-4.1' }
            { name: 'CopilotCli__Url',          value: 'localhost:4321' }
          ]
        }
        // --- Sidecar container: Copilot CLI in headless mode ---
        {
          name: 'copilot-cli'
          image: 'ghcr.io/github/copilot-cli:latest'
          args: [ '--headless', '--port', '4321' ]
          resources: {
            cpu: json('0.5')
            memory: '1Gi'
          }
          volumeMounts: [
            {
              volumeName: 'session-state'
              mountPath: '/root/.copilot/session-state'
            }
          ]
        }
      ]
      volumes: [
        {
          name: 'session-state'
          storageName: 'session-state'
          storageType: 'AzureFile'
        }
      ]
      scale: {
        minReplicas: 1
        maxReplicas: 5
        rules: [
          {
            name: 'http-scaling'
            http: { metadata: { concurrentRequests: '20' } }
          }
        ]
      }
    }
  }
}

// Grant Managed Identity access to Azure AI Foundry
resource cognitiveServicesRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(containerApp.id, aiFoundryId, 'CognitiveServicesUser')
  scope: resourceGroup()
  properties: {
    roleDefinitionId: subscriptionResourceId(
      'Microsoft.Authorization/roleDefinitions',
      'a97b65f3-24c7-4388-baec-2e87135dc908'  // Cognitive Services User
    )
    principalId: containerApp.identity.principalId
    principalType: 'ServicePrincipal'
  }
}

The critical thing to notice above: there are no API keys or GitHub tokens in any environment variable. The system-assigned Managed Identity handles authentication to Azure AI Foundry, and BYOK mode means no GitHub auth is needed.

6c. Supporting Storage Account

// infra/storage.bicep

param location string = resourceGroup().location
param storageAccountName string

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: { name: 'Standard_LRS' }
  kind: 'StorageV2'
  properties: {}
}

resource fileService 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = {
  parent: storageAccount
  name: 'default'
}

resource fileShare 'Microsoft.Storage/storageAccounts/fileServices/shares@2023-01-01' = {
  parent: fileService
  name: 'copilot-sessions'
  properties: {
    shareQuota: 5   // GB
  }
}

BYOK: Bring Your Own Foundry Model

The BYOK model is the core enabler for this entire architecture. Here’s how the authentication flow works without any GitHub dependency:

  1. Your app calls DefaultAzureCredential.GetTokenAsync() — which uses the Container App’s Managed Identity automatically.
  2. Entra ID returns a short-lived bearer token (~1 hour).
  3. The SDK session passes the token to Azure AI Foundry via the BearerToken field in ProviderConfig.
  4. Model responses flow back through the Copilot CLI agent runtime to your app.

No GitHub token. No API key. No secrets in config.

The Code — Minimal BYOK Session

using Azure.Identity;
using GitHub.Copilot.SDK;

var credential = new DefaultAzureCredential();
var token = await credential.GetTokenAsync(
    new Azure.Core.TokenRequestContext(
        new[] { "https://cognitiveservices.azure.com/.default" }));

await using var client = new CopilotClient(new CopilotClientOptions
{
    CliUrl = "localhost:4321",
    UseStdio = false,
});

await using var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    Provider = new ProviderConfig
    {
        Type = "openai",
        BaseUrl = "https://your-resource.openai.azure.com/openai/v1/",
        BearerToken = token.Token,
        WireApi = "responses",
    },
});

var response = await session.SendAndWaitAsync(
    new MessageOptions { Prompt = "Summarize our Q1 sales data." });
Console.WriteLine(response?.Data.Content);

What You Keep vs. What You Lose

✅ You Keep⚠️ You Lose
Full agent runtime (tool calling, hooks)GitHub-specific context awareness
Multi-turn sessions with compactionCopilot-tuned model optimizations
Streaming responsesBuilt-in copyright/security filters
System message customizationGitHub skill integrations
File & image attachments

For enterprise workloads where you need data residencycost control, and compliance — this is the right trade-off. You own the model, you own the data flow, and you still get a world-class agent runtime.

📖 Full BYOK documentation: Official BYOK Setup Guide · Azure Managed Identity Guide


Conclusion

The GitHub Copilot SDK’s BYOK mode unlocks a powerful pattern: use the battle-tested Copilot agent runtime with your own models, on your own infrastructure, with zero GitHub runtime dependency.

Here’s what makes this combination compelling:

  • Azure AI Foundry gives you enterprise-grade model hosting with full control over data residency, fine-tuning, and model selection.
  • Managed Identity eliminates secrets management entirely – DefaultAzureCredential just works in Azure Container Apps.
  • The Copilot SDK provides the agent runtime you’d otherwise have to build from scratch: session management, tool calling, context compaction, streaming, and hooks.
  • Azure Container Apps gives you serverless container hosting with built-in scaling, ingress, and volume mounts for session state.

The result is an AI agent service that’s secure by default, scales automatically, and costs you only what your Azure model usage incurs — no GitHub Copilot subscription required.

⚠️ Note: The GitHub Copilot SDK is in technical preview. APIs may change. Watch the releases page and test thoroughly before production use.

Further Reading

Leave a comment