Get the FREE Ultimate OpenClaw Setup Guide →

gen-env

npx machina-cli add skill aiskillstore/marketplace/gen-env --openclaw
Files (1)
SKILL.md
7.8 KB

gen-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 -v nukes 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_NAME from name
  • Generates DOCKER_NETWORK, VOLUME_PREFIX, CONTAINER_PREFIX
  • Generates *_HOST for 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
  • --force regenerates all
  • --clean removes 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:

  1. Does it create instance identity? (not just ports)
  2. Does it set COMPOSE_PROJECT_NAME? (controls Docker naming)
  3. Does it generate a browser-safe host? (*.localhost)
  4. Are URLs derived with correct host? (not hardcoded localhost)
  5. Is cleanup surgical? (can remove one instance without affecting others)
  6. Does the lockfile store the name? (for consistency across runs)
  7. 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

  1. Step 1: gen-env --name <workspace> to generate per-instance env data and .localnet.env
  2. Step 2: source or load the generated env (e.g., eval "$(cat .localnet.env)") so Docker/ Tilt can use it
  3. 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)

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers