{"slug":"foundationdb-aspire-2","title":"foundationdb-aspire","summary":"How to run a FoundationDB cluster and connect to it from .NET — getting the IFdbDatabaseProvider that the keys/transactions/layers skills assume you already have. Covers the ways to get a provider (plain DI services.AddFoundationDb, FdbDatabaseProvider.Create, or Aspire), the Asp","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-27T20:59:40.715385Z","repo":{"url":"https://github.com/SnowBankSDK/foundationdb-dotnet-client","stars":158,"forks":33,"license":"BSD-3-Clause","updatedAt":"2026-09-26T23:36:54Z"},"bodyHtml":"<hr>\n<h2>name: foundationdb-aspire\ndescription: How to run a FoundationDB cluster and connect to it from .NET — getting the IFdbDatabaseProvider that the keys/transactions/layers skills assume you already have. Covers the ways to get a provider (plain DI services.AddFoundationDb, FdbDatabaseProvider.Create, or Aspire), the Aspire AppHost integration (FoundationDB.Aspire.Hosting — builder.AddFoundationDb starts a Docker cluster, AddFoundationDbCluster connects to an existing one), the Aspire client integration (FoundationDB.Aspire — builder.AddFoundationDb reads the injected connection), wiring with WithReference and WaitFor, launching with the aspire CLI vs plain dotnet run plus launchSettings, the native client (libfdb_c via UseNativeClient, the platforms FoundationDB.Client.Native ships, and the macOS system-library fallback), and the client-vs-cluster version-compatibility rule. Use whenever code opens or connects to a cluster, registers AddFoundationDb, runs an Aspire host, or hits libfdb_c load failures or transactions that hang on a fresh cluster.</h2>\n<h1>FoundationDB — Running a cluster &amp; connecting from .NET (Aspire, the native client)</h1>\n<p>The <strong><code>foundationdb-keys-and-layers</code></strong>, <strong><code>foundationdb-transactions</code></strong>, and <strong><code>foundationdb-advanced-layers</code></strong> skills all start from an <code>IFdbDatabase</code> / <code>IFdbDatabaseProvider</code> you already have. This skill is about getting there: standing up a cluster, loading the native client correctly, and connecting — including the .NET Aspire integration shipped in this repo (<code>FoundationDB.Aspire.Hosting</code> + <code>FoundationDB.Aspire</code>).</p>\n<hr>\n<h2>1. Getting an <code>IFdbDatabaseProvider</code></h2>\n<p>Application code resolves an <strong><code>IFdbDatabaseProvider</code></strong> (then <code>await provider.GetDatabase(ct)</code> or, more commonly, the retry-loop methods). Three ways to create one:</p>\n<table>\n<thead>\n<tr>\n<th>Way</th>\n<th>API</th>\n<th>When</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Plain DI</strong></td>\n<td><code>services.AddFoundationDb(apiVersion, opt =&gt; …)</code> → <code>IFdbDatabaseProviderBuilder</code> (registers <code>IFdbDatabaseProvider</code>)</td>\n<td>a regular host, you supply the cluster file via config</td>\n</tr>\n<tr>\n<td><strong>No DI</strong></td>\n<td><code>FdbDatabaseProvider.Create(new FdbDatabaseProviderOptions { ApiVersion = 730, … })</code></td>\n<td>tools/tests/standalone; call <code>provider.Start()</code></td>\n</tr>\n<tr>\n<td><strong>Aspire client</strong></td>\n<td><code>builder.AddFoundationDb(\"fdb\", settings, options)</code></td>\n<td>the connection string is injected by an Aspire AppHost (see §3)</td>\n</tr>\n</tbody>\n</table>\n<p><code>apiVersion</code> selects the client API level and <strong>caps the features you can use</strong>: <code>730</code> ⇒ FoundationDB 7.3 semantics, <code>740</code> ⇒ 7.4. This client accepts <strong>610 to 740</strong>, and <code>Fdb.DefaultApiVersion</code> is <strong>730</strong>, which is why 730 appears in most examples. Pick the level whose semantics you actually rely on, not the newest available: raising it is a behavior change, and it must stay ≤ what the cluster supports. The provider also carries a <strong>root</strong> <code>FdbPath</code> (a directory-layer partition); everything else resolves relative to it.</p>\n<p>Plain-DI / no-DI namespaces: <code>FoundationDB.DependencyInjection</code>. These all still need the <strong>native client</strong> loaded (§5).</p>\n<hr>\n<h2>2. The Aspire AppHost integration (<code>FoundationDB.Aspire.Hosting</code>)</h2>\n<p>On a <code>IDistributedApplicationBuilder</code> (the AppHost), namespace <code>Aspire.Hosting</code>:</p>\n<pre><code>// starts a FoundationDB cluster as a Docker container, and emits a connection string named \"fdb\"\nvar fdb = builder.AddFoundationDb(\"fdb\",\n        apiVersion: 730,\n        root: \"/MyApp\")                            // clusterVersion omitted on purpose - see below\n    .WithLifetime(ContainerLifetime.Persistent);   // reuse the container across runs (faster restarts)\n\nbuilder.AddProject&lt;Projects.MyApp&gt;(\"app\")\n    .WithReference(fdb)                            // injects the \"fdb\" connection string into the app\n    .WaitFor(fdb);                                 // hold the app until the cluster is healthy\n</code></pre>\n<ul>\n<li><strong>Prefer omitting <code>clusterVersion</code>.</strong> Left out, the hosting package picks its own last-known-good Docker tag for the major it selects (the <code>FdbAspireHostingExtensions.LatestVersionNN</code> constants, refreshed from Docker Hub by <code>scripts/check-fdb-image-tags.ps1</code>). A tag you hardcode is a number that rots in your AppHost: pin one only when you need a specific image, and then treat it as something to review, alongside <code>rollForward: FdbVersionPolicy.Exact</code>.</li>\n<li><code>AddFoundationDb(...)</code> → <code>IResourceBuilder&lt;FdbClusterResource&gt;</code> <strong>runs a container</strong>.</li>\n<li><strong>A fresh volume self-provisions.</strong> On first start the integration runs <code>configure new single ssd</code> inside the container (a brand-new cluster has no database until then), and <code>WaitFor(fdb)</code> holds dependents until the database answers. An already-configured database is left untouched. Opt out with <code>.WithAutoProvisioning(false)</code>; run mode only.</li>\n<li><code>AddFoundationDbCluster(name, apiVersion, root, clusterFile?, clusterVersion?)</code> → <code>IResourceBuilder&lt;FdbConnectionResource&gt;</code> instead <strong>connects to an already-running cluster</strong> (no container) given a cluster file.</li>\n<li><code>FdbVersionPolicy</code> (<code>Exact</code>, etc.) lives in <code>Aspire.Hosting.ApplicationModel</code>. <code>.WithDefaults(timeout, retryLimit, tracing)</code> tunes the connection.</li>\n</ul>\n<p><strong>AppHost project setup:</strong> <code>Sdk=\"Aspire.AppHost.Sdk/&lt;ver&gt;\"</code>; reference <code>FoundationDB.Aspire.Hosting</code> with <code>IsAspireProjectResource=\"false\"</code> (it's a <em>compile</em> dependency for <code>AddFoundationDb</code>); reference each app project normally (those are launched as resources — their TFM need not match the AppHost).</p>\n<hr>\n<h2>3. The Aspire client integration (<code>FoundationDB.Aspire</code>)</h2>\n<p>In the <strong>app</strong> (not the AppHost), on an <code>IHostApplicationBuilder</code>, namespace <code>Microsoft.Extensions.Hosting</code>:</p>\n<pre><code>// reads the \"fdb\" connection string injected by WithReference(fdb) and registers IFdbDatabaseProvider\nbuilder.AddFoundationDb(\"fdb\",\n    settings =&gt; { /* FdbClientSettings: tracing, health checks, … */ },\n    options  =&gt; options.UseNativeClient());        // load libfdb_c (see §5)\n</code></pre>\n<p>The app references <code>FoundationDB.Aspire</code>. The injected connection string looks like <code>ApiVersion=730;Root=/MyApp;ClusterFileContents=…;ClusterVersion=7.4.6</code> (the <code>ClusterVersion</code> is whatever tag the AppHost resolved, so do not read the one in this example as a recommendation). Standalone (no AppHost), provide the cluster file via configuration instead.</p>\n<blockquote>\n<p>Don't confuse the two <code>AddFoundationDb</code>s: the <strong>AppHost</strong> one (<code>IDistributedApplicationBuilder</code>, starts the container) vs the <strong>client</strong> one (<code>IHostApplicationBuilder</code>, consumes the connection). They have different receivers, so they never collide.</p>\n</blockquote>\n<hr>\n<h2>4. Launching the AppHost</h2>\n<p><strong>Preferred: the <code>aspire</code> CLI</strong> — it provisions the dashboard, OTLP, and ports for you:</p>\n<pre><code>dotnet tool install --global aspire.cli          # one-time\naspire run --apphost path/to/MyApp.AppHost.csproj\n# headless / CI: aspire run --apphost … --detach --non-interactive --format Json\n#   → returns { appHostPid, dashboardUrl (with login token), logFile }\n</code></pre>\n<p><strong>Fallback: plain <code>dotnet run</code></strong> (or IDE F5) on the AppHost. This requires a <strong><code>Properties/launchSettings.json</code></strong> providing the dashboard + OTLP endpoints, e.g.:</p>\n<pre><code>{ \"profiles\": { \"http\": { \"commandName\": \"Project\",\n  \"applicationUrl\": \"http://localhost:15200\",\n  \"environmentVariables\": {\n    \"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL\": \"http://localhost:19200\",\n    \"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL\": \"http://localhost:20200\",\n    \"ASPIRE_ALLOW_UNSECURED_TRANSPORT\": \"true\" } } } }\n</code></pre>\n<p>Without it, a bare <code>dotnet run</code> on the AppHost crashes at startup: <em>\"Failed to configure dashboard resource because ASPNETCORE_URLS … was not set / … OTLP endpoint must be provided.\"</em> The <code>aspire</code> CLI does not need this file.</p>\n<hr>\n<h2>5. The native client (<code>libfdb_c</code>) — <code>UseNativeClient()</code></h2>\n<p>The .NET client talks to the cluster through the native <strong><code>libfdb_c</code></strong> library. <code>options.UseNativeClient(allowSystemFallback: false)</code> loads <strong>only</strong> the copy shipped by the <code>FoundationDB.Client.Native</code> package.</p>\n<p>That package ships <code>libfdb_c</code> for <code>linux-arm64</code>, <code>linux-x64</code>, <code>win-x64</code>, <code>osx-arm64</code>, and (since the 7.4.6 native repackaging) <code>osx-x64</code>, so <code>UseNativeClient(allowSystemFallback: false)</code> loads the redistributed client on macOS too, with no system install. Use the system fallback only when your <code>FoundationDB.Client.Native</code> pin predates the macOS RID you need:</p>\n<ol>\n<li>install a system <code>libfdb_c.dylib</code> (Homebrew <code>foundationdb</code>, or the official client package) of a matching version (§6) and matching CPU arch (an arm64 process cannot load an x64 dylib), and</li>\n<li>call <code>UseNativeClient(allowSystemFallback: true)</code>.</li>\n</ol>\n<p>The loader searches the <strong>app's output directory</strong> and the standard dyld paths, not <code>/usr/local/lib</code> by bare name. To force a system copy, place (or copy) <code>libfdb_c.dylib</code> next to the built app. Symptom when no client loads: <code>DllNotFoundException: Unable to load shared library 'fdb_c'</code>.</p>\n<hr>\n<h2>6. Client ⇄ cluster version compatibility (the silent hang)</h2>\n<p>The <strong>client library version must be compatible with the cluster version.</strong> A 7.3 client <strong>cannot</strong> talk to a 7.4 cluster.</p>\n<ul>\n<li>Symptom: transactions <strong>hang</strong> (no exception); <code>fdbcli</code> against the cluster reports <em>\"One or more processes … incompatible\"</em> and <em>\"database is unavailable\"</em>, even though the cluster is internally healthy.</li>\n<li>Fix: make the AppHost <code>clusterVersion</code> (the Docker image tag) match the <code>libfdb_c</code> available to the app. Check the client with <code>fdbcli --version</code>, the server with <code>docker exec &lt;container&gt; fdbcli --version</code>. If you did not pin a tag, the mismatch is almost always a <strong>system</strong> <code>libfdb_c</code> shadowing the redistributed one (see §5), not the container.</li>\n<li>The <code>apiVersion</code> (e.g. <code>730</code>) must be ≤ the cluster's supported level; <code>730</code> ⇒ 7.3, <code>740</code> ⇒ 7.4.</li>\n</ul>\n<p>For talking to <strong>multiple</strong> cluster versions from one process, FoundationDB's multi-version client loads several external <code>libfdb_c</code> libraries — but the simplest demo/dev setup is: one cluster version, one matching client library.</p>\n<hr>\n<h2>7. Golden rules &amp; gotchas</h2>\n<p>✅ <strong>DO</strong></p>\n<ul>\n<li>Pick the provider source by context: Aspire client integration under an AppHost; plain <code>services.AddFoundationDb(...)</code> / <code>FdbDatabaseProvider.Create(...)</code> otherwise.</li>\n<li>Launch the AppHost with <code>aspire run</code>; keep a <code>launchSettings.json</code> only as the <code>dotnet run</code>/F5 fallback.</li>\n<li>Keep the cluster image and the client library on the <strong>same major.minor</strong> (7.4 with 7.4). Omitting <code>clusterVersion</code> lets the hosting package pair them for you against the <code>libfdb_c</code> that <code>FoundationDB.Client.Native</code> redistributes; pin it only when you supply your own client library, and then verify both with <code>fdbcli --version</code>.</li>\n<li>On macOS, the redistributed client covers <code>osx-arm64</code> and <code>osx-x64</code> (since 7.4.6), so <code>UseNativeClient(allowSystemFallback: false)</code> works; fall back to a system <code>libfdb_c.dylib</code> and <code>allowSystemFallback: true</code> only for an older pin without your RID.</li>\n</ul>\n<p>⚠️ <strong>GOTCHAS</strong></p>\n<ul>\n<li><strong>Transactions that hang on a brand-new cluster</strong> are almost always a client/cluster <strong>version mismatch</strong>, not a deadlock — check versions first.</li>\n<li><strong><code>DllNotFoundException: fdb_c</code></strong> ⇒ no native client found; on macOS the package ships none (see §5).</li>\n<li><strong>AppHost <code>dotnet run</code> crash about ASPNETCORE_URLS / OTLP</strong> ⇒ missing <code>launchSettings.json</code>, or just use <code>aspire run</code>.</li>\n<li><strong><code>AddFoundationDb</code> \"ambiguous\" / wrong overload</strong> ⇒ you imported the wrong namespace; the AppHost call is on <code>IDistributedApplicationBuilder</code> (<code>Aspire.Hosting</code>), the client call on <code>IHostApplicationBuilder</code> (<code>Microsoft.Extensions.Hosting</code>).</li>\n<li><code>WithLifetime(ContainerLifetime.Persistent)</code> keeps the cluster's data across runs — handy in dev, but remember to <code>docker rm -f</code> it when you want a clean slate.</li>\n</ul>\n<hr>\n<h2>8. File map</h2>\n<table>\n<thead>\n<tr>\n<th>What</th>\n<th>Where</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Aspire AppHost integration</td>\n<td><code>FoundationDB.Aspire.Hosting/FdbAspireHostingExtensions.cs</code> (<code>AddFoundationDb</code>, <code>AddFoundationDbCluster</code>, <code>FdbVersionPolicy</code>)</td>\n</tr>\n<tr>\n<td>Aspire client integration</td>\n<td><code>FoundationDB.Aspire/FdbAspireComponentExtensions.cs</code> (<code>AddFoundationDb</code>), <code>FdbClientSettings.cs</code></td>\n</tr>\n<tr>\n<td>Plain DI / no-DI provider</td>\n<td><code>FoundationDB.Client/DependencyInjection/FdbDatabaseServiceCollectionExtensions.cs</code>, <code>Implementation/FdbDatabaseProvider.cs</code></td>\n</tr>\n<tr>\n<td>Native client loader</td>\n<td><code>FoundationDB.Client.Native/</code> (<code>FdbClientNativeExtensions.cs</code> → <code>UseNativeClient</code>; <code>runtimes/</code> holds the shipped libs)</td>\n</tr>\n</tbody>\n</table>\n","files":[{"path":"SKILL.md","sizeBytes":12183,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-27T21:00:31.685371Z","sha256":"50AEC888057F22D1E76EBE595B4F58BC52CCFAE8B055A6C5398CF76BEF76512A","sizeBytes":4946},"review":null,"source":{"repositoryUrl":"https://github.com/SnowBankSDK/foundationdb-dotnet-client","path":".claude/skills/foundationdb-aspire","license":"BSD-3-Clause","commit":"dcbebf1f6f19ddd6b79978c3762f0df18728ccfc","subtreeSha":"3DC98EB2077EC792E1985F98342C44369FCDAA9AAE4D7F9B1353C1BE6E19CF5D","lastSyncedAt":"2026-09-27T20:59:38.964495Z"},"reviewedAt":"2026-09-27T21:06:26.807712Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/SnowBankSDK/foundationdb-dotnet-client/tree/master/.claude/skills/foundationdb-aspire"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install snowbanksdk-foundationdb-dotnet-client@llmmart"},{"target":"git","command":"git clone https://github.com/SnowBankSDK/foundationdb-dotnet-client.git"}]}