name: shiny-aspire-orleans description: Generate code using Shiny Aspire integrations — Orleans ADO.NET hosting and Gluetun VPN container routing auto_invoke: true triggers:
- "aspire orleans"
- "orleans aspire"
- "WithDatabaseSetup"
- "UseAdoNet"
- "UseAdoNetClient"
- "orleans database setup"
- "orleans schema"
- "orleans clustering"
- "orleans grain storage"
- "orleans reminders"
- "OrleansFeature"
- "Shiny.Aspire.Orleans"
- "aspire orleans hosting"
- "aspire orleans server"
- "aspire orleans client"
- "orleans adonet"
- "orleans ado.net"
- "gluetun"
- "gluetun vpn"
- "AddGluetun"
- "WithRoutedContainer"
- "WithVpnProvider"
- "WithWireGuard"
- "WithOpenVpn"
- "vpn container"
- "aspire vpn"
- "Shiny.Aspire.Hosting.Gluetun"
- "GluetunResource"
- "network_mode"
- "vpn routing"
Shiny Aspire Skill
Triggers
- aspire orleans
- orleans aspire
- WithDatabaseSetup
- UseAdoNet
- UseAdoNetClient
- orleans database setup
- orleans schema
- orleans clustering
- orleans grain storage
- orleans reminders
- OrleansFeature
- Shiny.Aspire.Orleans
- aspire orleans hosting
- aspire orleans server
- aspire orleans client
- orleans adonet
- orleans ado.net
- gluetun
- gluetun vpn
- AddGluetun
- WithRoutedContainer
- WithVpnProvider
- WithWireGuard
- WithOpenVpn
- vpn container
- aspire vpn
- Shiny.Aspire.Hosting.Gluetun
- GluetunResource
- network_mode
- vpn routing
You are an expert in Shiny's .NET Aspire integrations:
- Shiny.Aspire.Orleans — Zero-friction integration between .NET Aspire and Microsoft Orleans for ADO.NET storage backends. Automatically provisions Orleans database schemas and wires up clustering, grain persistence, and reminders from Aspire configuration.
- Shiny.Aspire.Hosting.Gluetun — Aspire hosting integration for Gluetun VPN containers. Models Gluetun as a first-class Aspire resource and lets other containers route their traffic through the VPN tunnel.
When to Use This Skill
Invoke this skill when the user wants to:
- Set up Orleans with .NET Aspire using ADO.NET storage (PostgreSQL, SQL Server, or MySQL)
- Automatically create Orleans database schemas from Aspire
- Configure an Orleans silo with ADO.NET providers from Aspire-injected config
- Configure an Orleans client with ADO.NET clustering from Aspire-injected config
- Use
WithDatabaseSetupto auto-provision Orleans tables - Use
silo.UseAdoNet()to configure a silo insideUseOrleans - Use
client.UseAdoNetClient()to configure a client insideUseOrleansClient - Select which Orleans features to provision (clustering, persistence, reminders)
- Set up multiple named grain storage providers
- Add a Gluetun VPN container to an Aspire app
- Route container traffic through a VPN tunnel
- Configure VPN providers, WireGuard, or OpenVPN in Aspire
- Use
AddGluetun,WithRoutedContainer,WithVpnProvider,WithWireGuard, orWithOpenVpn - Set up Docker Compose publishing with VPN network mode and port transfer
Library Overview
- Repository: https://github.com/shinyorg/aspire
- Target:
net10.0 - Aspire: 13.1+
- Orleans: 10.0+ (Orleans packages only)
Packages
| Package | NuGet | Usage |
|---|---|---|
Shiny.Aspire.Orleans.Hosting |
Install in Aspire AppHost | Auto-runs Orleans schema scripts when the database becomes ready |
Shiny.Aspire.Orleans.Server |
Install in Orleans silo | Registers ADO.NET provider builders for clustering, grain storage, and reminders |
Shiny.Aspire.Orleans.Client |
Install in Orleans client | Registers ADO.NET provider builder for clustering |
Shiny.Aspire.Hosting.Gluetun |
Install in Aspire AppHost | Adds a Gluetun VPN container and routes other containers through it |
Supported Databases
| Database | ADO.NET Invariant | ProviderType Value |
|---|---|---|
| PostgreSQL | Npgsql |
PostgresDatabase |
| SQL Server | Microsoft.Data.SqlClient |
SqlServerDatabase |
| MySQL | MySql.Data.MySqlClient |
MySqlDatabase |
Architecture: Provider Registration
The Server and Client packages register Orleans provider builders via [assembly: RegisterProvider] attributes. When Orleans calls ApplyConfiguration during UseOrleans(), it reads the Aspire-injected configuration (e.g. Orleans:Clustering:ProviderType = "PostgresDatabase") and resolves the matching provider builder automatically. The provider builder maps the database type to the correct ADO.NET invariant and resolves the connection string from the ServiceKey.
This means:
- No manual configuration — providers are resolved automatically from Aspire config.
- Extension methods —
UseAdoNet()onISiloBuilderandUseAdoNetClient()onIClientBuilderare identity methods for discoverability. The actual wiring happens through the registered provider builders. - Composable — because configuration happens inside
UseOrleans(silo => { ... })orUseOrleansClient(client => { ... }), users can add other features alongside.
Setup
1. Aspire AppHost
Install Shiny.Aspire.Orleans.Hosting in the AppHost project.
using Shiny.Aspire.Orleans.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
var db = builder.AddPostgres("pg")
.WithPgAdmin()
.AddDatabase("orleans-db");
var orleans = builder.AddOrleans("cluster")
.WithClustering(db)
.WithGrainStorage("Default", db)
.WithReminders(db)
.WithDatabaseSetup(db); // creates all Orleans tables automatically
builder.AddProject<Projects.MySilo>("silo")
.WithReference(orleans)
.WaitFor(db);
builder.AddProject<Projects.MyApi>("api")
.WithReference(orleans.AsClient())
.WaitFor(db);
builder.Build().Run();
2. Orleans Silo
Install Shiny.Aspire.Orleans.Server in the silo project. Call silo.UseAdoNet() inside UseOrleans.
using Shiny.Aspire.Orleans.Server;
var builder = WebApplication.CreateBuilder(args);
builder.UseOrleans(silo =>
{
silo.UseAdoNet();
});
var app = builder.Build();
app.Run();
3. Orleans Client
Install Shiny.Aspire.Orleans.Client in the client project (e.g. an API gateway). Call client.UseAdoNetClient() inside UseOrleansClient.
using Shiny.Aspire.Orleans.Client;
var builder = WebApplication.CreateBuilder(args);
builder.UseOrleansClient(client =>
{
client.UseAdoNetClient();
});
var app = builder.Build();
app.MapGet("/counter/{name}", async (string name, IClusterClient client) =>
{
var grain = client.GetGrain<ICounterGrain>(name);
var count = await grain.GetCount();
return Results.Ok(new { name, count });
});
app.Run();
API Reference
Hosting Package — Shiny.Aspire.Orleans.Hosting
WithDatabaseSetup
public static OrleansService WithDatabaseSetup(
this OrleansService orleans,
IResourceBuilder<IResourceWithConnectionString> database,
OrleansFeature features = OrleansFeature.All
)
Subscribes to Aspire's ResourceReadyEvent for the database resource. When the database is up and accepting connections, it executes embedded Orleans SQL schema scripts. The database type (PostgreSQL, SQL Server, MySQL) is auto-detected from the Aspire resource.
Parameters:
orleans— The Orleans service frombuilder.AddOrleans()database— An Aspire database resource (e.g. fromAddPostgres,AddSqlServer,AddMySql)features— Which Orleans features to provision (default:OrleansFeature.All)
OrleansFeature (Flags Enum)
[Flags]
public enum OrleansFeature
{
Clustering = 1, // Membership tables for silo discovery
Persistence = 2, // Grain storage tables
Reminders = 4, // Reminder tables
All = Clustering | Persistence | Reminders
}
Use to limit which schemas are provisioned:
// Only clustering and persistence (no reminders)
orleans.WithDatabaseSetup(db, OrleansFeature.Clustering | OrleansFeature.Persistence);
// Only clustering
orleans.WithDatabaseSetup(db, OrleansFeature.Clustering);
DatabaseType (Enum)
public enum DatabaseType
{
SqlServer,
PostgreSQL,
MySql
}
Auto-detected from the Aspire resource — you do not need to specify this directly.
Server Package — Shiny.Aspire.Orleans.Server
UseAdoNet (ISiloBuilder extension)
public static ISiloBuilder UseAdoNet(this ISiloBuilder siloBuilder)
Marker extension for discoverability. The actual provider registration happens automatically via [assembly: RegisterProvider] attributes when the package is referenced. Providers are registered for all three database types across Clustering, GrainStorage, and Reminders.
Call inside UseOrleans:
builder.UseOrleans(silo =>
{
silo.UseAdoNet();
// compose with other silo features here
});
Client Package — Shiny.Aspire.Orleans.Client
UseAdoNetClient (IClientBuilder extension)
public static IClientBuilder UseAdoNetClient(this IClientBuilder clientBuilder)
Marker extension for discoverability. Registers ADO.NET clustering provider builders for both Silo and Client targets. Clients do not need grain storage or reminders.
Call inside UseOrleansClient:
builder.UseOrleansClient(client =>
{
client.UseAdoNetClient();
});
Configuration Flow
Aspire injects the following configuration when you use .WithReference(orleans):
Orleans:Clustering:ProviderType = "PostgresDatabase"
Orleans:Clustering:ServiceKey = "orleans-db"
Orleans:GrainStorage:Default:ProviderType = "PostgresDatabase"
Orleans:GrainStorage:Default:ServiceKey = "orleans-db"
Orleans:Reminders:ProviderType = "PostgresDatabase"
Orleans:Reminders:ServiceKey = "orleans-db"
ConnectionStrings:orleans-db = "Host=...;Database=..."
Orleans' ApplyConfiguration reads these sections and delegates to the registered provider builders, which configure the ADO.NET providers with the correct connection strings and invariants.
Schema Provisioning Order
WithDatabaseSetup runs embedded SQL scripts in order:
- Main — creates the
OrleansQuerytable (query registry) - Clustering — creates
OrleansMembershipVersionTable,OrleansMembershipTable, and stored procedures - Persistence — creates
OrleansStoragetable and stored procedures - Reminders — creates
OrleansRemindersTableand stored procedures
Switching Databases
Swap the Aspire resource builder — everything else stays the same:
// PostgreSQL
var db = builder.AddPostgres("pg").AddDatabase("orleans-db");
// SQL Server
var db = builder.AddSqlServer("sql").AddDatabase("orleans-db");
// MySQL
var db = builder.AddMySql("mysql").AddDatabase("orleans-db");
Multiple Grain Storage Providers
// AppHost
var orleans = builder.AddOrleans("cluster")
.WithClustering(db)
.WithGrainStorage("Default", db)
.WithGrainStorage("Archive", archiveDb)
.WithDatabaseSetup(db);
// Grain
public class MyGrain(
[PersistentState("state", "Default")] IPersistentState<MyState> state,
[PersistentState("archive", "Archive")] IPersistentState<ArchiveState> archive
) : Grain, IMyGrain { }
Each named provider reads from Orleans:GrainStorage:{Name}:ProviderType and Orleans:GrainStorage:{Name}:ServiceKey.
Code Generation Best Practices
- Always use
WithDatabaseSetupin the AppHost to auto-provision schemas — never require manual SQL scripts. - Always call
WaitFor(db)on projects that reference Orleans, so the database is ready before the silo starts. - Use
.AsClient()when wiring a client project — this provides only clustering config, not full silo config. - Use
UseOrleansandUseOrleansClient— callsilo.UseAdoNet()in silo projects insideUseOrleansandclient.UseAdoNetClient()in client projects insideUseOrleansClient. - Feature flags are optional — only use
OrleansFeatureflags if the user explicitly wants to skip certain schemas. - Don't hardcode connection strings — Aspire injects them automatically via configuration.
- Don't manually configure ADO.NET invariants — the packages auto-detect the correct invariant from the provider type.
- Use named grain storage for multiple persistence stores — each name maps to a separate configuration section.
Gluetun VPN — Shiny.Aspire.Hosting.Gluetun
Setup
Install Shiny.Aspire.Hosting.Gluetun in the Aspire AppHost project.
var builder = DistributedApplication.CreateBuilder(args);
var vpn = builder.AddGluetun("vpn")
.WithVpnProvider("mullvad")
.WithWireGuard(builder.AddParameter("wireguard-key", secret: true))
.WithServerCountries("US", "Canada");
var scraper = builder.AddContainer("scraper", "my-scraper")
.WithHttpEndpoint(targetPort: 8080);
vpn.WithRoutedContainer(scraper);
builder.Build().Run();
API Reference
AddGluetun
public static IResourceBuilder<GluetunResource> AddGluetun(
this IDistributedApplicationBuilder builder,
string name,
int? httpProxyPort = null,
int? shadowsocksPort = null)
Creates a Gluetun container resource with:
- Image:
qmcgaw/gluetun:latestfromdocker.io --cap-add NET_ADMINruntime arg--device /dev/net/tunruntime arg- Docker Compose publish callback that sets
cap_add,devices, and transfers ports from routed containers
Optional port parameters expose Gluetun's built-in HTTP proxy (target port 8888) and Shadowsocks proxy (target port 8388).
WithVpnProvider
vpn.WithVpnProvider("mullvad");
Sets the VPN_SERVICE_PROVIDER environment variable. Required for all Gluetun setups.
WithOpenVpn
// String credentials
vpn.WithOpenVpn("username", "password");
// Aspire parameter resources (recommended for secrets)
vpn.WithOpenVpn(
builder.AddParameter("openvpn-user"),
builder.AddParameter("openvpn-pass", secret: true));
Sets VPN_TYPE=openvpn, OPENVPN_USER, and OPENVPN_PASSWORD.
WithWireGuard
// String key
vpn.WithWireGuard("my-private-key");
// Aspire parameter resource (recommended for secrets)
vpn.WithWireGuard(builder.AddParameter("wireguard-key", secret: true));
Sets VPN_TYPE=wireguard and WIREGUARD_PRIVATE_KEY.
WithServerCountries / WithServerCities
vpn.WithServerCountries("US", "Canada", "Germany");
vpn.WithServerCities("New York", "Toronto");
Values are comma-joined and set as SERVER_COUNTRIES / SERVER_CITIES environment variables.
WithHttpProxy / WithShadowsocks
vpn.WithHttpProxy(); // HTTPPROXY=on
vpn.WithHttpProxy(false); // HTTPPROXY=off
vpn.WithShadowsocks(); // SHADOWSOCKS=on
vpn.WithShadowsocks(false); // SHADOWSOCKS=off
WithFirewallOutboundSubnets
vpn.WithFirewallOutboundSubnets("10.0.0.0/8", "192.168.0.0/16");
Sets FIREWALL_OUTBOUND_SUBNETS (comma-joined). Useful for allowing traffic to local network resources outside the VPN tunnel.
WithTimezone
vpn.WithTimezone("America/New_York");
Sets the TZ environment variable.
WithGluetunEnvironment
// String value
vpn.WithGluetunEnvironment("DNS_ADDRESS", "1.1.1.1");
// Aspire parameter resource
vpn.WithGluetunEnvironment("UPDATER_PERIOD", builder.AddParameter("updater-period"));
Generic passthrough for any Gluetun environment variable not covered by the typed methods.
WithRoutedContainer
vpn.WithRoutedContainer(scraper);
vpn.WithRoutedContainer(downloader);
Routes a container's traffic through the Gluetun VPN tunnel. Each call:
- Adds a
GluetunRoutedResourceAnnotationto the Gluetun resource - Sets
--network container:<vpn-name>runtime args on the routed container - On Docker Compose publish, sets
network_mode: "service:<vpn-name>"and transfers port mappings to the Gluetun service
You can route multiple containers through the same VPN.
Docker Compose Output
When published as Docker Compose, routed containers automatically get:
services:
vpn:
image: qmcgaw/gluetun:latest
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${wireguard-key}
- SERVER_COUNTRIES=US,Canada
ports:
- "8080:8080" # forwarded from scraper
scraper:
image: my-scraper
network_mode: "service:vpn"
# ports moved to vpn service
Types
GluetunResource
namespace Aspire.Hosting.ApplicationModel;
public class GluetunResource(string name) : ContainerResource(name);
GluetunRoutedResourceAnnotation
namespace Aspire.Hosting.ApplicationModel;
public sealed record GluetunRoutedResourceAnnotation(
GluetunResource GluetunResource,
ContainerResource RoutedResource) : IResourceAnnotation;
Stored on the Gluetun resource. References each container that routes through it.
Gluetun Code Generation Best Practices
- Always use
WithVpnProvider— it is required for Gluetun to connect to any VPN service. - Use
ParameterResourcefor secrets — never hardcode private keys or passwords in the AppHost. Usebuilder.AddParameter("key", secret: true). - Call
WithRoutedContaineron the VPN builder — not on the container. The method is onIResourceBuilder<GluetunResource>. - Ports transfer automatically — when a container is routed through Gluetun, its endpoints are served on the Gluetun container in Docker Compose. Do not manually duplicate port mappings.
- Use
WithGluetunEnvironmentfor provider-specific settings — the typed methods cover common settings, but many providers have additional options documented in the Gluetun wiki. - Use
WithFirewallOutboundSubnetsfor local network access — if routed containers need to reach local services (databases, APIs) outside the VPN, allow those subnets explicitly. - Multiple containers can share one VPN — call
WithRoutedContainermultiple times on the same Gluetun resource.