Workspace Strategy
How to structure your project when working with Nodevisor packages.
Umbrella vs. Individual Packages
Nodevisor is split into ~20 focused packages. You have two ways to consume them:
The nodevisor umbrella
One dependency, one import source. Best for application-level scripts and deployment configs.
import $, { Docker, UFW, Users, SSH, DockerCluster, DockerNode } from 'nodevisor';
Individual @nodevisor/* packages
Install only what you need. Best for libraries, CI scripts, or when bundle size matters.
import $ from '@nodevisor/shell';
import Docker from '@nodevisor/docker';
import UFW from '@nodevisor/ufw';
Rule of thumb: Use nodevisor for deployment scripts and cluster definitions. Use individual packages when building reusable modules or when you only need one or two capabilities.
Project Layout
A typical Nodevisor project looks like this:
my-project/
├── .nodevisor/
│ ├── .env # Secrets (DB passwords, registry tokens)
│ ├── production.ts # Production cluster definition
│ └── staging.ts # Staging cluster definition
├── apps/
│ ├── api/ # Your application code
│ └── web/
├── package.json
└── tsconfig.json
The .nodevisor/ directory is the conventional location for cluster definitions. The CLI automatically looks there when you pass a bare filename:
# These are equivalent:
nv deploy production.ts
nv deploy .nodevisor/production.ts
The CLI also auto-loads .env files from the same directory as the cluster file.
Cluster Definition Patterns
Single environment
For simple projects, one file per environment:
// .nodevisor/production.ts
import { DockerCluster, DockerNode, NodeWeb, Postgres, Traefik } from 'nodevisor';
const cluster = new DockerCluster({
name: 'production',
nodes: [new DockerNode({ host: '10.0.0.1' })],
});
cluster.addDependency(new Traefik({ ssl: { email: 'ops@example.com' } }));
cluster.addDependency(new Postgres({ database: 'app', username: 'app', password: process.env.DB_PASSWORD! }));
cluster.addDependency(new NodeWeb({ name: 'api', appDir: './apps/api', domains: ['api.example.com'], port: 3000 }));
export default cluster;
Shared services across environments
Extract service definitions to keep cluster files DRY:
// .nodevisor/services.ts
import { NodeWeb, Postgres, Traefik } from 'nodevisor';
export const traefik = (email: string) => new Traefik({ ssl: { email } });
export const postgres = (password: string) => new Postgres({
database: 'app',
username: 'app',
password,
});
export const api = (domains: string[]) => new NodeWeb({
name: 'api',
appDir: './apps/api',
domains,
port: 3000,
});
// .nodevisor/production.ts
import { DockerCluster, DockerNode } from 'nodevisor';
import { traefik, postgres, api } from './services';
const cluster = new DockerCluster({
name: 'production',
nodes: [
new DockerNode({ host: '10.0.0.1' }),
new DockerNode({ host: '10.0.0.2' }),
],
});
cluster.addDependency(traefik('ops@example.com'));
cluster.addDependency(postgres(process.env.DB_PASSWORD!));
cluster.addDependency(api(['api.example.com']));
export default cluster;
Environment Variables
Keep secrets in .env files next to your cluster definitions:
# .nodevisor/.env
DB_PASSWORD=super-secret
REDIS_PASSWORD=also-secret
REGISTRY_TOKEN=ghp_xxxxx
The CLI loads this automatically. In your cluster file, just use process.env:
new Postgres({
password: process.env.DB_PASSWORD!,
});
Add .nodevisor/.env to your .gitignore.
Related
- Getting Started — Installation and first steps
@nodevisor/cli— CLI command reference- Docker Cluster Example — Full production deployment example