gen-env
Creates, updates, or reviews a project's gen-env command for running multiple isolated instances on localhost. Handles instance identity, port allocation, data isolation, browser state separation, and cleanup.
What this skill does
# 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
```bash
# .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
```bash
# .envrc
PATH_add bin # or scripts
dotenv_if_exists .localnet.env
```
## Reference Implementation (TypeScript/Bun)
See @IMPLEMENTATION.md for full implementation.
Key types:
```typescript
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:
```bash
# 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
```bash
# Derive name from git worktree directory
WORKTREE_NAME=$(basename "$(git rev-parse --show-toplevel)")
gen-env --name "$WORKTREE_NAME"
```
### Pattern 2: Branch-Based Naming
```bash
# Derive name from branch
BRANCH=$(git branch --show-current | tr '/' '-')
gen-env --name "$BRANCH"
```
### Pattern 3: Explicit Naming
```bash
# 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**
```bash
WEB_URL=http://localhost:${WEB_PORT} # BAD: shares cookies
```
✅ **Use instance host**
```bash
WEB_URL=http://${APP_HOST}:${WEB_PORT} # GOOD: isolated cookies
```
❌ **No COMPOSE_PROJECT_NAME**
```bash
# BAD: uses directory name, may conflict
docker compose up
```
✅ **Explicit project name**
```bash
COMPOSE_PROJECT_NAME=localnet-feature-x
docker compose up # Uses project name for all resources
```
❌ **Shared cleanup**
```bash
docker compose down -v # BAD: which instance?
```
✅ **Instance-specific cleanup**
```bash
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)
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.