Convoy.Cli 1.1.0

dotnet tool install --global Convoy.Cli --version 1.1.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Convoy.Cli --version 1.1.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Convoy.Cli&version=1.1.0
                    
nuke :add-package Convoy.Cli --version 1.1.0
                    

Convoy

Multi-tenancy for .NET. All of it.

nuget .NET license tests build

SQL Server PostgreSQL SQLite EF Core OpenTelemetry JWT


You have 200 tenants.

You need to deploy a migration.

So you write a foreach loop, call MigrateAsync, and hope nothing fails at tenant 94.

It does. You don't know which tenants succeeded. You don't know which ones failed. You have no rollback path, no retry, no audit trail, and a production incident at 2am that nobody planned for.

Every multi-tenant team hits this wall. Most of them build around it. Convoy tears it down.


Resolve. One line.

Seven strategies built in. Use one. Chain several. Write your own. Done.

convoy.WithResolution(r => r.FromSubdomain());
// acme.yourapp.com → "acme". That's it.

Need more?

convoy.WithResolution(r =>
    r.FromHeader("X-Tenant-Id")
     .ThenFromSubdomain()
     .ThenFromClaim("tid"));
// Try header first. Fall back to subdomain. Fall back to claim.
// Convoy figures it out. You move on.

Every strategy you'll ever need — subdomain, header, route, claim, query string, static mapping, custom — all included, all tested, all production-ready.


Isolate. Your way.

Three data isolation models. Pick the one that fits. Or mix them — per tenant, at runtime.

// Enterprise customers get their own database.
// Pro customers share one, separated by schema.
// Everyone else lives in the same table.
// Convoy routes each request to the right place automatically.

ef.UseIsolation(t => t.Plan switch
{
    "enterprise" => TenantIsolation.DatabasePerTenant,
    "pro"        => TenantIsolation.SchemaPerTenant,
    _            => TenantIsolation.RowLevel
});

Your DbContext is already pointing at the right database before your first line of business logic runs. No per-query filtering. No WHERE TenantId = ? everywhere. Just inject and use.


Migrate. Across your entire fleet.

Not one database. All of them. In parallel.

var result = await orchestrator.MigrateAllAsync(new MigrationOptions
{
    MaxParallelism = 10,              // 10 tenant databases, simultaneously
    RetryPolicy    = RetryPolicy.ExponentialBackoff(maxAttempts: 3),
    Progress       = new Progress<TenantMigrationProgress>(p =>
        Console.WriteLine($"[{p.Completed}/{p.Total}] {p.TenantId}: {p.Status}"))
});

Not sure it's safe? Run a dry run first. Zero writes. Full simulation.

await orchestrator.MigrateAllAsync(new MigrationOptions { DryRun = true });
// See exactly what would happen. Commit nothing.

Not ready to run anything at all? Just validate.

await orchestrator.ValidateAsync();
// Connectivity, migration gaps, schema conflicts — all checked before a single query runs.

Know exactly where every tenant stands, at any moment.

var status = await orchestrator.GetFleetStatusAsync();
// TENANT      MIGRATION                  PENDING
// acme        20250501_AddOrdersTable    0
// globex      20250401_InitialCreate     3   ← behind
// initech     20250501_AddOrdersTable    0

Roll back. Like it never happened.

Something went wrong at tenant 47. Roll it back.

await orchestrator.RollbackTenantAsync("globex", targetMigration: "20250401_InitialCreate");

Something went wrong across the whole fleet. Roll it all back.

await orchestrator.RollbackAllAsync("20250401_InitialCreate", new RollbackOptions
{
    DryRun         = true,  // see what would happen first
    MaxParallelism = 5
});

Every run is checkpointed. If something is interrupted, resume from exactly where you left off. Nothing is lost.


Auth. Per tenant. Not per app.

Your enterprise customers use Okta. Your startup customers use Auth0. Your on-prem customers run their own identity server. No problem.

convoy.WithAuth(auth =>
{
    auth.UseJwtBearer(jwt =>
    {
        // Each tenant gets their own issuer, audience, and signing keys.
        // Validated correctly on every single request. Automatically.
        jwt.WithIssuerResolver(t => t.Settings["auth:issuer"]);
        jwt.WithAudienceResolver(t => t.Settings["auth:audience"]);
        jwt.WithSigningKeyResolver(async (tenant, token) =>
            await jwks.GetSigningKeysAsync(tenant.Settings["auth:jwksUri"]));
    });

    // Different authorization rules per plan.
    auth.WithPolicy("RequiresPro", (policy, tenant) =>
    {
        if (tenant.Plan == "free") policy.RequireClaim("plan", "pro", "enterprise");
        else policy.RequireAuthenticatedUser();
    });
});

Implemented cleanly, using IPostConfigureOptions — the right way, not the hack everyone else reaches for.


See everything. Miss nothing.

Every request. Every migration. Every tenant. Observable.

convoy.WithOpenTelemetry();

builder.Services.AddOpenTelemetry()
    .WithTracing(t => t.AddConvoyInstrumentation())
    .WithMetrics(m => m.AddConvoyInstrumentation());

Convoy emits spans for every tenant request (convoy.request) and every migration (convoy.migrate), tagged with tenant context. Metrics for request count, resolution latency, migration duration, and active tenant count — all ready for Grafana, Datadog, or whatever you run.


A CLI that feels like a superpower.

dotnet tool install -g Convoy.Cli
# What's the state of your fleet right now?
convoy status

# Don't touch anything yet. Just show me what would happen.
convoy migrate --dry-run

# Alright. Do it. Ten at a time.
convoy migrate --parallel 10

# Just these two. Nothing else.
convoy migrate --tenants acme,globex

# That migration broke something. Put it back.
convoy rollback --tenant acme --target 20250401_InitialCreate

# New customer signed up. Provision them.
convoy tenant add --id newcorp --name "New Corp" --connection-string "..."

# Plug it into CI. Get JSON back. Gate your deploy on it.
convoy status --format json

Configure it once. Use it everywhere.

{ "Url": "https://yourapp.com", "ApiKey": "your-key" }

A REST API for everything else.

Admin dashboards. Ops tooling. Internal scripts. CI/CD pipelines. Whatever you're building, the management API has an endpoint for it.

app.MapConvoyManagementApi<AppTenantInfo>();
GET  /convoy/api/tenants                  → list every tenant
POST /convoy/api/tenants                  → create one
POST /convoy/api/tenants/{id}/activate    → go live
POST /convoy/api/tenants/{id}/deactivate  → suspend

GET  /convoy/api/migrations/status        → fleet snapshot
POST /convoy/api/migrations/validate      → pre-flight check
POST /convoy/api/migrations/migrate       → trigger a run
POST /convoy/api/migrations/rollback      → roll it back
GET  /convoy/api/migrations/runs          → history

Install what you need. Leave the rest.

Package What it does
Convoy Core. Tenant model, DI wiring, in-memory store.
Convoy.AspNetCore Middleware + 7 resolution strategies.
Convoy.EFCore Per-tenant DbContext, 3 isolation models, connection pooling.
Convoy.Migrations The fleet migration engine. Parallel, dry-run, rollback, retry, audit.
Convoy.Auth Per-tenant JWT validation, policies, claims transformation.
Convoy.HealthChecks Health probes for your tenant store.
Convoy.OpenTelemetry Spans and metrics for every tenant operation.
Convoy.Management.Api Self-hosted REST API.
Convoy.Stores.SqlServer SQL Server tenant store.
Convoy.Stores.Postgres PostgreSQL tenant store.
Convoy.Stores.Sqlite SQLite tenant store for dev and tests.
Convoy.Cli The convoy command.
# The essentials
dotnet add package Convoy
dotnet add package Convoy.AspNetCore
dotnet add package Convoy.EFCore
dotnet add package Convoy.Migrations
dotnet add package Convoy.Stores.SqlServer

# The full suite
dotnet add package Convoy.Auth
dotnet add package Convoy.HealthChecks
dotnet add package Convoy.OpenTelemetry
dotnet add package Convoy.Management.Api

# The CLI
dotnet tool install -g Convoy.Cli

Get started in five minutes.

// Program.cs — the whole thing
builder.Services.AddConvoy<AppTenantInfo>(convoy =>
{
    convoy
        .WithResolution(r => r.FromSubdomain())
        .WithStore(s => s.UseSqlServer(connectionString))
        .WithEFCore<AppDbContext>(ef =>
        {
            ef.UseIsolation(TenantIsolation.DatabasePerTenant);
            ef.UseProvider((opt, cs) => opt.UseSqlServer(cs));
        })
        .WithMigrations()
        .WithAuth(auth => auth.UseJwtBearer(jwt =>
        {
            jwt.WithIssuerResolver(t => t.Settings["auth:issuer"]);
            jwt.WithAudienceResolver(t => t.Settings["auth:audience"]);
        }))
        .WithHealthChecks()
        .WithOpenTelemetry();
});

app.UseConvoyTelemetry<AppTenantInfo>();
app.UseConvoy<AppTenantInfo>();
app.UseAuthentication();
app.UseAuthorization();
// Anywhere in your app
public class OrderService(IConvoyContext<AppTenantInfo> convoy, AppDbContext db)
{
    public async Task<Order> CreateAsync(CreateOrderRequest req)
    {
        var tenant = convoy.CurrentTenant;
        // tenant.Id, tenant.Plan, tenant.Settings — right here, right now
        // db is already pointed at this tenant's database
        // No extra work. Just write your feature.
    }
}

Built for .NET 8 and .NET 10.

Targets both. Tests pass on both. Ships on both.


Open source. MIT. Always.

git clone https://github.com/priceds/convoy
cd convoy
dotnet build
dotnet test

All tests run without any external dependencies. SQL Server and PostgreSQL integration tests are off by default — set CONVOY_SQLSERVER_TEST_CS or CONVOY_POSTGRES_TEST_CS to turn them on.


Built by Sarvesh Patil · Pune, India

Stop writing foreach loops over your tenants at 2am. Use Convoy.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.1.0 97 5/9/2026

Convoy 1.1.0 adds .NET 10 multi-targeting, tenant resolution, EF Core isolation, fleet migrations, per-tenant auth, health checks, OpenTelemetry, management API, SQL stores, and CLI tooling.