previewctl
A CLI tool for managing isolated, reproducible local development environments. Spin up complete sandboxes — git worktrees, Docker infrastructure, cloned databases, and auto-generated config — with a single command.
Features
- Isolated compute — Git worktrees + Docker Compose per environment
- Database cloning — Template-based PostgreSQL cloning for fast, isolated copies
- Deterministic port allocation — Unique, conflict-free ports per environment
- Auto-generated
.env files — Template variables for ports, database URLs, and more
- Lifecycle hooks — Run custom scripts before/after any step
- Persistent state — Track and manage multiple concurrent environments
Install
From source
make build
# Binary at ./bin/previewctl
With Go
go install github.com/jake-landersweb/previewctl/src/cmd/previewctl@latest
Quick start
- Create a
previewctl.yaml in your project root (see Configuration)
- Validate your config:
previewctl vet
- Seed your template database:
previewctl db seed --snapshot path/to/snapshot.sql
- Create an environment:
previewctl create my-feature --branch feat/my-feature
Usage
previewctl create <name> [-b branch] Create a new environment
previewctl list [--json] List all environments
previewctl status [name] Show environment details
previewctl delete [name] Destroy an environment and its resources
previewctl db seed [--snapshot path] Seed the template database
previewctl db reset [name] [--db name] Reset an environment's database from template
previewctl vet Validate previewctl.yaml
Configuration
Create a previewctl.yaml in your project root:
version: 1
name: myproject
packageManager: pnpm # optional
core:
databases:
main:
engine: postgres
image: postgres:16
port: 5432
user: postgres
password: postgres
templateDb: template_db
seed:
strategy: snapshot
snapshot:
source: local
script: schema/seed.sql
infrastructure:
redis:
image: redis:7-alpine
port: 6379
services:
backend:
path: apps/backend
port: 8000
command: pnpm dev
dependsOn: [redis]
env:
PORT: "{{ports.backend}}"
DATABASE_URL: "{{databases.main}}"
web:
path: apps/web
port: 3000
dependsOn: [backend]
env:
NEXT_PUBLIC_API_URL: "http://localhost:{{ports.backend}}"
local:
worktree:
basePath: ~/worktrees
symlinkPatterns: [".env", ".env.*"]
composeFile: compose.worktree.yaml
hooks:
create:
after:
- run: npm run migrate
continueOnError: true
Template variables
Use these in service env values:
| Variable |
Description |
{{ports.<service>}} |
Allocated port for a service |
{{databases.<name>}} |
Connection string for a database |
{{env.<VAR>}} |
Value from an existing environment variable |
Hooks
Hooks can be attached to any lifecycle step: allocate_ports, create_compute, ensure_database, clone_database, symlink_env, generate_env, start_infra, save_state, and top-level create/delete/reset.
Scripts receive context via environment variables: PREVIEWCTL_ENV_NAME, PREVIEWCTL_BRANCH, PREVIEWCTL_WORKTREE_PATH, PREVIEWCTL_STEP, PREVIEWCTL_PHASE, plus port and database info as JSON.
How it works
When you run previewctl create, the tool:
- Allocates deterministic ports (FNV-1a hash of environment name)
- Creates a git worktree for code isolation
- Clones databases from templates
- Symlinks shared env files from the main worktree
- Generates
.env.local files with resolved template variables
- Starts infrastructure containers via Docker Compose
- Persists state to
~/.cache/previewctl/<project>/state.json
previewctl delete reverses all of the above, cleaning up every resource.
License
AGPL-3.0