Ephemeral services per workspace.

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.

two checkouts, two workspaces
~/projects/app
  ->  eph-a1b2c3d4-postgres  localhost:54321

~/projects/app-v2
  ->  eph-e5f6g7h8-postgres  localhost:54322
$ eph up
Services started:
  postgres -> localhost:54321

Run `eval "$(eph env)"` to set environment variables
macOS · Linux
curl -sSfL https://raw.githubusercontent.com/attunehq/doteph/main/scripts/install.sh | bash
Windows
irm https://raw.githubusercontent.com/attunehq/doteph/main/scripts/install.ps1 | iex

Local services get messy fast.

Working on multiple projects, or multiple checkouts of the same project, surfaces the same three problems every time.

01

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.

02

Data bleeds between environments

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.

03

Containers run all the time

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.

One file, three commands.

Every service your project needs lives in a single .eph file at the workspace root. The loop is the same for every project.

.ephini
[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}/myapp
  1. 1eph up

    Starts every service (as a container, a Compose project, or a plain process) and waits until each health check passes.

  2. 2eval "$(eph env)"

    Loads the resolved connection strings, with the real assigned ports filled in, into your shell.

  3. 3eph down

    Stops the containers. Data stays put for a fast restart; eph clean wipes it when you want a fresh start.

Everything a per-workspace dev stack needs.

No orchestration platform, no shared daemon config: one file per workspace, one binary.

Per-workspace isolation

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.

Automatic, loopback-bound ports

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.

Health-checked startup

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.

Four ways to define a service

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.

Lifecycle hooks

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.

Roles and tiers

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.

eph dev for your app

Foregrounds your run= app for a preview server, with --watch to restart on source changes and clean teardown of only what it started.

Runs natively everywhere

Linux, macOS, and Windows, with the same Docker-backed services on every platform and prebuilt binaries for each.

Checksum-verified updates

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.

Built for agents.

Coding agents work in the same workspaces you do. eph is written to be driven by one directly.

Skills, installed

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.

Machine-readable docs

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 dep

Full reference for agents and automation is in For Agents and Scripts, and at /llms.txt.