gen-env
npx machina-cli add skill aiskillstore/marketplace/gen-env --openclawgen-env Skill
Generate or review a gen-env command that enables running multiple isolated instances of a project on localhost simultaneously (e.g., multiple worktrees, feature branches, or versions).
The Problem
Without isolation, multiple instances of the same project:
- Fight for hardcoded ports (3000, 5432, 8080)
- Share Docker volumes → data corruption
- Share browser cookies/localStorage → auth confusion
- Have ambiguous container names → can't tell which is which
- Risk catastrophic cleanup →
docker down -vnukes everything
The Solution: Instance Identity
Everything flows from a workspace name:
name = "feature-x"
↓
┌─────────────────────────────────────────────────────┐
│ COMPOSE_PROJECT_NAME = localnet-feature-x │
│ DOCKER_NETWORK = localnet-feature-x │
│ VOLUME_PREFIX = localnet-feature-x │
│ CONTAINER_PREFIX = localnet-feature-x- │
│ TILT_HOST = feature-x.localhost │
│ Ports = dynamically allocated │
│ URLs = derived from host + ports │
└─────────────────────────────────────────────────────┘
Isolation Dimensions
1. Port Isolation
Each instance gets unique ports from ephemeral range (49152-65535).
2. Data Isolation
Docker Compose project name controls volume naming:
- Instance A:
localnet-main_postgres_data - Instance B:
localnet-feature-x_postgres_data
No cross-contamination. Independent databases.
3. Network Isolation
Separate Docker networks per instance. Containers reference each other by service name without collision.
4. Browser State Isolation
Critical: Different ports on localhost still share cookies!
http://localhost:3000 ─┐
├─ SAME cookies, localStorage
http://localhost:3001 ─┘
Solution: subdomain isolation via *.localhost:
http://main.localhost:3000 ─ separate cookies
http://feature-x.localhost:3001 ─ separate cookies
Chrome/Edge treat *.localhost as 127.0.0.1 automatically. No /etc/hosts needed.
5. Auth Isolation
Each instance can have its own auth realm/audience, preventing token confusion.
6. Resource Naming
Clear prefixes on containers, volumes, Tilt resources, logs → know exactly which instance you're looking at.
Implementation Checklist
When creating or reviewing gen-env:
Identity & Naming:
- Requires
--name <workspace>argument - Validates name (alphanumeric + dashes, max 63 chars for DNS)
- Generates
COMPOSE_PROJECT_NAMEfrom name - Generates
DOCKER_NETWORK,VOLUME_PREFIX,CONTAINER_PREFIX - Generates
*_HOSTfor browser isolation (name.localhost)
Port Allocation:
- Allocates from ephemeral range (49152-65535)
- Checks port availability before assignment
- Uses short timeout (100ms) for CI compatibility
- Handles IPv6-disabled environments gracefully
Persistence:
- Lockfile stores name + ports (
.gen-env.lock) - Reuses ports when lockfile exists and name matches
-
--forceregenerates all -
--cleanremoves generated files
Output:
- Generates
.localnet.env(or project-specific name) - Clear header with generation timestamp
- All derived URLs use correct host + port
Integration:
- Script added to PATH via
.envrc - Generated env sourced by
.envrc - Works with Docker Compose (
--env-file) - Works with Tilt (Starlark reads env file)
Generated Environment Structure
# .localnet.env - generated by gen-env
# Instance: feature-x
# Generated: 2024-01-15T10:30:00Z
# === Instance Identity ===
WORKSPACE_NAME=feature-x
COMPOSE_NAME=localnet-feature-x
COMPOSE_PROJECT_NAME=localnet-feature-x
DOCKER_NETWORK=localnet-feature-x
VOLUME_PREFIX=localnet-feature-x
CONTAINER_PREFIX=localnet-feature-x-
# === Host (for browser isolation) ===
APP_HOST=feature-x.localhost
TILT_HOST=feature-x.localhost
# === Allocated Ports ===
POSTGRES_PORT=51234
REDIS_PORT=51235
API_PORT=51236
WEB_PORT=51237
# ... more ports
# === Derived URLs ===
DATABASE_URL=postgres://user:pass@localhost:51234/dev
WEB_URL=http://feature-x.localhost:51237
API_URL=http://feature-x.localhost:51236
direnv Integration
# .envrc
PATH_add bin # or scripts
dotenv_if_exists .localnet.env
Reference Implementation (TypeScript/Bun)
See @IMPLEMENTATION.md for full implementation.
Key types:
interface InstanceConfig {
name: string; // Workspace identity
composeName: string; // Docker Compose project name
dockerNetwork: string; // Docker network name
volumePrefix: string; // Docker volume prefix
containerPrefix: string; // Container name prefix
host: string; // Browser hostname (name.localhost)
ports: Record<string, number>; // Allocated ports
urls: Record<string, string>; // Derived URLs
}
interface LockfileData {
version: 1;
generatedAt: string;
instance: InstanceConfig;
}
Cleanup Patterns
Surgical cleanup per instance:
# Clean only feature-x (containers + volumes + networks)
docker compose -p localnet-feature-x down -v
# Or via gen-env
gen-env --clean # removes .localnet.env and .gen-env.lock
# List all localnet instances
docker ps -a --filter "name=localnet-" --format "table {{.Names}}\t{{.Status}}"
# Nuclear option (all instances) - DANGEROUS
docker ps -a --filter "name=localnet-" -q | xargs docker rm -f
docker volume ls --filter "name=localnet-" -q | xargs docker volume rm
Common Patterns
Pattern 1: Worktree-Based Naming
# Derive name from git worktree directory
WORKTREE_NAME=$(basename "$(git rev-parse --show-toplevel)")
gen-env --name "$WORKTREE_NAME"
Pattern 2: Branch-Based Naming
# Derive name from branch
BRANCH=$(git branch --show-current | tr '/' '-')
gen-env --name "$BRANCH"
Pattern 3: Explicit Naming
# User specifies (recommended for clarity)
gen-env --name bb-dev
gen-env --name testing-v2
Review Checklist
When reviewing an existing gen-env:
- Does it create instance identity? (not just ports)
- Does it set COMPOSE_PROJECT_NAME? (controls Docker naming)
- Does it generate a browser-safe host? (
*.localhost) - Are URLs derived with correct host? (not hardcoded
localhost) - Is cleanup surgical? (can remove one instance without affecting others)
- Does the lockfile store the name? (for consistency across runs)
- Does it validate name conflicts? (warn if lockfile has different name)
Anti-Patterns
❌ Hardcoded localhost in URLs
WEB_URL=http://localhost:${WEB_PORT} # BAD: shares cookies
✅ Use instance host
WEB_URL=http://${APP_HOST}:${WEB_PORT} # GOOD: isolated cookies
❌ No COMPOSE_PROJECT_NAME
# BAD: uses directory name, may conflict
docker compose up
✅ Explicit project name
COMPOSE_PROJECT_NAME=localnet-feature-x
docker compose up # Uses project name for all resources
❌ Shared cleanup
docker compose down -v # BAD: which instance?
✅ Instance-specific cleanup
docker compose -p localnet-feature-x down -v # GOOD: explicit
References
- @IMPLEMENTATION.md - Full TypeScript implementation
- @ADVANCED_PATTERNS.md - Complex scenarios (monorepos, CI, Tilt integration)
Source
git clone https://github.com/aiskillstore/marketplace/blob/main/skills/0xbigboss/gen-env/SKILL.mdView on GitHub Overview
gen-env generates or reviews a command to run multiple isolated instances of a project on localhost. It centralizes workspace identity, port allocation, data isolation, and browser-state separation, plus cleanups to prevent cross-environment contamination.
How This Skill Works
Given a workspace name, gen-env derives per-instance identifiers like COMPOSE_PROJECT_NAME, DOCKER_NETWORK, VOLUME_PREFIX, CONTAINER_PREFIX, and a name.localhost host. It allocates ports from the ephemeral 49152-65535 range, checks availability, and writes a .localnet.env and a .gen-env.lock to persist state, enabling reproducible, collision-free Docker Compose or Tilt deployments. Browser state is isolated using subdomains (e.g., feature.localhost) to separate cookies and localStorage.
When to Use It
- Working on multiple feature branches or worktrees of the same app locally
- Running parallel dev environments for different versions
- Need separate databases, volumes, and network namespaces per instance
- Avoiding browser cookie/localStorage collisions across localhost deployments
- Preparing repeatable env setups for CI, demos, or handoffs
Quick Start
- Step 1: gen-env --name <workspace> to generate per-instance env data and .localnet.env
- Step 2: source or load the generated env (e.g., eval "$(cat .localnet.env)") so Docker/ Tilt can use it
- Step 3: bring up the environment with Docker Compose or Tilt, e.g. docker-compose --env-file=.localnet.env up -d
Best Practices
- - Always pass a unique --name <workspace> to establish a distinct identity
- - Validate workspace names as alphanumeric with dashes (max 63 chars for DNS compatibility)
- - Rely on the generated COMPOSE_PROJECT_NAME, DOCKER_NETWORK, VOLUME_PREFIX, and CONTAINER_PREFIX for isolation
- - Use the ephemeral port range (49152-65535) and verify port availability before assignment
- - Maintain and reference the .gen-env.lock and .localnet.env for reproducible environments
Example Use Cases
- Feature X branch: gen-env --name feature-x creates localnet-feature-x environments with isolated DBs and distinct URLs
- Main vs feature-x: separate Docker networks and volumes prevent data cross-contamination (localnet-main_postgres_data vs localnet-feature-x_postgres_data)
- Browser isolation via subdomains: http://main.localhost:3000 and http://feature-x.localhost:3001
- CI ready: generate environment, lock ports, and reuse them when name matches to speed up pipelines
- Demo clusters: quick spin-up of multiple versions for customer demos with clean teardown capabilities (--clean)