Ports collide across checkouts
Two clones of the same project both want Postgres on 5432. Whichever one grabbed the port first wins; the other fails to bind, or worse, talks to the wrong database.
Like .env files, but for services.
Describe the Postgres, Redis, or MinIO your project needs in a single.eph file. eph up starts them isolated per workspace, with host ports assigned automatically, so two checkouts of the same project run on different ports, keep separate data, and don't stay running once you're done with them.
~/projects/app
-> eph-a1b2c3d4-postgres localhost:54321
~/projects/app-v2
-> eph-e5f6g7h8-postgres localhost:54322$ eph upServices started:
postgres -> localhost:54321
Run `eval "$(eph env)"` to set environment variablescurl -sSfL https://raw.githubusercontent.com/attunehq/doteph/main/scripts/install.sh | bashirm https://raw.githubusercontent.com/attunehq/doteph/main/scripts/install.ps1 | iexWorking on multiple projects, or multiple checkouts of the same project, surfaces the same three problems every time.
Two clones of the same project both want Postgres on 5432. Whichever one grabbed the port first wins; the other fails to bind, or worse, talks to the wrong database.
One shared Postgres container means one shared database. A migration you're testing in one checkout, or a seed script gone wrong, shows up in every other checkout using the same container.
Services started by hand tend to stay started, whether or not you're using them: consuming memory and CPU, drifting from what a fresh checkout would actually need.
eph gives every workspace its own isolated services, started on demand, with host ports assigned automatically. No port to agree on and no container to share; nothing runs when you aren't working.
Every service your project needs lives in a single .eph file at the workspace root. The loop is the same for every project.
[postgres]
image=postgres:16-alpine
port=5432
env.POSTGRES_USER=dev
env.POSTGRES_PASSWORD=dev
healthcheck=pg_isready -U dev
[web]
run=npm run dev
port=auto
env.PORT=${web.port}
DATABASE_URL=postgres://dev:dev@localhost:${postgres.port}/myappeph upStarts every service (as a container, a Compose project, or a plain process) and waits until each health check passes.
eval "$(eph env)"Loads the resolved connection strings, with the real assigned ports filled in, into your shell.
eph downStops the containers. Data stays put for a fast restart; eph clean wipes it when you want a fresh start.
No orchestration platform, no shared daemon config: one file per workspace, one binary.
Every resource eph creates is namespaced by a hash of the workspace's canonical path: eph-a1b2c3d4-postgres. Two checkouts never see each other's containers, volumes, or data.
You never pick a host port. eph asks Docker for a free one and binds it to 127.0.0.1, so nothing collides and nothing is exposed to the local network.
eph up waits for each service's health check to pass before returning, so the connection strings eph env prints are already good to use.
A Docker image, a Dockerfile to build, a Compose file to delegate to, or a plain run= shell command for your own app, all in the same file.
pre-start, post-start, pre-stop, and post-stop run at each phase of a service's life, with eph's resolved environment injected, for migrations, seeding, and cleanup.
Tag services role=dep or role=app and eph up --role dep brings up just the backing services, ready for a prewarmed, known-good tier your app can attach to later.
Foregrounds your run= app for a preview server, with --watch to restart on source changes and clean teardown of only what it started.
Linux, macOS, and Windows, with the same Docker-backed services on every platform and prebuilt binaries for each.
eph update downloads the latest release, verifies its SHA-256 checksum, and swaps the binary in place. eph update --check just reports whether one is available.
Coding agents work in the same workspaces you do. eph is written to be driven by one directly.
eph skills install drops a using-eph skill into .claude/skills and .agents/skills, so an agent working in the repo discovers eph on its own.
This site serves llms.txt and every guide page as raw Markdown at its .md URL, for an agent to fetch directly instead of scraping HTML.
# SessionStart hook: prewarm deps, inject their env.
$ eph up --role dep
postgres -> localhost:54321
$ eph env >> "$CLAUDE_ENV_FILE"
# SessionEnd hook (optional): stop the tier on session end.
$ eph down --role depFull reference for agents and automation is in For Agents and Scripts, and at /llms.txt.