Multiple environments

Run isolated CocoIndex environments side by side with explicit Environment builders and separate db_paths — for multi-tenancy, library development, and testing.

Version
v 1.0.14
Last reviewed
Nov 4, 2026
ExperimentalThe Rust SDK is experimental. It is outside CocoIndex's compatibility promise: any release, including a minor one, may change or remove its API. It is not yet published on crates.io.
An environment manages a database and the context values shared by its apps. Most programs build one environment, through Environment::from_registered() or a builder. You can create multiple isolated environments when you need:
  • Library development: Libraries that depend on CocoIndex can use their own environment to avoid sharing state with other libraries or the application
  • Database isolation: Different apps using separate databases
  • Multi-tenant deployments: Isolated data per tenant
  • Testing: Isolated test environments that don’t interfere with production

Creating an Environment

Create an explicit environment with Environment::builder() and a db_path:

use cocoindex::Environment;

let env = Environment::builder()
    .db_path("./my_db.db")
    .build()
    .await?;

An Environment built this way reads no environment variables. Environment::from_registered() is the path that runs the #[cocoindex::lifespan] functions and falls back to COCOINDEX_DB; it is what the cocoindex CLI uses.

The builder also has lmdb_max_dbs(..) and lmdb_map_size(..) for tuning the internal LMDB database. See Internal Storage for details.

Different from Python

An Environment has no name of its own: the name a handler sees in ExceptionContext::env_name is the app’s name.

Associating Apps with an Environment

Create the app from the environment:

let app = env.app("MyApp").await?;

Several apps can share one environment: each name is its own LMDB namespace, and they share the environment’s provided resources. App::open(name, db_path) and App::builder(name) build a single-app environment for you.

Different from Python

There is no implicit default environment: an App always comes from an Environment you built, or from Environment::from_registered(), which runs the #[cocoindex::lifespan] functions and reads COCOINDEX_DB when no lifespan set a db path.

Closing an Environment

An environment holds its database open, and LMDB allows one environment per db_path per process: while one is open, building another on the same db_path fails with an error that says the database “is already open in this process”.

Call env.shutdown().await once you’re done with it to close the database:

env.shutdown().await;
let env = Environment::builder().db_path("./my_db.db").build().await?;

shutdown() runs the on_shutdown hooks registered on the builder, most recently registered first, then closes the state db. Await every running update or drop before calling it.

Example: Multiple Environments

This example creates two apps in different environments, each with its own database:

use cocoindex::prelude::*;

#[cocoindex::function]
async fn build1(_ctx: &Ctx) -> Result<()> {
    // ... pipeline logic for env1 ...
    Ok(())
}

#[cocoindex::function]
async fn build2(_ctx: &Ctx) -> Result<()> {
    // ... pipeline logic for env2 ...
    Ok(())
}

// Two environments with separate databases
let env1 = Environment::builder().db_path("./db1/cocoindex.db").build().await?;
let env2 = Environment::builder().db_path("./db2/cocoindex.db").build().await?;

// Apps in different environments
let app1 = env1.app("App1").await?;
let app2 = env2.app("App2").await?;
app1.run(|ctx| async move { build1(&ctx).await }).await?;
app2.run(|ctx| async move { build2(&ctx).await }).await?;

Same App Name in Different Environments

Apps with the same name can coexist in different environments. This is useful for multi-tenant scenarios or running the same pipeline against different databases: env_alpha.app("MyApp") and env_default.app("MyApp") are two apps with their own state, one per environment.

Different from Python

The cocoindex CLI for Rust drives the one environment that Environment::from_registered() builds, so it has no @env_name syntax.

Testing with Isolated Environments

For tests, create isolated environments to avoid interference between test runs:

async fn temp_app(name: &str) -> (App, tempfile::TempDir) {
    let dir = tempfile::tempdir().unwrap();
    let app = App::builder(name)
        .db_path(dir.path().join(".cocoindex_db"))
        .build()
        .await
        .unwrap();
    (app, dir)
}

#[tokio::test]
async fn my_pipeline() {
    let (app, _dir) = temp_app("TestApp").await;
    app.update(|ctx| async move { my_main_fn(&ctx).await }).await.unwrap();
    // ... assertions ...
}

Keep the TempDir alive for the test: dropping it deletes the database directory.

When to Use Multiple Environments

Use CaseApproach
Single app, single databaseApp::open(name, db_path) or App::builder(name) (a single-app environment)
Multiple apps sharing stateOne environment, several env.app(..)
Apps needing separate databasesExplicit environments with different db_path
Multi-tenant isolationOne environment per tenant
TestingTemporary isolated environments