One command to rule them all.
Define your entire local dev environment in a single file. Start everything with lokl up.
New developer joins your team. Instead of spending a day setting up their environment:
lokl upThat's it. Frontend, backend, databases, HTTPS routing β all running.
π Single config file β Define all services in lokl.yaml
π Automatic HTTPS β Generated certificates for custom domains (app.myproject.dev)
π Process management β Health checks, dependency ordering, auto-restart
π³ Docker support β Run databases and caches as containers alongside your services
π Env files & interpolation β Load secrets from .env files, reference with ${VAR}
π₯οΈ Interactive TUI β Start/stop services, view logs, toggle proxy
π Project detection β lokl init scans your project and generates config
Homebrew (macOS/Linux):
brew install shahin-bayat/tap/loklOne-liner:
curl -fsSL https://raw.githubusercontent.com/shahin-bayat/lokl/main/install.sh | bashGo install:
go install github.com/shahin-bayat/lokl/cmd/lokl@latest# Initialize config from your project
lokl init
# Start your environment
lokl upname: my-project
version: "1"
proxy:
domain: myproject.dev
services:
frontend:
command: pnpm dev
path: apps/frontend
port: 5173
subdomain: app
api:
command: pnpm dev
path: apps/api
port: 3000
subdomain: api
env:
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/myproject
depends_on:
- db
db:
image: postgres:16
ports:
- "5432:5432"
port: 5432
env_file:
- .env
env:
POSTGRES_DB: myproject
health:
command: "pg_isready -U myproject" # exec-based check (no HTTP endpoint)
interval: 2s
retries: 10services:
web:
command: php artisan serve
subdomain:
- sellify.shop
- "*.sellify.shop"
port: 8000sudo lokl dns setup writes /etc/hosts and /etc/resolver/sellify.shop; lokl up then runs an in-process DNS listener so every subdomain resolves locally. Cert SANs cover both apex and wildcard.
macOS only for now; Linux support (systemd-resolved) is coming in a follow-up release.
Some containers expose two HTTP servers on different ports β MinIO's S3 API on 9000 and its console on 9001, Postgres + a pgAdmin sidecar, Jaeger UI on its own port. Declare a second service entry with proxy_only: true to route another subdomain to the second port without starting a second container:
services:
minio:
image: minio/minio:latest
ports: ["9000:9000", "9001:9001"]
port: 9000
subdomain: s3
minio-console:
proxy_only: true
subdomain: console # β https://console.<domain> β 127.0.0.1:9001
port: 9001
depends_on: [minio]A proxy_only service doesn't start a process or container β it only forwards. Same pattern works for host-native processes (e.g. a Go server you run manually with go run) that you want reachable via HTTPS.
Containers in the same lokl project share a bridge network (lokl-{name}).
They can reach each other by service name β no need to expose ports between containers:
services:
api:
image: myapp:latest
env:
DB_HOST: db # reaches the "db" container directly
REDIS_HOST: cache # reaches the "cache" container directly
db:
image: postgres:16
cache:
image: redis:7services:
web:
image: node:20
command: "npm run dev" # overrides image CMD, keeps ENTRYPOINTThen:
https://app.myproject.devβ frontend (port 5173)https://api.myproject.devβ api (port 3000)
- macOS or Linux
- Go 1.25+ (for installation from source)
- Docker (for container-based services)