Multiple environments
Run isolated CocoIndex environments side by side with explicit Environment and Settings objects and separate db_paths — for multi-tenancy, library development, and testing. The CLI targets named environments via @env_name.
By default, all CocoIndex apps share the same environment, which manages a shared database and context. For most use cases, this default behavior is sufficient. However, 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 by providing Settings with a db_path:
import cocoindex as coco
import pathlib
env = coco.Environment(coco.Settings.from_env(db_path=pathlib.Path("./my_db.db")))
You can optionally name the environment for easier identification:
env = coco.Environment(
coco.Settings.from_env(db_path=pathlib.Path("./my_db.db")),
name="production"
)
Settings also accepts db_settings — a coco.LmdbSettings or coco.PgSettings instance, matched to db_path’s form — for tuning the underlying backend. See State Storage for backend selection and per-backend tuning.
Associating Apps with an Environment
Pass the environment to AppConfig when creating an app:
app = coco.App(
coco.AppConfig(name="MyApp", environment=env),
main_fn,
)
Apps that don’t specify an environment use the default environment (configured via @coco.lifespan or the COCOINDEX_DB environment variable).
Closing an Environment
An environment holds its database open. A process can’t open the same db_path again until the environment is closed. Call env.close() once you’re done with it:
env.close()
env = coco.Environment(coco.Settings.from_env(db_path=pathlib.Path("./my_db.db")))
close() waits for in-flight writes to finish. After that, using the environment raises an error, and so does updating the apps that use it. Context values provided to the environment stay open: whoever provided them closes them. Stopping the default environment (leaving coco.runtime(), or calling coco.stop()) closes it for you.
Don’t count on garbage collection to close it. An environment created while an event loop is running (for example, inside a function run by asyncio.run()) runs its apps on that loop. On free-threaded Python 3.14+, that loop’s worker threads, which connectors use for file I/O, then keep the environment alive, and its database open, for as long as the loop runs — even after you drop every reference to it. So do threads you start from your own functions while an app updates.
Reopening a Postgres db_path never waits on a close: Postgres doesn’t hold the database exclusively. Closing a Postgres environment closes its connections, and later use of it raises the same error. It doesn’t wait for in-flight writes, though: each finishes on its connection, which then closes.
Example: Multiple Environments
This example creates two apps in different environments, each with its own database:
import cocoindex as coco
import pathlib
# Create two environments with separate databases
env1 = coco.Environment(coco.Settings.from_env(db_path=pathlib.Path("./db1/cocoindex.db")))
env2 = coco.Environment(coco.Settings.from_env(db_path=pathlib.Path("./db2/cocoindex.db")))
@coco.fn
def build1() -> None:
# ... pipeline logic for env1 ...
pass
@coco.fn
def build2() -> None:
# ... pipeline logic for env2 ...
pass
# Apps in different environments
app1 = coco.App(coco.AppConfig(name="App1", environment=env1), build1)
app2 = coco.App(coco.AppConfig(name="App2", environment=env2), build2)
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:
import cocoindex as coco
import pathlib
from typing import Iterator
# Named environment for "alpha" tenant
env_alpha = coco.Environment(
coco.Settings.from_env(db_path=pathlib.Path("./alpha/cocoindex.db")),
name="alpha"
)
# Default environment via lifespan
@coco.lifespan
def _lifespan(builder: coco.EnvironmentBuilder) -> Iterator[None]:
builder.settings.db_path = pathlib.Path("./default/cocoindex.db")
yield
@coco.fn
def build() -> None:
# ... pipeline logic ...
pass
# Same app name, different environments
app_alpha = coco.App(coco.AppConfig(name="MyApp", environment=env_alpha), build)
app_default = coco.App("MyApp", build) # Uses default environment
CLI Support
When working with multiple environments, the CLI groups apps by their environment. Use the @env_name syntax to target a specific app:
# List all apps grouped by environment
cocoindex ls ./multi_env.py
# Update a specific app in a named environment
cocoindex update ./multi_env.py:MyApp@alpha
# Update the app in the default environment
cocoindex update ./multi_env.py:MyApp@default
Testing with Isolated Environments
For tests, create isolated environments to avoid interference between test runs:
import cocoindex as coco
import pathlib
import tempfile
def create_test_env(test_name: str) -> coco.Environment:
db_path = pathlib.Path(tempfile.mkdtemp()) / f"{test_name}.db"
return coco.Environment(coco.Settings.from_env(db_path=db_path))
# In your test
def test_my_pipeline():
env = create_test_env("test_my_pipeline")
app = coco.App(
coco.AppConfig(name="TestApp", environment=env),
my_main_fn,
)
app.update_blocking()
# ... assertions ...
When to Use Multiple Environments
| Use Case | Approach |
|---|---|
| Single app, single database | Default environment (no explicit environment needed) |
| Multiple apps sharing state | Default environment |
| Apps needing separate databases | Explicit environments with different db_path |
| Multi-tenant isolation | Named environments per tenant |
| Testing | Temporary isolated environments |