Managing multiple @imqueue projects locally: one isolated CLI home per fleet
Every imq invocation shares one ~/.imq — one config, one pid registry, one set of logs. Here's how to give each project its own isolated CLI environment with IMQ_CLI_HOME, plus recipes for direnv, shell wrappers, per-client tokens and disposable sandboxes.
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_HOMEto give each project its own CLI home:~/.imqbecomes$IMQ_CLI_HOME/.imq, and everything moves with it —config.json, the templates cache and thevar/pid/log store used byimq ctlandimq 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):
config.json— the global configuration: providers, namespaces, auth tokens. Written with0600permissions because it may hold secrets.templates/— a cached git clone of the templates repository, pinned to thetemplatesRefbranch from your config.custom-templates/— cached custom templates.var/— fleet runtime state: one<service>.logper service and a single.pidsfile ofservice:pidrecords.
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 withimq config setor 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-serviceoverwrite each other's pid records and wipe each other's logs. - The stop fallback. Run
imq ctl stopfrom 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+checkouton use. Two projects pinning differenttemplatesRefbranches 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 ~:
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 firstimq config initorimq config setcreates the directory chain itself. - It's the complete list. Per-service settings live in each repo's
committed
.imqrc.json, 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:
# ~/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:
# ~/.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 "$@"
}
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:
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?
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:
# 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
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
imqstarts. The CLI resolves its home once at startup; exportingIMQ_CLI_HOMEhas no effect on an already-running command. ~/.sshstays put. SSH keys are deliberately not relocated — a sandboxed environment still uses your real keys for git transport. Override separately withIMQ_SSH_DIRif you need to.- Install shell completions from your normal environment.
imq completions onresolves~/.zshrc/~/.bashrcagainst the CLI home, so withIMQ_CLI_HOMEset it would write the completion block into$IMQ_CLI_HOME/.zshrcinstead 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/clishared by every environment — which is why the recipes above bake inIMQ_NO_UPDATE_CHECK=1.
FAQ
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, and fleet operations are covered in 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/. New to @imqueue? Start with Get Started — your first service is about five minutes away.
Building on @imqueue? The open-source packages live on GitHub and the docs at imqueue.org. Shipping inside a closed-source product? See commercial licensing & support.