Complete reference for zap.yaml syntax and all CLI commands.
- Installation
- Project Configuration
- CLI Commands
- Native Processes
- Docker Services
- Environment Variables
- Instances
- Tasks
- Dependencies
- Profiles
- Links
- Git Cloning
npm install -g pm2 @maplab/zapperFor VS Code/Cursor, install the extension: felixsebastian.zapper-vscode
project: myapp
native:
api:
cmd: pnpm devproject: myapp # Required. Used as PM2/Docker namespace
env_files: # Load env vars from these files
default: [.env.base, .env]
prod_dbs: [.env.base, .env.prod-dbs]
ports: # Port names to assign random values
- FRONTEND_PORT
- BACKEND_PORT
git_method: ssh # ssh | http | cli (for repo cloning)
native:
# ... process definitions
docker:
# ... container definitions
tasks:
# ... task definitions
homepage: http://localhost:3000 # Optional default URL for `zap launch`
links:
# ... quick reference linksAvailable with any command:
--config <file> # Use a specific config file (default: zap.yaml)
-v, --verbose # Increase logging verbosity
-q, --quiet # Reduce logging output
-d, --debug # Enable debug loggingExamples:
zap --config prod.yaml up
zap --config staging.yaml status
zap --debug restart
zap --verbose --config custom.yaml task buildzap up # Start all services
zap up backend # Start one service (and its dependencies)
zap up api worker db # Start multiple services
zap up --json # Output command result as JSON
zap down # Stop all services
zap down backend # Stop one service
zap down api worker db # Stop multiple services
zap down backend --json # Output command result as JSON
zap restart # Restart all services
zap restart api # Restart one service
zap restart api worker db # Restart multiple services
zap r api worker # Short alias for: zap restart api workerzap status # Show status of all services
zap status api db # Show status for specific services
zap logs api # Follow logs for one service
zap logs api worker --no-follow # Show logs for multiple services and exitWhen passing multiple services to zap logs, use --no-follow.
zap task # List all tasks
zap task <name> # Run a task
zap run <name> # Alias for: zap task <name>
zap task seed
zap task build --target=prod # Run with named parameters
zap task test -- --coverage # Run with pass-through args
zap task build --list-params # Show task parameters as JSONzap reset # Stop all services and delete .zap folder
zap reset --json # Output command result as JSON
zap kill # Kill all PM2 processes and containers for current project, across all instances
zap kill my-old-project # Kill all PM2 processes and containers for a specific project, across all instances
zap kill --force # Skip the interactive confirmation
zap kill --json # Output kill result as JSON
zap clone # Clone all repos defined in config
zap clone api # Clone one repo
zap clone api web # Clone multiple repos
zap clone --json # Output command result as JSON
zap assign # Assign random ports to ports defined in config
zap assign --json # Output as JSON
zap launch # Open homepage (if configured)
zap launch "API Docs" # Open a configured link by name
zap launch "API Docs" --json # Output command result as JSON
zap open # Alias for: zap launch
zap o "API Docs" # Short alias for: zap launch "API Docs"
zap isolate --json # Output isolation result as JSONzap profile dev # Enable a profile
zap profile --disable # Disable active profile
zap profile dev --json # Output profile action result as JSONzap env --list # List available environment sets
zap env prod_dbs # Switch env file set
zap env --disable # Reset to default env set
zap env prod_dbs --json # Output environment action result as JSONAliases:
zap environment --list
zap envset prod_dbsMost non-streaming commands support --json and will print machine-readable JSON to stdout.
Examples: up, down, restart, clone, reset, kill, status, task (list/params), profile, env, state, config, launch, isolate, and git subcommands.
Streaming commands keep stream output and are not JSON-encoded:
zap logs <service> [more-services...] [--no-follow]
zap task <name>zap kill <project> does not require a local zap.yaml; it targets resources by prefix (zap.<project>.*).
Native processes run via PM2 on your local machine.
native:
api:
cmd: pnpm devnative:
api:
cmd: pnpm dev # Required. Command to run
cwd: ./backend # Working directory (relative to zap.yaml)
env: # Env vars to pass (whitelist)
- PORT
- DATABASE_URL
- DEBUG=true # Inline override
depends_on: [postgres] # Start these first
profiles: [dev, test] # Only start when profile matches
repo: myorg/api-repo # Git repo (for zap clone)
healthcheck: 10 # Seconds to wait before considering "up"
# OR
healthcheck: http://localhost:3000/health # URL to poll for readinessnative:
frontend:
cmd: pnpm dev
cwd: ./packages/frontend # Relative to project rootnative:
api:
cmd: pnpm dev
cwd: ./api
worker:
cmd: pnpm worker
cwd: ./api
frontend:
cmd: pnpm dev
cwd: ./webContainers managed via Docker CLI.
docker:
redis:
image: redis:latest
ports:
- 6379:6379docker:
postgres:
image: postgres:15 # Required. Docker image
ports: # Port mappings (host:container)
- 5432:5432
env: # Env vars for container
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=dev
volumes: # Volume mounts
- postgres-data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
depends_on: [other] # Start dependencies first
profiles: [dev] # Profile filtering
healthcheck: 10 # Seconds to wait before considering "up"
# OR
healthcheck: http://localhost:5432 # URL to poll for readinessdocker:
postgres:
image: postgres:15
ports:
- 5432:5432
env:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- postgres-data:/var/lib/postgresql/datadocker:
mongodb:
image: mongo:7
ports:
- 27017:27017
volumes:
- mongodb-data:/data/dbdocker:
redis:
image: redis:7-alpine
ports:
- 6379:6379docker:
mysql:
image: mysql:8
ports:
- 3306:3306
env:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=myapp
volumes:
- mysql-data:/var/lib/mysqlZapper uses a whitelist approach: define where vars come from, then each service declares which ones it needs.
env_files: [.env] # Single file
env_files: [.env.base, .env] # Multiple files (later files override)You can define multiple env file sets and switch between them with
zap env <name>. The default set is optional; if omitted and no
environment is active, no env files are loaded.
env_files:
default: [.env.base, .env]
prod_dbs: [.env.base, .env.prod-dbs]Legacy array syntax is still supported:
env_files: [.env.base, .env]Split into two files:
.env.base— non-secrets (ports, URLs), committed to git.env— secrets (API keys, passwords), gitignored
env_files: [.env.base, .env]Each service only receives the vars it explicitly lists:
native:
backend:
cmd: pnpm dev
env:
- PORT
- DATABASE_URL
- JWT_SECRET
frontend:
cmd: pnpm dev
env:
- VITE_API_URLBackend sees PORT, DATABASE_URL, JWT_SECRET. Frontend only sees VITE_API_URL. No leakage.
Override or hardcode values inline:
native:
api:
cmd: pnpm dev
env:
- PORT=3000 # Hardcoded value
- DATABASE_URL # From env_files
- DEBUG=true # OverrideDefine port variable names in your config and assign random values with zap assign:
project: myapp
ports:
- FRONTEND_PORT
- BACKEND_PORT
- DB_PORT
env_files: [.env]
native:
frontend:
cmd: pnpm dev
env:
- FRONTEND_PORT
backend:
cmd: pnpm dev
env:
- BACKEND_PORT
- DB_PORTThen run:
zap assign # Assigns random ports to .zap/ports.jsonThe assigned ports have highest precedence - they override values from any .env files. This is useful for:
- Avoiding port conflicts when running multiple instances
- Dynamic port assignment in development
- Sharing configurations with different port needs
Interpolation works with assigned ports:
# .env
FRONTEND_PORT=3000
FRONTEND_URL=http://localhost:${FRONTEND_PORT}After zap assign:
# If FRONTEND_PORT was assigned 54321
FRONTEND_URL will be http://localhost:54321Docker services can use env vars too:
docker:
postgres:
image: postgres:15
env:
- POSTGRES_PASSWORD=dev # Hardcoded
- POSTGRES_DB # From env_fileszap env --service api # Show resolved env vars for a service
zap env api # Works if no environment set named 'api'Instances let you run multiple copies of the same project from Git worktrees without process/container name collisions.
zap isolate # Create random 6-char local instance ID in .zap/instance.json
zap isolate my-feature-123 # Provide an explicit instance ID
zap up # Start with instance-aware namesIf you are in a worktree without isolation, Zapper prints a warning for non-isolate commands and still runs commands. See Instances for full details.
One-off commands that can use your env vars and accept parameters.
tasks:
seed:
cmds:
- pnpm db:seedtasks:
seed:
desc: Seed the database # Description (shown in help)
cwd: ./backend # Working directory
env: # Env vars to pass
- DATABASE_URL
params: # Named parameters
- name: count
default: "10"
desc: Number of records
- name: env
required: true
desc: Target environment
cmds: # Commands to run (in order)
- pnpm db:migrate
- 'pnpm db:seed --count={{count}}'zap task seed
zap task lintTasks can accept named parameters and pass-through arguments.
Define parameters with defaults and validation:
tasks:
build:
desc: Build for target environment
params:
- name: target
default: development
desc: Build target
- name: minify
desc: Enable minification
cmds:
- 'echo "Building for {{target}}"'
- 'npm run build -- --env={{target}}'Run with parameters:
zap task build --target=production --minify=trueMark parameters as required (task fails if not provided):
tasks:
deploy:
params:
- name: env
required: true
desc: Deployment environment
cmds:
- 'deploy.sh {{env}}'zap task deploy --env=staging # Works
zap task deploy # Error: Required parameter 'env' not providedUse {{REST}} to forward extra CLI arguments:
tasks:
test:
desc: Run tests with optional args
cmds:
- 'pnpm vitest {{REST}}'Everything after -- is passed through:
zap task test -- --coverage src/
# Runs: pnpm vitest --coverage src/If your commands contain {{ and }}, use custom delimiters:
project: myapp
task_delimiters: ["<<", ">>"]
tasks:
build:
cmds:
- 'echo "Building <<target>>"'For tooling integration (VS Code extension), get parameter info as JSON:
zap task build --list-paramsOutput:
{
"name": "build",
"params": [
{ "name": "target", "default": "development", "required": false, "desc": "Build target" }
],
"acceptsRest": false
}tasks:
db:migrate:
desc: Run database migrations
env: [DATABASE_URL]
cmds:
- pnpm prisma migrate dev
db:seed:
desc: Seed the database
env: [DATABASE_URL]
cmds:
- pnpm prisma db seed
db:reset:
desc: Reset and reseed database
env: [DATABASE_URL]
cmds:
- pnpm prisma migrate reset --forcetasks:
lint:
cmds:
- pnpm eslint . --fix
- pnpm prettier --write .
typecheck:
cmds:
- pnpm tsc --noEmit
test:
env: [DATABASE_URL]
cmds:
- pnpm vitest run
checks:
desc: Run all checks before committing
cmds:
- pnpm eslint .
- pnpm tsc --noEmit
- pnpm vitest runControl startup order with depends_on.
docker:
postgres:
image: postgres:15
ports:
- 5432:5432
native:
api:
cmd: pnpm dev
depends_on: [postgres] # Postgres starts firstdocker:
postgres:
image: postgres:15
redis:
image: redis:7
native:
api:
cmd: pnpm dev
depends_on: [postgres, redis]
worker:
cmd: pnpm worker
depends_on: [api] # API (and its deps) start first
frontend:
cmd: pnpm dev
depends_on: [api]When you run zap up frontend, Zapper starts: postgres → redis → api → frontend.
Run different subsets of services.
native:
api:
cmd: pnpm dev
profiles: [dev, test]
api-prod:
cmd: pnpm start
profiles: [prod]
frontend:
cmd: pnpm dev
profiles: [dev]
docker:
postgres:
image: postgres:15
profiles: [dev, test]
postgres-test:
image: postgres:15
env:
- POSTGRES_DB=test
profiles: [test]zap up # Starts only services with no `profiles` field
zap profile dev # Enables 'dev' profile and starts matching services
zap restart # Restarts all services using active profile filtering
zap profile --disable # Disables active profileServices without a profiles field run regardless of profile state.
Services with a profiles field run only when an active profile matches.
Quick reference links for your project. These are for your own reference and can be displayed by tooling.
You can also set a top-level homepage URL as the default target for zap launch with no arguments.
homepage: http://localhost:3000links:
- name: API Docs
url: https://api.example.com/docs
- name: Staging
url: https://staging.example.com
- name: Figma
url: https://figma.com/file/abc123Link URLs support ${VAR} syntax to reference environment variables from your env_files:
env_files: [.env]
links:
- name: API
url: http://localhost:${API_PORT}
- name: Frontend
url: http://localhost:${FRONTEND_PORT}zap launch # Open homepage
zap launch "API Docs" # Open by link name (quote if spaces)
zap open # Alias for: zap launch
zap o "API Docs" # Short alias for: zap launch "API Docs"| Property | Required | Description |
|---|---|---|
name |
Yes | Display name (max 100 characters) |
url |
Yes | URL (supports ${VAR} interpolation) |
For multi-repo setups, Zapper can clone repositories.
project: myapp
git_method: ssh # ssh | http | cli
native:
api:
cmd: pnpm dev
cwd: ./api
repo: myorg/api-service
web:
cmd: pnpm dev
cwd: ./web
repo: myorg/web-app| Method | URL Format | Notes |
|---|---|---|
ssh |
git@github.com:myorg/repo.git |
Requires SSH key |
http |
https://github.com/myorg/repo.git |
May prompt for auth |
cli |
Uses gh repo clone |
Requires GitHub CLI |
zap clone # Clone all repos
zap clone api # Clone one repo
zap clone api web # Clone multiple reposRepos are cloned to the path specified in cwd.
A complete example for a typical full-stack app:
project: myapp
env_files: [.env.base, .env]
git_method: ssh
native:
api:
cmd: pnpm dev
cwd: ./api
repo: myorg/api
env:
- PORT
- DATABASE_URL
- REDIS_URL
- JWT_SECRET
depends_on: [postgres, redis]
worker:
cmd: pnpm worker
cwd: ./api
env:
- DATABASE_URL
- REDIS_URL
depends_on: [api]
frontend:
cmd: pnpm dev
cwd: ./web
repo: myorg/web
env:
- VITE_API_URL
depends_on: [api]
docker:
postgres:
image: postgres:15
ports:
- 5432:5432
env:
- POSTGRES_DB=myapp
- POSTGRES_PASSWORD=dev
volumes:
- postgres-data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- 6379:6379
tasks:
db:migrate:
desc: Run migrations
env: [DATABASE_URL]
cmds:
- pnpm --filter api prisma migrate dev
db:seed:
desc: Seed database
env: [DATABASE_URL]
params:
- name: count
default: "10"
desc: Number of seed records
cmds:
- 'pnpm --filter api prisma db seed --count={{count}}'
test:
desc: Run tests with optional args
env: [DATABASE_URL]
cmds:
- 'pnpm vitest {{REST}}'
deploy:
desc: Deploy to environment
params:
- name: env
required: true
desc: Target environment (staging, production)
cmds:
- 'deploy.sh {{env}}'
lint:
cmds:
- pnpm eslint . --fix
- pnpm tsc --noEmit
homepage: http://localhost:5173
links:
- name: API Docs
url: http://localhost:3000/docs
- name: Storybook
url: http://localhost:6006