-
Notifications
You must be signed in to change notification settings - Fork 155
Add comprehensive documentation for ExecutableResource and AddExecutable API #4188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cdc178a
5f5a0d0
abf751c
e6c968e
cb941af
315ea45
96aec56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,237 @@ | ||||||
--- | ||||||
title: Host external executables in .NET Aspire | ||||||
description: Learn how to use ExecutableResource and AddExecutable to host external executable applications in your .NET Aspire app host. | ||||||
ms.date: 08/04/2025 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
--- | ||||||
|
||||||
# Host external executables in .NET Aspire | ||||||
|
||||||
In .NET Aspire, you can host external executable applications alongside your .NET projects using the <xref:Aspire.Hosting.ExecutableResourceBuilderExtensions.AddExecutable%2A> method. This capability is useful when you need to integrate non-.NET applications or tools into your distributed application, such as Node.js applications, Python scripts, or specialized CLI tools. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this isn't necessarily untrue, I'd like to make this intro even more broad, without the .NET fixation. For example, instead of "alongside your .NET projects" say "alongside your projects. And, instead of "integration non-.NET applications" just say "integrate executable applications" for example. |
||||||
|
||||||
## When to use executable resources | ||||||
|
||||||
Use executable resources when you need to: | ||||||
|
||||||
- Host non-.NET applications that don't have containerized equivalents. | ||||||
- Integrate command-line tools or utilities into your application. | ||||||
- Run external processes that other resources depend on. | ||||||
- Develop with tools that provide local development servers. | ||||||
|
||||||
Common examples include: | ||||||
|
||||||
- **Frontend development servers**: Tools like [Vercel CLI](https://vercel.com/docs/cli), Vite, or webpack dev server. | ||||||
- **Language-specific applications**: Node.js apps, Python scripts, or Go applications. | ||||||
- **Database tools**: Migration utilities or database seeders. | ||||||
- **Build tools**: Asset processors or code generators. | ||||||
|
||||||
## Basic usage | ||||||
|
||||||
The <xref:Aspire.Hosting.ExecutableResourceBuilderExtensions.AddExecutable%2A> method requires a resource name, the executable path, and optionally command-line arguments and a working directory: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
// Basic executable without arguments | ||||||
var nodeApp = builder.AddExecutable("frontend", "node", ".", "server.js"); | ||||||
|
||||||
// Executable with command-line arguments | ||||||
var pythonApp = builder.AddExecutable("api", "python", ".", "-m", "uvicorn") | ||||||
.WithArgs("main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"); | ||||||
|
||||||
builder.Build().Run(); | ||||||
``` | ||||||
|
||||||
This code demonstrates setting up a basic executable resource. The first example runs a Node.js server script, while the second starts a Python application using Uvicorn with specific configuration options. | ||||||
|
||||||
## Configure command-line arguments | ||||||
|
||||||
You can provide command-line arguments in several ways: | ||||||
|
||||||
### Arguments in the AddExecutable call | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
// Arguments provided directly in AddExecutable | ||||||
var app = builder.AddExecutable("vercel-dev", "vercel", ".", "dev", "--listen", "3000"); | ||||||
``` | ||||||
|
||||||
### Dynamic arguments with WithEnvironment | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This heading is no longer accurate. Please correct it. In fact, you might consider folding this section into the next section. |
||||||
|
||||||
For arguments that depend on other resources, use environment variables: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var database = builder.AddPostgres("postgres").AddDatabase("db"); | ||||||
|
||||||
var migrator = builder.AddExecutable("migrator", "dotnet", ".", "run") | ||||||
.WithReference(database); | ||||||
``` | ||||||
|
||||||
IEvangelist marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
When one resource depends on another, `WithReference` passes along environment variables containing the dependent resource's connection details. For example, the `migrator` executable's reference to the `database` provides it with the `ConnectionStrings__db` environment variable, which contains the database connection string. | ||||||
|
||||||
## Work with resource dependencies | ||||||
|
||||||
Executable resources can reference other resources and access their connection information: | ||||||
|
||||||
### Basic resource references | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var redis = builder.AddRedis("cache"); | ||||||
var postgres = builder.AddPostgres("postgres").AddDatabase("appdb"); | ||||||
|
||||||
var app = builder.AddExecutable("worker", "python", ".", "worker.py") | ||||||
.WithReference(redis) // Provides ConnectionStrings__cache | ||||||
.WithReference(postgres); // Provides ConnectionStrings__appdb | ||||||
``` | ||||||
|
||||||
### Access specific endpoint information | ||||||
|
||||||
For more control over how connection information is passed to your executable: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var redis = builder.AddRedis("cache"); | ||||||
|
||||||
var app = builder.AddExecutable("app", "node", ".", "app.js") | ||||||
.WithReference(redis) | ||||||
.WithEnvironment(context => | ||||||
{ | ||||||
// Provide individual connection details | ||||||
context.EnvironmentVariables["REDIS_HOST"] = redis.Resource.PrimaryEndpoint.Property(EndpointProperty.Host); | ||||||
context.EnvironmentVariables["REDIS_PORT"] = redis.Resource.PrimaryEndpoint.Property(EndpointProperty.Port); | ||||||
context.EnvironmentVariables["REDIS_URL"] = $"redis://{redis.Resource.PrimaryEndpoint}"; | ||||||
}); | ||||||
``` | ||||||
|
||||||
## Practical example: Vercel CLI | ||||||
|
||||||
Here's a complete example using the [Vercel CLI](https://vercel.com/docs/cli) to host a frontend application with a backend API: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
// Backend API | ||||||
var api = builder.AddProject<Projects.Api>("api") | ||||||
.WithExternalHttpEndpoints(); | ||||||
|
||||||
// Frontend with Vercel CLI | ||||||
var frontend = builder.AddExecutable( | ||||||
"vercel-dev", "vercel", ".", "dev", "--listen", "3000") | ||||||
.WithEnvironment("API_URL", api.GetEndpoint("http")) | ||||||
.WithHttpEndpoint(port: 3000, name: "http"); | ||||||
|
||||||
builder.Build().Run(); | ||||||
``` | ||||||
|
||||||
## Configure endpoints | ||||||
|
||||||
Executable resources can expose HTTP endpoints that other resources can reference: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var frontend = builder.AddExecutable( | ||||||
"vite-dev", "npm", ".", "run", "dev", "--", "--port", "5173", "--host", "0.0.0.0") | ||||||
.WithHttpEndpoint(port: 5173, name: "http"); | ||||||
|
||||||
// Another service can reference the frontend | ||||||
var e2eTests = builder.AddExecutable("playwright", "npx", ".", "playwright", "test") | ||||||
.WithEnvironment("BASE_URL", frontend.GetEndpoint("http")); | ||||||
``` | ||||||
|
||||||
## Environment configuration | ||||||
|
||||||
Configure environment variables for your executable: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var app = builder.AddExecutable( | ||||||
"api", "uvicorn", ".", "main:app", "--reload", "--host", "0.0.0.0") | ||||||
.WithEnvironment("DEBUG", "true") | ||||||
.WithEnvironment("LOG_LEVEL", "info") | ||||||
.WithEnvironment(context => | ||||||
{ | ||||||
// Dynamic environment variables | ||||||
context.EnvironmentVariables["START_TIME"] = DateTimeOffset.UtcNow.ToString(); | ||||||
}); | ||||||
``` | ||||||
|
||||||
## Publishing with PublishAsDockerfile | ||||||
|
||||||
For production deployment, executable resources need to be containerized. Use the <xref:Aspire.Hosting.ExecutableResourceBuilderExtensions.PublishAsDockerfile%2A> method to specify how the executable should be packaged: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var app = builder.AddExecutable( | ||||||
"frontend", "npm", ".", "start", "--port", "3000") | ||||||
.PublishAsDockerfile(); | ||||||
``` | ||||||
|
||||||
When you call `PublishAsDockerfile()`, .NET Aspire generates a Dockerfile during the publish process. You can customize this by providing your own Dockerfile: | ||||||
|
||||||
### Custom Dockerfile for publishing | ||||||
|
||||||
Create a `Dockerfile` in your executable's working directory: | ||||||
|
||||||
```dockerfile | ||||||
FROM node:22-alpine | ||||||
WORKDIR /app | ||||||
COPY package*.json ./ | ||||||
RUN npm ci --only=production | ||||||
COPY . . | ||||||
EXPOSE 3000 | ||||||
CMD ["npm", "start"] | ||||||
``` | ||||||
|
||||||
Then reference it in your app host: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var app = builder.AddExecutable("frontend", "npm", ".", "start") | ||||||
.PublishAsDockerfile([new DockerfileBuildArg("NODE_ENV", "production")]); | ||||||
``` | ||||||
|
||||||
### Conditional execution | ||||||
|
||||||
Use explicit start control for executables that should only run under certain conditions: | ||||||
|
||||||
```csharp | ||||||
var builder = DistributedApplication.CreateBuilder(args); | ||||||
|
||||||
var database = builder.AddPostgres("postgres").AddDatabase("appdb"); | ||||||
|
||||||
var migrator = builder.AddExecutable("migrator", "dotnet", ".", "ef") | ||||||
.WithArgs("database", "update") | ||||||
.WithReference(database) | ||||||
.WithExplicitStart(); // Only run when manually started | ||||||
var api = builder.AddProject<Projects.MyApi>("api") | ||||||
.WithReference(database) | ||||||
.WaitFor(migrator); // API waits for migrator to complete | ||||||
``` | ||||||
|
||||||
## Best practices | ||||||
|
||||||
When working with executable resources: | ||||||
|
||||||
1. **Use explicit paths**: For better reliability, use full paths to executables when possible. | ||||||
2. **Handle dependencies**: Use `WithReference` to establish proper dependency relationships. | ||||||
3. **Configure explicit start**: Use `WithExplicitStart()` for executables that shouldn't start automatically. | ||||||
4. **Prepare for deployment**: Always use `PublishAsDockerfile()` for production scenarios. | ||||||
5. **Environment isolation**: Use environment variables rather than command-line arguments for sensitive configuration. | ||||||
6. **Resource naming**: Use descriptive names that clearly identify the executable's purpose. | ||||||
|
||||||
## See also | ||||||
|
||||||
- [App host overview](../fundamentals/app-host-overview.md) | ||||||
- [Add Dockerfiles to the app model](withdockerfile.md) | ||||||
- [Node.js apps in .NET Aspire](../get-started/build-aspire-apps-with-nodejs.md) | ||||||
- [Python apps in .NET Aspire](../get-started/build-aspire-apps-with-python.md) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @copilot -
WithArgs
doesn't exist. Args are passed as theparams string[] args
parameter, the last param of theAddExecutable
API. Please fix this throughout.