Configuration

.ecluse.toml lives at repo root and is written by ecluse init. All fields are optional except mode.

mode = "hybrid"
max_slots = 8
prefix = "ecluse"
worktree_dir = ".ecluse/worktrees"

# Port collision handling (both optional)
# strict_port = false        # default: search for a free port on collision
# port_search_range = 10     # how many alternatives to try (bump by max_slots each time)

# One [[services]] block per service. port = base_port + slot.
# Native services run on the host; docker services run in containers.
# The first native entry also sets the PORT alias for framework compatibility.
# Add command = "..." to have ecluse spawn the process on ecluse up.

[[services]]
name = "api"
base_port = 3000             # slot 1 → ECLUSE_API_PORT=3001 + PORT, slot 2 → 3002
command = "npm run dev"      # optional — ecluse spawns this on ecluse up
# port_env = "DJANGO_PORT"  # optional — also set DJANGO_PORT to the allocated port
# port_env = ["DJANGO_PORT", "APP_PORT"]  # or multiple aliases

[[services]]
name = "postgres"
run = "docker"
base_port = 5432             # slot 1 → ECLUSE_POSTGRES_PORT=5433, slot 2 → 5434

# Optional: lifecycle hooks (see Hooks page for full details)
[hooks]
post_up  = "npx prisma migrate deploy"
pre_down = "npx prisma migrate reset --force"

Fields

FieldTypeDefaultDescription
modestringcontainer, host, or hybrid
max_slotsinteger8Maximum parallel sessions
prefixstring"ecluse"Prefix for compose project names and volume names
worktree_dirstring".ecluse/worktrees"Directory for git worktrees
strict_portboolfalseFail immediately on port collision instead of searching
port_search_rangeinteger10How many alternatives to try on collision
app_labelstring"ecluse.role"Docker Compose label key used to identify app vs data services in hybrid mode
app_label_valuestring"app"Value of app_label that marks a service as the app (not a data service)

[[services]]

Each [[services]] block defines one service. Each gets a stable, collision-free port per slot (base_port + slot).

FieldTypeDescription
namestringService name — becomes ECLUSE_<NAME>_PORT
base_portintegerPort formula: base_port + slot
runstring"docker" to run in a container; omit for native
commandstringShell command ecluse spawns on ecluse up (native services only; managed by your global process_manager setting)
port_envstring or arrayExtra env var names to set to this service's allocated port — accepts a single string or an array

The first native (non-docker) service entry also sets PORT for framework compatibility.

Omit [[services]] entirely for single-service projects — ecluse falls back to PORT = 3000 + slot.

Global config (~/.config/ecluse/config.toml)

Controls how ecluse spawns native service processes. Written by ecluse init based on what's installed.

process_manager = "tmux"   # "tmux" | "nohup" | "none"
ValueBehaviour
tmuxCreates a detached tmux session ecluse-<slug> with one window per service. ecluse shell <slug> attaches to it.
nohupSpawns each service as a background process. Logs at .ecluse/logs/<slug>/, PIDs at .ecluse/pids/<slug>/.
noneSpawns nothing — current behaviour before this was added.

ecluse init sets this automatically: tmux if available, otherwise nohup. Change it at any time — the setting is per-machine, not per-repo.

Run ecluse validate to check the configured binary is installed.

[hooks]

FieldWhen it runsEnv vars
pre_upBefore any infrastructure is createdNone (runs from repo root)
post_upAfter all services are up and .env.ecluse is writtenAll ECLUSE_* + PORT
pre_downBefore services are stoppedAll ECLUSE_* + PORT
post_downAfter all services are stopped and worktree is removedAll ECLUSE_* + PORT

Use post_up for migrations and seeding, pre_down for teardown that needs a live database. See the Hooks page for full details and examples.