Configuration¶
Rift uses Mountebank-compatible JSON configuration with optional _rift extensions for advanced features.
Mountebank Format¶
Use the standard Mountebank JSON format for creating imposters:
{
"imposters": [
{
"port": 4545,
"protocol": "http",
"stubs": [
{
"predicates": [{ "equals": { "path": "/api/users" } }],
"responses": [{ "is": { "statusCode": 200, "body": "[]" } }]
}
]
}
]
}
Load at startup:
docker run -v $(pwd)/imposters.json:/imposters.json \
zainalpour/rift-proxy:latest --configfile /imposters.json
Top-level keys¶
| Key | Purpose |
|---|---|
imposters |
The imposters to create — the Mountebank format above. |
intercept |
Optional, Rift extension. Declares the HTTPS intercept listener and its rules, so a container needs no post-boot admin call to install them. Its keys are host (default 127.0.0.1), port (default 0, which lets the OS pick), the CA pair (caCertPath/caKeyPath, or caCertPem/caKeyPem, or caCertPemEnv/caKeyPemEnv naming two environment variables), rules, and auth; any other key is a startup error. With no CA pair, Rift generates a CA at start. returnCaKey: true is refused too: a config file has no response to return a generated key in, so use POST /intercept for that. |
routes |
Optional, Rift extension. The front door route table, as {"routes": [ ... ]}. Validated at load, so a table that cannot route is a startup error. It only takes effect with --front-door. |
intercept.auth (issue #878) is {"username": "…", "password": "…"} and requires
Proxy-Authorization: Basic … on every CONNECT to the listener. Omit it and the proxy is open —
see Authenticating the proxy
for why that matters on a shared host. A blank username or password is a startup error, not a
disabled gate. Certificate and CA handling is covered in TLS/HTTPS.
caCertPemEnv and caKeyPemEnv hold the names of two environment variables, not the PEM text. Rift
reads the variables when the listener starts. Use them where a secret store delivers secrets only as
environment variables, as ECS/Fargate does. If a named variable is unset, the start fails and the
error names it.
POST /admin/reload re-applies the file's intercept.rules, so you can change a rule by editing the
file. The listener itself stays as booted: a change to host, port, auth or the CA source needs a
restart, and the reload reply carries a warning that says so. If one edit changes the listener and
the rules together, the rules are not re-applied either; restart to apply both. See
Hot reload for the reply format.
Any other top-level key is ignored.
Document shapes and formats¶
A document is read as JSON when it starts with { or [, and as YAML otherwise. The accepted
shapes are:
| Shape | JSON | YAML |
|---|---|---|
{"imposters": [...]} wrapper (may carry intercept / routes) |
yes | no |
A single imposter object ({"port": 4545, ...}) |
yes | no |
| A bare array (sequence) of imposters | yes | yes — the only YAML shape |
So a YAML config file is always a sequence at the root, even for one imposter. Only the wrapper has
somewhere to put an intercept or routes block; declaring one in a single-imposter JSON document
is a startup error naming the fix rather than a block that silently does nothing.
Before parsing, a --configfile document is run through the EJS subset Mountebank config files use.
Mountebank renders the file with EJS 2.7.4; Rift evaluates these forms of it, with ' or " quotes:
| Tag | Result |
|---|---|
<% include file %>, <%- include('file') %> |
The file, itself rendered, so includes nest. The path is relative to the file the tag is in; .ejs is appended to a path with no extension. |
<%- stringify('file') %>, <%- stringify(filename, 'file') %> |
The file, rendered, trimmed and escaped for the inside of a JSON string (below). The path is relative to the root config file, however deep the tag is, as in Mountebank; an absolute path is read as written. Stringifies nest. |
<%- stringify(filename, 'file', {key: 'value'}) %> |
The same, with data.key set in that file: <%- data.key %> inserts the value, <%= data.key %> HTML-escapes it, and a missing key renders empty. Values are quoted strings, plain numbers, true or false. |
<%- inject(...) %> |
Mountebank's older name for stringify, in each form above. |
<%= process.env.VAR %>, <%= process.env.VAR \|\| 'default' %> |
The variable's value, or the default, HTML-escaped as EJS's <%= escapes it (& becomes &, and so on for < > " '). |
<%- process.env.VAR %>, <%- process.env.VAR \|\| 'default' %> |
The value as it is. Use it for a value that may hold &, <, > or quotes. |
A tag closed with -%> drops the line break right after it, as in EJS. An include in a stringified
file resolves against the root config file's directory, because Mountebank renders a stringified
file with the root as its EJS filename. A file that includes or stringifies one that is already
being rendered (itself, or a file that led to it) is an error naming the chain of files, and so is
nesting deeper than 16 files; Mountebank would run out of stack instead.
Any other <% %> tag is refused with an error rather than stripped, including <%= around an
include or stringify (EJS would HTML-escape the file) and stringify('file', {...}), which
Mountebank's own renderer fails on (write stringify(filename, 'file', {...})); pass --no-parse
for a document that holds a literal <%. A referenced variable that is unset renders empty and logs
a warning naming it. See
CLI Reference → Imposter Sources for what a
document fetched over https: may not do, and
Data directory for the stricter
rules on --datadir files.
<%- stringify('file') %> yields the file's contents, trimmed and escaped for the inside of a JSON
string — you write the quotes, as in Mountebank:
The result is a string body, served byte-for-byte as the file has it (keys unsorted — see
object vs string bodies). Written without the quotes,
"body": <%- stringify('fixtures/datafile.json') %>, the document does not parse; the load error
then names the tag, where it is, and the quoted form, and says its line and column are in the
rendered document. Run with --loglevel debug to see that rendered document — it includes every
stringified file, so a stringified private key appears in that log. The tag can also sit
inside a longer string: "x <%- stringify('f') %> y".
An imposter with no port, or "port": 0, gets a free port assigned; imposters that declare a port
are always created first.
Or create dynamically via API:
curl -X POST http://localhost:2525/imposters \
-H "Content-Type: application/json" \
-d @imposter.json
Full Mountebank Format Reference
Rift Extensions (_rift namespace)¶
Extend Mountebank configurations with advanced chaos engineering features:
{
"port": 4545,
"protocol": "http",
"_rift": {
"flowState": {
"ttlSeconds": 300
}
},
"stubs": [{
"predicates": [{ "equals": { "path": "/api/users" } }],
"responses": [{
"is": { "statusCode": 200, "body": "[]" },
"_rift": {
"fault": {
"latency": {
"probability": 0.3,
"minMs": 100,
"maxMs": 500
}
}
}
}]
}]
}
Available _rift features:
- Flow State: Stateful testing with the in-memory backend
- Fault Injection: Probabilistic latency, error, and TCP faults
- Scripting:
respond(ctx)scripts in Rhai or JavaScript - Templating and state operations:
_rift.templated(function-grammar response templates) and_rift.stateOps(declarative flow-state writes)
Full Rift Extensions Reference
Environment Variables¶
Configure Rift behavior via environment variables:
| Variable | Description | Default |
|---|---|---|
MB_PORT |
Admin API port | 2525 |
MB_HOST |
Admin API bind IP address (IPv4, or IPv6 ::1 / [::1]) |
every interface, IPv6 and IPv4 ([::] dual-stack; 0.0.0.0 on a host without IPv6) |
MB_CONFIGFILE |
Imposter config file | |
MB_DATADIR |
Persistent storage directory | |
MB_ALLOW_INJECTION |
Enable JavaScript injection | false |
MB_LOCAL_ONLY |
Localhost only: the admin API, /metrics and --intercept-port listen on 127.0.0.1 and ::1, and they, every imposter port and the front door refuse non-loopback clients |
false |
MB_LOGLEVEL |
Log level: trace, debug, info, warn, error |
info |
MB_APIKEY |
Admin API key | |
RIFT_METRICS_PORT |
Prometheus metrics port | 9090 |
RUST_LOG |
Full tracing filter; overrides MB_LOGLEVEL when set. A value that does not parse is refused at startup |
unset |
This is the common subset; the full list — TLS, intercept, runtime and socket-tuning variables — is in the CLI Reference.
docker run -e MB_PORT=2525 -e MB_ALLOW_INJECTION=true \
-e MB_LOGLEVEL=debug zainalpour/rift-proxy:latest
Command Line Options¶
rift [OPTIONS]
Options:
--port <PORT> Admin API port [default: 2525]
--host <HOST> Admin API bind IP address (IPv4, or IPv6 `::1` / `[::1]`) [default: every interface, IPv6 and IPv4]
--configfile <FILE> Load imposters from a JSON or YAML file
--imposters <URI,...> Load imposters from file: paths or https:// URLs
--datadir <DIR> Persist admin-API imposters as <DIR>/<port>.json
--allow-injection Enable JavaScript injection and scripts
--local-only Bind the admin API and /metrics to loopback (`127.0.0.1` and `::1`); they, imposters and the front door refuse non-loopback clients
--api-key <TOKEN> Require this token on every admin API request
--rcfile <FILE> Read defaults from a Mountebank-style JSON rcfile
--loglevel <LEVEL> trace, debug, info, warn, error [default: info]
--metrics-port <PORT> Prometheus metrics port [default: 9090]
--no-parse Load --configfile verbatim, without EJS rendering
-h, --help Print help
-V, --version Print version
This is an excerpt. Subcommands (save, replay, stop, restart, script, healthcheck),
TLS and intercept flags, and the --rcfile key list are in the full reference.
Use Cases¶
Standard API Mocking¶
Use Mountebank JSON format for:
- Migrating from Mountebank
- Creating API mocks for integration tests
- Working with existing Mountebank tooling
- Service virtualization
Advanced Chaos Engineering¶
Add _rift extensions for:
- Probabilistic fault injection
- Stateful testing scenarios
- Complex conditional logic with scripting