vivarium-cli
v1.0.2
Published
Local dev stack manager — auto-allocates ports, manages Docker Compose services, generates .env files
Maintainers
Readme
Vivarium
Local dev stack manager. Auto-allocates ports, manages Docker Compose services, generates .env files, and provides a single CLI for the entire local development lifecycle.
Table of Contents
- The Problem
- Philosophy
- Prerequisites
- Installation
- Quick Start
- What Setup Does
- CLI Reference
- Port Allocation
- Configuration
- Teardown
- MCP Proxy
- How It Works
- Development
- Contributing
- AI Disclosure
- License
The Problem
When you work on multiple projects that each run PostgreSQL, Redis, and object storage locally, port conflicts are inevitable. Hardcoded ports in compose files mean you can only run one project at a time, or you spend time manually juggling port assignments.
Vivarium solves this by assigning each project a unique index (0--99) and deterministically computing all ports from that index. No conflicts. No manual bookkeeping.
Philosophy
This is an opinionated tool. It makes deliberate choices about how local development infrastructure should work:
- Convention over configuration. Known package names (
backend,frontend) get standard env vars automatically. You only configure what differs from the defaults. - Generated artifacts are ephemeral. Everything Vivarium produces goes into the registry at
~/.local/share/vivarium/<project>/. The source of truth is yourvivarium.json, not the generated files. - One command to rule them all.
vivarium setuptakes you from zero to a fully running local stack with seeded databases and generated env files. - No runtime dependencies beyond Docker. If Docker and
jqare installed, Vivarium works.
If these opinions don't align with your workflow, this may not be the right tool for you.
Prerequisites
- Node.js >= 20
- Docker with Compose v2 (included in Docker Desktop)
- AWS CLI -- only required if using the S3 service, for bucket creation
Vivarium supports macOS and Linux (including WSL).
Installation
npm install -g vivarium-cli
# or as a devDependency
npm install --save-dev vivarium-cliThe package is published as vivarium-cli on npm. Once installed, the CLI is available as vivarium.
Quick Start
- Create a
vivarium.jsonin your project root:
{
"services": {
"postgres": { "user": "myapp", "password": "myapp", "database": "myapp" },
"redis": true,
"s3": { "accessKey": "admin", "secretKey": "admin", "buckets": ["uploads"] }
},
"packages": {
"backend": {
"envFile": "backend/.env",
"env": { "CUSTOM_VAR": "value" },
"postSetup": ["pnpm db:migrate"]
},
"frontend": {
"envFile": "frontend/.env"
}
}
}- Run setup:
vivarium setupVivarium is now running. Here is what happened:
- An index (0--99) was assigned to your project and all service ports were computed from it
- A Docker Compose stack was generated at
~/.local/share/vivarium/<project>/compose.yamland started - A
.envfile for Docker Compose interpolation was written alongside the compose file - Package-level
.envfiles (e.g.backend/.env,frontend/.env) were written with connection strings matching the assigned ports - Any
postSetupscripts (migrations, seeds) were executed
No files are written to your project directory except the package
.envfiles you explicitly configured.
What Setup Does
The vivarium setup command executes these steps in order:
- Check prerequisites -- verifies
dockerandjqare on PATH - Load config -- reads
vivarium.json(orpackage.json["vivarium"]) - Claim index -- assigns the lowest available index (0--99), checking both the registry and actual port usage
- Compute ports -- deterministically derives all service ports from the index
- Generate compose.yaml -- builds a Docker Compose file and writes it to
~/.local/share/vivarium/<project>/ - Generate .env -- writes compose interpolation variables (ports, credentials) alongside the compose file
- Pull and start services -- runs
docker compose pullthendocker compose up -d --wait - Create S3 buckets -- if S3 is configured, creates buckets via the AWS CLI
- Write package .env files -- generates
.envfiles for each package at the paths specified byenvFile - Persist state -- writes
state.jsonto the registry directory with the project's index, ports, and metadata - Run postSetup scripts -- executes each package's
postSetupcommands in sequence - Print summary -- displays assigned ports and a reminder to start your dev servers
All generated infrastructure artifacts go into ~/.local/share/vivarium/<project>/. Only the package .env files are written into your project directory.
CLI Reference
All commands must be run from your project root (the directory containing vivarium.json or package.json).
| Command | Description |
|--------------------------------|------------------------------------------------------------------------------------------------------------------------|
| vivarium setup | Full setup: claim index, compute ports, generate compose and env files, start services, create S3 buckets, run hooks |
| vivarium teardown | Full teardown: stop and remove containers/volumes, release index, delete generated files |
| vivarium start | Start existing compose services (requires prior setup; no index claiming or env generation) |
| vivarium stop | Stop compose services without removing volumes or releasing the index |
| vivarium status | Show project state: claimed index, assigned ports, and container status |
| vivarium compose [args...] | Pass-through to docker compose using the generated config |
| vivarium mcp-proxy <service> | Start a stdio-to-SSE bridge for an MCP service container. See MCP Proxy |
start vs setup
vivarium start restarts previously created containers. It does not regenerate configuration, reassign ports, or run postSetup scripts. If you have changed vivarium.json, run vivarium setup again.
compose pass-through
vivarium compose forwards all arguments to docker compose using the generated config from the registry. For example:
vivarium compose logs -f postgres
vivarium compose exec postgres psql -U myapp -d myappPort Allocation
Given an index i (0--99), ports are computed deterministically:
| Service | Formula | Index 0 | Index 1 |
|----------------|-----------------------|---------|---------|
| PostgreSQL | 5433 + i | 5433 | 5434 |
| Redis | 6380 + i | 6380 | 6381 |
| S3 API | 9010 + (i * 10) | 9010 | 9020 |
| S3 Console | 9011 + (i * 10) | 9011 | 9021 |
| Frontend | 4000 + (i * 10) | 4000 | 4010 |
| Backend | 4001 + (i * 10) | 4001 | 4011 |
Indexes are claimed via a registry at ~/.local/share/vivarium/. Vivarium auto-assigns the lowest available index, checking both the registry and actual port usage to handle legacy setups.
Configuration
Vivarium looks for configuration in this order:
vivarium.jsonat the project root"vivarium"key inpackage.json
Services
| Service | Config |
|------------|------------------------------------------------------------------------|
| postgres | { "user": "...", "password": "...", "database": "..." } |
| redis | true |
| s3 | { "accessKey": "...", "secretKey": "...", "buckets": ["..."] } |
When postgres is enabled, a postgres-mcp sidecar (CrystalDBA) is automatically included for AI-assisted database tooling.
Packages
Each entry in packages can specify:
envFile-- path (relative to project root) where the generated.envwill be writtenenv-- custom env vars merged on top of convention defaultspostSetup-- shell commands to run after services are ready (executed in the package directory)framework--"nextjs"(default) or"vite". Controls the public env var prefix (NEXT_PUBLIC_vsVITE_) for frontend packagesdirectory-- directory path (relative to project root) forpostSetupcommand execution. Defaults to the package key name
Convention-Based Env Vars
For known package names, Vivarium generates standard env vars automatically. Custom env entries always take precedence.
Backend gets: DATABASE_URL, REDIS_ENABLED, REDIS_URL, AWS_S3_*, API_LISTEN_PORT, API_URL, FRONTEND_URL (when applicable).
Frontend gets: PORT, {PREFIX}_FRONTEND_URL, {PREFIX}_API_URL, {PREFIX}_ASSET_SRC (when applicable). The prefix is NEXT_PUBLIC_ by default or VITE_ when "framework": "vite" is set.
Teardown
vivarium teardown reverses everything setup created:
- Stop Docker Compose services and remove containers and volumes (
docker compose down --remove-orphans --volumes) - Release the claimed index by deleting the project's registry directory (
~/.local/share/vivarium/<project>/) - Clean up legacy artifacts -- removes
.vivarium/from the project root if it exists (from older versions) - Delete generated package .env files -- removes each file specified in
packages.<name>.envFile
After teardown, the index is free for another project to claim.
MCP Proxy
When PostgreSQL is enabled, Vivarium automatically includes a CrystalDBA sidecar container that exposes a Model Context Protocol (MCP) server over SSE. This allows AI tools (such as Claude Desktop, Cursor, or other MCP-compatible clients) to query and inspect your local database.
The sidecar runs inside the Docker network and is not exposed to the host. To bridge it to a local MCP client that expects stdio, use:
vivarium mcp-proxy postgres-mcpThis spawns a short-lived Docker container running mcp-proxy that translates between stdio and the sidecar's SSE endpoint (http://postgres-mcp:8000/sse).
You can reference this in your MCP client configuration. For example, in Claude Desktop's claude_desktop_config.json:
{
"mcpServers": {
"vivarium-db": {
"command": "vivarium",
"args": ["mcp-proxy", "postgres-mcp"]
}
}
}How It Works
Vivarium is a synchronous Node.js CLI (~500 lines of TypeScript) with two runtime dependencies: commander for argument parsing and yaml for Compose file serialization.
Registry. Each project's state is stored in ~/.local/share/vivarium/<project>/ as three files: state.json (index, ports, metadata), compose.yaml (generated Docker Compose), and .env (Compose interpolation variables). No files are written to the source project directory except package .env files.
Port computation. All ports are derived from a single index (0--99) via arithmetic formulas in src/ports.ts. Index assignment checks both the registry and actual port usage on the host to avoid collisions with non-Vivarium services.
Compose generation. Docker Compose files are constructed as plain JavaScript objects and serialized via the yaml library. Services use public images: PostgreSQL 18, Valkey 8 (Redis-compatible), and RustFS (S3-compatible).
src/
cli.ts -- Commander entrypoint, registers subcommands
config.ts -- Loads vivarium.json or package.json["vivarium"]
ports.ts -- Deterministic port computation
registry.ts -- Project state in ~/.local/share/vivarium/
compose.ts -- Docker Compose YAML generation
env.ts -- .env generation (compose + per-package)
commands/ -- One file per CLI command
utils/ -- Docker helpers, logger, prerequisite checksDevelopment
git clone https://github.com/niranjan94/vivarium.git
cd vivarium
pnpm install| Command | Purpose |
|----------------|----------------------------------------------------|
| pnpm build | Compile TypeScript to dist/ |
| pnpm dev | Watch mode compilation |
| pnpm format | Lint and format with auto-fix (Biome) |
| pnpm test | Lint and format check + run tests |
The project uses Biome for linting and formatting (2-space indent, single quotes). All source is TypeScript ESM with .js import extensions.
Contributing
Vivarium is opinionated by design, but contributions are welcome. If you have ideas for improving the tool, fixing bugs, or supporting additional service types, please open an issue or submit a pull request.
When contributing, keep in mind:
- The tool should remain dependency-light at runtime (
commanderfor CLI parsing andyamlfor Compose file generation are the only dependencies) - Configuration validation is intentionally minimal -- trust the developer
See Development for build instructions. Report bugs and suggest features at github.com/niranjan94/vivarium/issues.
AI Disclosure
Parts of this project were developed with the assistance of AI coding tools. All generated code has been reviewed and tested by the maintainer.
License
MIT -- Niranjan Rajendran
