# One isolated imq CLI home per @imqueue project

Source: https://imqueue.org/blog/isolated-imq-cli-environments/
Published: 2026-07-23
Author: Andrii Glushko — Maintainer, @imqueue (https://github.com/creomobile)

**Managing multiple @imqueue projects on one machine** works fine — right up
until it doesn't. The `imq` CLI keeps everything it owns under a single home
directory, `~/.imq`: your global configuration, the templates cache, and the
runtime state behind `imq ctl` and `imq log`. With one project you'll never
notice. With two — a work fleet and a side project, or two clients with
different VCS orgs and tokens — that shared home becomes a source of quiet
collisions.

> **TL;DR** — set `IMQ_CLI_HOME` to give each project its own CLI home:
> `~/.imq` becomes `$IMQ_CLI_HOME/.imq`, and *everything* moves with it —
> `config.json`, the templates cache and the `var/` pid/log store used by
> `imq ctl` and `imq log`. Wire it up once with direnv or a two-line shell
> function, and every fleet gets its own config, tokens, template branch,
> logs and pid registry.

## Where one shared ~/.imq bites

The CLI creates exactly four things under its home
([full table](https://imqueue.org/cli/installation/#files-the-cli-creates)):

- **`config.json`** — the global configuration: providers, namespaces, auth
  tokens. Written with `0600` permissions because it may hold secrets.
- **`templates/`** — a cached git clone of the templates repository, pinned to
  the `templatesRef` branch from your config.
- **`custom-templates/`** — cached custom templates.
- **`var/`** — fleet runtime state: one `<service>.log` per service and a
  single `.pids` file of `service:pid` records.

Share that home across two projects and here's what actually goes wrong:

- **One global config.** `vcs.namespace`, provider choices and auth tokens are
  machine-wide. Working for two clients means either flipping values back and
  forth with `imq config set` or leaking one client's defaults into the
  other's scaffolds.
- **One pid registry, logs keyed by service name.** Logs are named after the
  service directory's basename and truncated on every start. Two fleets that
  both contain a `user-service` overwrite each other's pid records and wipe
  each other's logs.
- **The stop fallback.** Run `imq ctl stop` from a directory where no services
  are discoverable — and without `-s` — and it falls back to stopping *every*
  pid tracked in that home. With a shared home, that includes the other
  project's fleet.
- **One templates working copy.** The cache is a single git clone that gets
  `fetch` + `checkout` on use. Two projects pinning different `templatesRef`
  branches will re-checkout each other's ref on every scaffold.

## One variable moves everything

`IMQ_CLI_HOME` replaces the *base* the CLI treats as your home directory —
it doesn't replace `~/.imq` itself, it replaces the `~`:

~~~bash
export IMQ_CLI_HOME=/some/where   # ~/.imq becomes /some/where/.imq
~~~

Everything the CLI touches flows through that one resolution — config,
templates, custom templates, pids and logs. Nothing in the CLI hardcodes your
real home directory, and this isn't an accident of implementation: the CLI's
own test suite runs every end-to-end test inside a fresh `IMQ_CLI_HOME`, so
the isolation path is exercised on every release.

Two properties make it pleasant to use:

- **No setup ceremony.** A fresh home doesn't need `mkdir` — the first
  `imq config init` or `imq config set` creates the directory chain itself.
- **It's the complete list.** Per-service settings live in each repo's
  committed [`.imqrc.json`](https://imqueue.org/cli/configuration/#per-service-overrides-imqrcjson),
  so they travel with the project no matter which home is active. The usual
  precedence chain — CLI flag → `.imqrc.json` → global config → prompt →
  default — still applies; only the "global config" layer moves.

That split is the mental model: **things the project owns stay in the repo
(`.imqrc.json`); things you or a client own move with the home (tokens,
namespaces, template branches, runtime state).**

## The recipes

### 1. A home per project, automatically, with direnv

Drop an `.envrc` at the root of each project directory:

~~~bash
# ~/work/acme/.envrc
export IMQ_CLI_HOME="$PWD/.imq-home"
export IMQ_NO_UPDATE_CHECK=1
~~~

Run `direnv allow` once. Every shell that enters `~/work/acme` — including
your editor's terminal — now talks to that project's private CLI home, and
leaves it behind on `cd ..`. Add `.imq-home/` to the project's `.gitignore`:
its `config.json` may hold tokens.

### 2. Named fleets with a shell function

If you prefer explicit switching over directory magic:

~~~bash
# ~/.zshrc or ~/.bashrc
imqf() {   # usage: imqf <fleet> <imq arguments…>
  local fleet="$1"; shift
  IMQ_CLI_HOME="$HOME/.imq-fleets/$fleet" IMQ_NO_UPDATE_CHECK=1 imq "$@"
}
~~~

~~~bash
imqf acme   config init
imqf acme   ctl start -p ~/work/acme/services -c
imqf globex ctl status -p ~/work/globex/services
~~~

The fleet name is right there in your shell history, and tab completion for
`imq` keeps working since the function just prefixes the environment.

### 3. Per-fleet configuration: clients, tokens, template branches

Each home carries its own `config.json`, so per-client setups stop being a
juggling act:

~~~bash
imqf acme   config set vcs.provider github
imqf acme   config set vcs.namespace acme-org
imqf acme   config set vcs.auth.token "$ACME_GITHUB_TOKEN"
imqf acme   config set templatesRef master

imqf globex config set vcs.provider gitlab
imqf globex config set vcs.namespace globex
imqf globex config set templatesRef v4-stable
~~~

Every `imq service create` under the acme home now scaffolds into the right
org with the right token — and because each home has its **own templates
clone**, the two `templatesRef` branches no longer fight over one working
copy. Point a fleet at a forked templates repo entirely with
`IMQ_TEMPLATES_REPO`.

### 4. Disposable sandboxes (and CI)

Want to try a risky config change, test a custom template, or demo the CLI
without touching your real setup?

~~~bash
IMQ_CLI_HOME="$(mktemp -d)" imq config init
~~~

Everything lands in the temp directory; delete it and the experiment never
happened. The same trick is the clean way to run the CLI in CI — give the job
a workspace-local home and set `IMQ_NO_UPDATE_CHECK=1` (the self-update check
also auto-skips when there's no TTY).

### 5. Two fleets running side by side

With separate homes, parallel fleets stop being scary:

~~~bash
# terminal 1 — the acme fleet
imqf acme ctl start -p ~/work/acme/services -c
imqf acme log

# terminal 2 — the globex fleet
imqf globex ctl start -p ~/work/globex/services -c
imqf globex log
~~~

Both fleets can contain a `user-service`; their logs and pid records live in
different `var/` directories, so nothing is overwritten or truncated.
`imqf acme ctl stop` can only ever touch acme's pids — the stop fallback is
now scoped to one project, which turns it from a hazard into a feature:
"stop everything in *this* fleet".

One boundary to respect: `IMQ_CLI_HOME` isolates the *CLI's* state, not your
infrastructure. If both fleets connect to the same Redis and share a service
name, those workers join the same queue — that's @imqueue's
[load balancing](https://imqueue.org/blog/load-balancing-microservices-without-a-load-balancer/)
doing its job across projects, which is not what you want here. Give each
fleet its own Redis port or database number.

## Gotchas worth knowing

- **Set it before `imq` starts.** The CLI resolves its home once at startup;
  exporting `IMQ_CLI_HOME` has no effect on an already-running command.
- **`~/.ssh` stays put.** SSH keys are deliberately *not* relocated — a
  sandboxed environment still uses your real keys for git transport. Override
  separately with `IMQ_SSH_DIR` if you need to.
- **Install shell completions from your normal environment.** `imq
  completions on` resolves `~/.zshrc` / `~/.bashrc` against the CLI home, so
  with `IMQ_CLI_HOME` set it would write the completion block into
  `$IMQ_CLI_HOME/.zshrc` instead of your real rc file. Run it once without
  the variable.
- **The self-update check is global anyway.** There's one `npm i -g
  @imqueue/cli` shared by every environment — which is why the recipes above
  bake in `IMQ_NO_UPDATE_CHECK=1`.

## Frequently asked questions about isolated imq CLI homes

### Do I need IMQ_CLI_HOME if I only have one project?
No. The defaults are exactly right for a single project — reach for isolation when the second project appears, or when you want a scratch environment.

### Does IMQ_CLI_HOME change how my services run?
No. Only the CLI reads it. Your services take their own configuration and environment as always — what moves is the CLI's bookkeeping *about* them: config, templates, logs and pid records.

### Where should per-client tokens live?
In that client's own home — each `config.json` is written `0600` in its own directory. For one-off runs you can still pass `-T <token>` on the command line instead. Never commit an `.imq-home` directory.

### Can two fleets use different template versions?
Yes — that's one of the main wins. Each home keeps its own clone of the templates repository, so `templatesRef` (and even `IMQ_TEMPLATES_REPO`) can differ per fleet without interference.

---

The full environment-variable table lives in the CLI User Guide's
[configuration reference](https://imqueue.org/cli/configuration/#environment-variable-reference),
and fleet operations are covered in
[Managing Local Services](https://imqueue.org/cli/managing-local-services/). If your AI assistant
manages your fleets for you, point it at the machine-readable version of these
recipes: [/agents/isolated-imq-environments/](https://imqueue.org/agents/isolated-imq-environments/).
New to @imqueue? Start with [Get Started](https://imqueue.org/get-started/) — your first service
is about five minutes away.

