Add Multi repo support #1137
Replies: 55 comments 71 replies
|
Clone multiple repos locally, create an Uber solution and add the apphost to it, wiring up all of the projects required. That's where I would start. it you don’t want to pull and run the other repos locally, then I’m not sure exactly what the workflow would be (point to something already running). |
|
Some patterns I imagine might emerge:
|
|
I believe we would need to publish our service defaults as a NuGet package, published to our own private NuGet server (Azure Artifacts in our case). Then projects throughout all the different repos could add a reference to this service defaults NuGet package. |
Yep, or you could put the ServiceDefaults in a sub-module and include it that way. There are lots of ways for folks to share code and assets among teams and I don't think we want to be too prescriptive about it. We should ensure we're flexible enough to support different patterns. |
|
Damian Edwards (@DamianEdwards) would be nice to have some samples on how to proceed with multi-repo scenarios. It is definitely a very cool and useful tool, but a huge part of microservice solutions are split across different repos, and in this case its not that obvious how to utilize it in local environment. |
|
I also don't understand how the deployment story is in this scenario, where we "run" our k8s cluster with a custom cloud platform team. For instance, we use github + tekton pipelines + argo CD to deploy to k8s using multiple repos (one per service), all things k8s related are generated/managed by the platform (I've worked in previous companies with similar setup bu using different tools like gitlab as an example) How Aspire can be helpful in this cases? |
Are you cloning everyone's repositories and running them today? |
It sounds like you don't need to use the deployment part of aspire because you have already figured it out! It's still great for local development, using components, and service discovery! |
|
David Fowler (@davidfowl) Yes, we clone all the repositories locally and debug service communication having multiple studio instances opened. Its doable, but its a pain. |
|
Artyom (@artyom-p) What does the pain free solution look like? What would you ideal experience be? |
|
David Fowler (@davidfowl) Let's say we have 2 microservices that need to communicate with each other, they live in their own git repositories. Each microservice can have its own or shared dependencies, for example: There are cases when you want to debug only one service, but you still need another one to be running with all its dependencies, in this case, we can just somehow reference that second service and make it run, I think there was something similar in Tye before if I'm not mistaken. The second case is more complex - when you want to debug both services. I'm not sure if it's doable without referencing projects of ServiceB in ServiceA solution. |
|
We also have the same multi-repo approach here, and this is where Tye was really strong for local development. A tye.yaml file could be relatively pathed to target x services, all cloned locally. Tye was also useable as a completely external tool, no changes were required to any service, and they could also target a variety of different dotnet versions. Aspire looks so much better in many ways to Tye, and I can see it's trying to achieve different things, but being able to use Tye as a non-invasive external local dev orchestrator remains a huge benefit and time saver for multi-repo scenarios. |
You can do a similar thing with the app host. Clone multiple repositories and create a project file that references individual projects. Maybe we need to enable referencing app hosts themselves 🤔.
Aspire does require .NET 8 and above.
Can you show me your tye setup? |
|
Many thanks for the reply, really enjoying the Conf this year.
You say that, and of course you're totally correct if you want to use everything in the Aspire stack (and who am I to argue with the mad professors who came up with this amazing stuff), but I have been able to hack something together today from the starter application template using AddExecutable(): In this case, ApiService6 is a dotnet6 api, inside which I've added OpenTelemetry support based on the code in the ServiceDefaults project. You do then end up with traces and (partial) metrics in the Asipre dashboard. So this is potentially useful-ish (I should try this with func.exe as well to see if we can spin up Functions in a decent way while Function support isn't quite ready yet). Service discovery is obvs broken here, and Aspire Components are presumably off the table too. This serves two purposes though - allows some use of older dotnet applications, and also allows targeting of different repos outside of the solution the Aspire AppHost lives in. So it falls back to a similar kind of experience to Tye in these respects.
Sure, we have a lot of tye files, each targeting a specific surface area of our microservices (which are all in individual repos). Redacted example below: Key thing here is that we haven't made any code changes to any of these services, and they're spread over 4 different repos. |
|
Artyom (@artyom-p) I've opened microsoft/aspire-samples#35 to track adding some samples of multi-repo orchestration from an AppHost project. |
|
Love it. Something like Although I agree it has an out-of-scopey flavour, I could see it being a very popular feature or extension. We'll see 🙂 |
|
Hey, I've been working on a project that I think may be useful for some here. Please feel free to send feedback and let me know what you think. |
|
So... after a few months, no further progress on multi-repo support? Is it already implemented? |
|
Not directly a multi-repo problem, but I have multiple solutions in the same repo. To reference an "external" project in the repo, I tried to use: |
|
Hello, Currently, our team uses a microservice architecture where each microservice resides in its own repository and is maintained by a dedicated team. We also use Docker Compose for each microservice to manage running the API, database, Redis, RabbitMQ, etc. Most of the time, each team deals only with their own microservice, but occasionally, they run all the microservices locally for end-to-end testing. To facilitate this, we have created another Docker Compose file that runs all the microservices along with their dependencies. Now, my question is: if we decide to go with Aspire, the use case where we want to run all the microservices locally works fine using one Uber AppHost solution and sharing ServiceDefaults as a NuGet package. But how would you suggest we run individual microservices (that currently use Docker Compose)? Besides the Uber AppHost, should we also have an AppHost per microservice that stays in the same repository as the microservice? Or is there another solution anyone would suggest? Thanks. |
|
Just to add to the multi-repo thing (using an Uber solution), being able to control provisioned resource name in Aspire 9 goes a long way (if not all the way for me) towards making Aspire work well with multiple repos and single/centralized resources. While it's not baked in connecting to another repo and spinning it up as a container as some will be after, if you're happy just adding projects from other solutions where required (vs 2022 does a good job with multi-repo solutions) and having an Uber solution for deployment, with fixed resource names you can:
More detail here: #6484 |
|
I have five microservices in different Azure repositories. I created a new repository for the Aspire solution. After that, I added the projects to the Aspire solution, and everything worked very well. The deployment to Azure App Container also worked using azd init and azd up. However, I encountered a problem: in the Azure pipeline, I can't figure out how to replicate this process. ProjectFolder |
|
Edit: One of my goals was to explicitly avoid bringing in the other projects as real projects. In our case, we don't want all that metadata bogging down the IDE. What I'm considering for cross-repo debugging is having the AppHost in the "Stack" repo optionally point at host endpoints, and then the user can open multiple IDEs as they see fit. This is what I am doing, though how much Aspire could further help is questionable - but maybe could inspire something builtin? "Stack" RepoThis repo contains two projects: Stack and AppHost. AppHost is a bare-bones host that contains a non-Aspire reference to Stack, which contains basically everything else. <ItemGroup>
<ProjectReference Include="..\Stack\Stack.csproj" IsAspireProjectResource="false"/>
</ItemGroup>The second project has its own builder private IResourceBuilder<IResource>? _myProject1;
private IResourceBuilder<IResource>? _myProject2;
public StackBuilder WithMyProject1<T>(
Action<IResourceBuilder<AzureFunctionsProjectResource>>? configure = null)
where T : IProjectMetadata, new()
{
var res = _dab.AddAzureFunctionsProject<T>();
configure?.Invoke(res);
_myProject1 = res;
}
public StackBuilder WithMyProject2<T>(
Action<IResourceBuilder<ProjectResource>>? configure = null)
where T : IProjectMetadata, new()
{
var res = _dab.AddProject<T>();
configure?.Invoke(res);
_myProject2 = res;
}
// etc.The stack builder will do the following for each project: if (_myProject1 == null) {
// Consider allowing the tag to be configurable, so that e.g. containers from PRs can be used
_myProject1 = _dab.AddContainer(/* ... */);
}
if (_myProject2 == null) {
_myProject2 = _dab.AddContainer(/* ... */);
}
ApplyMyProject1Config(_myProject1);
ApplyMyProject1Config(_myProject2);This is where we could see some improvements to the API. You'll notice that the fields are _myProject1.AsResource<IResourceWithEnvironment>().Inspect(v => v.WithEnvironment(/* ... */))At this point the stack repo should be capable of bringing up any combination of the repos, where each is either the real project or a container. The The "MyProject1" Repo (i.e. each micro repo)This repo has its own var stack = new StackBuilder(args);
stack.WithMyProject1<Projects.MyProject1>();
stack.Build().Run();Thereby disabling the container and using the real project. This repo also has to publish "MyProject1" as a container image, so that the |
|
Same multi-repo requirement here. We have some scenarios where I would like to run 5-6 micro-services locally (coordinated with Aspire) and I want to be able to attach a debugger to one or more of these services during the execution. So I created a simple solution containing a single project with the AppHost. The AppHost would call a AddProject(path_to_external_service_project) for each service. This works well, but I cannot attach a debugger to one of my service because Aspire is already attached to my services. Is there a way to avoid this? I tried running Aspire outside of a debugger (dotnet run --project AsppHost.pcsproj), but got the same behavior. This link seems to imply that this is not yet implemented? AppHost configuration to not attach debugger to a project #4761 |
|
With Aspire now supporting multi‑language scenarios, multi‑repo support has become even more valuable for us. In our enterprise environment, services are distributed across multiple repositories, managed by different teams, and implemented in various languages (.NET, NodeJS, Java). To deploy a complete solution (whether locally, on‑premises, or on Azure) we need to connect all these services, and Aspire’s multi‑repo orchestration would make that possible, even for local development. Is multi‑repo support already planned on the mid/short‑term roadmap? |
|
See https://devblogs.microsoft.com/aspire/aspire-windows-365/ for windows 365 integration of multi-repo support based on existing aspire primitive with container resources as microservice resource abstraction |
|
We have an independent repository for the Frontend (React/Next/TS) and another for the Backend: C# and and .NET with several services. |
|
David Fowler (@davidfowl) I have the same problem as a1-jesper I got Codex to write a script that wraps my three repos together so I can atleast launch them together with this script.
I still end up with three Aspire dashboards I would love if Aspire had multi-repo support in natively and I had one Aspire start command and one dashboard for the services living in different repos. param(
[string] $Root,
[string] $MaesterCloudPath,
[string] $WebsitePath,
[string] $KermitPath,
[switch] $NoKillPorts,
[switch] $UseCurrentTerminal
)
$ErrorActionPreference = "Stop"
function Resolve-RepoPath {
param(
[string] $ExplicitPath,
[string] $RepoName
)
if (-not [string]::IsNullOrWhiteSpace($ExplicitPath)) {
return (Resolve-Path $ExplicitPath).Path
}
$scriptRoot = Split-Path -Parent $PSCommandPath
$repoRoot = Resolve-Path (Join-Path $scriptRoot "..")
$siblingRoot = Split-Path -Parent $repoRoot.Path
if (-not [string]::IsNullOrWhiteSpace($Root)) {
$siblingRoot = (Resolve-Path $Root).Path
}
$candidate = Join-Path $siblingRoot $RepoName
if (-not (Test-Path $candidate)) {
throw "Could not find $RepoName at $candidate. Pass an explicit path parameter or -Root."
}
return (Resolve-Path $candidate).Path
}
function Assert-Command {
param([string] $CommandName)
if (-not (Get-Command $CommandName -ErrorAction SilentlyContinue)) {
throw "Required command '$CommandName' was not found on PATH."
}
}
function Get-PortProcessIds {
param([int] $Port)
if ($IsWindows) {
return @(Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty OwningProcess -Unique)
}
$output = & lsof -tiTCP:$Port -sTCP:LISTEN 2>$null
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($output)) {
return @()
}
return @($output -split "\s+" | Where-Object { $_ } | ForEach-Object { [int]$_ } | Select-Object -Unique)
}
function Stop-Ports {
param([hashtable] $Ports)
if ($NoKillPorts -or $UseCurrentTerminal) {
return
}
foreach ($entry in $Ports.GetEnumerator() | Sort-Object Name) {
$port = [int]$entry.Value
$pids = @(Get-PortProcessIds -Port $port | Where-Object { $_ -and $_ -ne $PID })
if (-not $pids.Count) {
continue
}
Write-Host "Stopping process(es) on port $port ($($entry.Name)): $($pids -join ', ')"
foreach ($processId in $pids) {
try {
Stop-Process -Id $processId -Force -ErrorAction Stop
}
catch {
if (-not $IsWindows) {
& kill -TERM $processId 2>$null
Start-Sleep -Milliseconds 300
if (Get-Process -Id $processId -ErrorAction SilentlyContinue) {
& kill -KILL $processId 2>$null
}
}
}
}
}
}
function New-CommandFile {
param(
[string] $Name,
[string] $WorkingDirectory,
[string] $Command
)
$safeName = $Name -replace "[^A-Za-z0-9_.-]", "-"
$path = Join-Path ([IO.Path]::GetTempPath()) "maester-cloud-dev-$safeName.command"
$content = @"
#!/bin/zsh
set -e
cd "$WorkingDirectory"
printf '\033]0;$Name\007'
echo "==> $Name"
echo "cd $WorkingDirectory"
echo "$Command"
echo
$Command
"@
Set-Content -Path $path -Value $content -Encoding UTF8
chmod +x $path
return $path
}
function Start-AspireWindow {
param(
[string] $Name,
[string] $WorkingDirectory,
[string] $LaunchProfile = "http"
)
$command = "ASPIRE_ALLOW_UNSECURED_TRANSPORT=true ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true aspire start --launch-profile $LaunchProfile"
if ($UseCurrentTerminal) {
Write-Host ""
Write-Host "[$Name] $WorkingDirectory"
Write-Host $command
return
}
if ($IsMacOS) {
$commandFile = New-CommandFile -Name $Name -WorkingDirectory $WorkingDirectory -Command $command
& open -a Terminal $commandFile
return
}
if ($IsWindows) {
Start-Process pwsh -ArgumentList @(
"-NoExit",
"-Command",
"Set-Location '$WorkingDirectory'; `$Host.UI.RawUI.WindowTitle = '$Name'; $command")
return
}
if (Get-Command gnome-terminal -ErrorAction SilentlyContinue) {
& gnome-terminal --title $Name --working-directory $WorkingDirectory -- zsh -lc "$command; exec zsh"
return
}
throw "No supported terminal launcher found. Rerun with -UseCurrentTerminal to print commands."
}
Assert-Command "aspire"
if (-not $IsWindows) {
Assert-Command "lsof"
}
$maesterCloud = Resolve-RepoPath -ExplicitPath $MaesterCloudPath -RepoName "maester-cloud"
$website = Resolve-RepoPath -ExplicitPath $WebsitePath -RepoName "maester-cloud-website"
$kermit = Resolve-RepoPath -ExplicitPath $KermitPath -RepoName "maester-cloud-kermit"
$ports = @{
"maester-cloud-dashboard" = 18888
"maester-cloud-otlp" = 18889
"maester-cloud-portal" = 5173
"website-dashboard" = 18890
"website-otlp" = 18891
"website" = 4321
"website-api" = 7071
"kermit-dashboard" = 18892
"kermit-otlp" = 18893
"kermit" = 7073
"azurite-blob" = 10000
"azurite-queue" = 10001
"azurite-table" = 10002
}
if ($UseCurrentTerminal) {
Write-Host "Dry run: not stopping ports. Normal startup stops occupied fixed ports unless -NoKillPorts is used."
}
else {
Stop-Ports -Ports $ports
}
Write-Host "Starting Aspire for each Maester Cloud repo..."
Write-Host " maester-cloud: $maesterCloud"
Write-Host " maester-cloud-website: $website"
Write-Host " maester-cloud-kermit: $kermit"
Start-AspireWindow -Name "maester-cloud" -WorkingDirectory $maesterCloud -LaunchProfile "http-fallback"
Start-Sleep -Milliseconds 500
Start-AspireWindow -Name "maester-cloud-website" -WorkingDirectory $website
Start-Sleep -Milliseconds 500
Start-AspireWindow -Name "maester-cloud-kermit" -WorkingDirectory $kermit
Write-Host ""
if ($UseCurrentTerminal) {
Write-Host "Printed 3 aspire start command(s)."
}
else {
Write-Host "Started 3 Aspire window(s)."
}
Write-Host ""
Write-Host "Useful local URLs:"
Write-Host " maester-cloud dashboard: http://localhost:18888"
Write-Host " maester-cloud customer portal: http://localhost:5173"
Write-Host " website dashboard: http://localhost:18890"
Write-Host " website: http://localhost:4321"
Write-Host " website signup API: http://localhost:7071/api/health"
Write-Host " Kermit dashboard: http://localhost:18892"
Write-Host " Kermit: http://localhost:7073"
|
|
With #18581 for Aspire Roadmap (2026 → 2027), I see this multi-repo support is on the plan as below
looking forward to more detail shared for this support. |
|
This is my take on multi repo support: https://github.com/shirubasoft/aspire-modular-apphosts Probably overly complex but it is based on real needs, I hope the team can take a look at this and make some of it possible. |


Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
In several companies that use microservices have their own cloud platform teams and normally this "translates" into having a service per git repos.
In this cases how to use .NET Aspire? Meaning, different projects from different repos? It's not common to have multiple services within the same repo (unless you have a monorepo like Google 😆)
What is your suggestion?
All reactions