Every parameter¶
Every field a configuration document may carry, with its type, its bounds and an example. Generated from the schema, which is generated from the Rust types that enforce the rules -- so a bound stated here is the bound the server applies.
The dashboard links here: the (i) beside a field opens that field's section.
For how the pieces fit together, read Configuration reference; for how to use them, the guides under Usage.
admin¶
The admin API's listener, its tokens, and who may do what.
| Type | object |
| Required | yes |
| Rules | no unknown keys |
admin:
host: 127.0.0.1
port: 8080
upload:
limit: 1048576
admin.access¶
Who may perform each admin action. Every action defaults to the
admin group, reads included.
| Type | object |
| Required | no |
| Rules | no unknown keys |
admin.access.create¶
Add a proxy. Refused for public by rule V34.
| Type | string or list of string |
| Required | no |
admin:
access:
create: public
admin.access.delete¶
Remove a proxy. Refused for public by rule V34.
| Type | string or list of string |
| Required | no |
admin:
access:
delete: public
admin.access.list¶
List the proxies. A listing exposes upstream URLs and injected headers.
| Type | string or list of string |
| Required | no |
admin:
access:
list: public
admin.access.read¶
Read one proxy document, credentials in its url included.
| Type | string or list of string |
| Required | no |
admin:
access:
read: public
admin.access.update¶
Replace a proxy. Refused for public by rule V34.
| Type | string or list of string |
| Required | no |
admin:
access:
update: public
admin.access.upload¶
Upload or delete a template file. Refused for public by rule V34.
| Type | string or list of string |
| Required | no |
admin:
access:
upload: public
admin.auth¶
Which header carries the bearer token.
| Type | object |
| Required | no |
| Rules | no unknown keys |
admin.auth.header¶
The header a caller presents its token in, as Bearer <token>.
Defaults to X-Proxy-Authorization.
| Type | string |
| Required | no |
admin:
auth:
header: X-Request-Id
admin.dashboard¶
Serve the browser dashboard from the admin listener's root.
On by default. Off means /, /static/* and /robots.txt are not
routed at all and answer 404 like any other unknown path; the JSON API is
untouched either way.
An Option and skipped when absent, for the reason given on groups:
adding it must not change the canonical YAML of configurations written
before it existed. Read it through
[AdminConfig::is_dashboard_enabled].
| Type | boolean |
| Required | no |
admin:
dashboard: true
admin.enable¶
Whether to run the admin listener at all.
Defaults to on. Off means the port is never bound and no admin task
starts; the proxy and the control socket are untouched, so
doppel config reload still works and is then the only way in.
The validation rules do not consult this. A configuration that is only safe because nothing serves it is a trap set for whoever turns the listener on later, and they will not re-read the rules first.
| Type | boolean |
| Required | no |
admin:
enable: true
admin.groups¶
Which names access may reference, here and in a proxy's overrides.
["*"], the default, allows any. A concrete list allows exactly those:
["admin", "ci"] permits admin and ci and refuses user.
[] names nobody, which leaves no action anything to reference -- so it
means the same thing as public: true, and is treated as it. It used to
mean a configuration with no legal value for create, update, delete
or upload: V36 refused their admin default and V34 refused public,
so the document could not be written at all.
public and admin are never governed by this. public is the absence
of a subject rather than a name; admin is the fallback every action
already has, and a list that revoked it would produce the same
unsatisfiable state as [] once did.
Checked by rule V36, not by this type: it compares one field against another, which is what is left for the rule set once the types have taken everything they can decide alone.
An Option rather than a Vec defaulting to ["*"], and skipped when
absent, so that adding this field did not change the canonical YAML of
every configuration written before it existed. The revision is derived
from that YAML, so a materialised default would have made every stored
configuration fail its own revision check on the first load after the
upgrade. Read it through [AdminConfig::allowed_groups].
| Type | array |
| Required | no |
admin:
groups:
- "*"
admin.host¶
An IP address, not a hostname: a name would have to be resolved,
and which address it resolves to is not the configuration's to
decide. utoipa has no schema for IpAddr, so it is described
here as the string it is written as.
| Type | string |
| Required | yes |
admin:
host: 127.0.0.1
admin.port¶
The TCP port the admin API listens on. Must differ from
server.port.
| Type | integer |
| Required | yes |
| Rules | 1 to 65535 |
admin:
port: 8080
admin.public¶
Serve the whole admin API unauthenticated.
false by default. true makes every action public and leaves no name
to reference, so groups is effectively empty and access effectively
all-public -- whatever either of them says. Anything they did say is
reported as a startup advisory rather than refused, so a configuration
being made temporarily public does not have to be gutted first and
rebuilt afterwards.
This overrides rule V34, which otherwise refuses a public write action.
V34 exists so that an unauthenticated writable proxy set cannot happen by
omission; a field named public set to true is not an omission. Think
of it as the flag V34 was holding the line for.
An Option and skipped when absent, for the reason given on groups:
adding it must not change the canonical YAML of configurations written
before it existed. Read it through [AdminConfig::is_public].
| Type | boolean |
| Required | no |
admin:
public: true
admin.title¶
The heading the dashboard shows, and the browser tab's title.
Defaults to Doppel. Useful when several Doppels are open at once and
the tabs are otherwise identical.
An Option and skipped when absent, for the same reason as dashboard.
Read it through [AdminConfig::title].
| Type | string |
| Required | no |
| Rules | 1 to 64 characters |
admin:
title: billing-api (staging)
admin.tokens¶
The tokens that may call the admin API. Names and token values are
each unique. DOPPEL_ADMIN_TOKENS can supply these instead.
| Type | list of object |
| Required | no |
admin:
tokens:
- name: billing-api
group: billing-api
token: replace-me-00000000000000000000000
admin.tokens[].group¶
The group it belongs to. admin and user are predefined; any other
name must be carried by at least one token.
| Type | string |
| Required | yes |
| Rules | matches ^[A-Za-z0-9_-]{2,64}$; 2 to 64 characters |
admin:
tokens:
- group: billing-api
admin.tokens[].name¶
What to call this token in access lists and in logs. Never the
secret itself.
| Type | string |
| Required | yes |
| Rules | matches ^[A-Za-z0-9_-]{2,64}$; 2 to 64 characters |
admin:
tokens:
- name: billing-api
admin.tokens[].token¶
The secret the caller sends. A version 4 UUID is the recommended shape.
| Type | string |
| Required | yes |
| Rules | 32 to 255 characters |
admin:
tokens:
- token: replace-me-00000000000000000000000
admin.upload¶
Bounds an uploaded template file.
| Type | object |
| Required | yes |
| Rules | no unknown keys |
admin:
upload:
limit: 1048576
admin.upload.limit¶
Largest template file the admin API accepts. A larger upload is
refused with 413.
| Type | integer or string |
| Required | yes |
| Rules | 1 to 1073741824 |
admin:
upload:
limit: 1048576
control¶
The Unix socket doppel config reload talks to.
| Type | object |
| Required | no |
| Rules | no unknown keys |
control.socket¶
Path to the control socket, created with mode 0600 and removed on
shutdown. Its parent directory must already exist.
A filesystem path. utoipa has no schema for PathBuf, so it is
described as the string it is written as.
| Type | string |
| Required | no |
control:
socket: /tmp/doppel.sock
logging¶
Log level and output format.
| Type | object |
| Required | no |
| Rules | no unknown keys |
logging.format¶
json for machines, text for a terminal.
| Type | string |
| Required | no |
| Rules | one of json, text |
logging:
format: json
logging.level¶
The lowest level that is logged. RUST_LOG overrides it when set and
non-empty.
| Type | string |
| Required | no |
| Rules | one of trace, debug, info, warn, error |
logging:
level: trace
proxies¶
The proxies this instance serves, in the order they are tried.
May be empty or left out: Doppel then starts and serves the admin API,
and a request is answered 503 NO_PROXIES_CONFIGURED until a proxy is
added by reload or over that API.
| Type | list of object |
| Required | no |
proxies:
- name: billing-api
type: http
url: https://example.com/api/
proxies[].access¶
Overrides the admin access rules for this proxy alone.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies[].access.delete¶
Who may remove this proxy. Absent leaves the global rule.
| Type | string or list of string |
| Required | no |
proxies:
- access:
delete: public
proxies[].access.read¶
Who may read this proxy's document. Absent leaves the global rule.
| Type | string or list of string |
| Required | no |
proxies:
- access:
read: public
proxies[].access.update¶
Who may replace this proxy. Absent leaves the global rule.
| Type | string or list of string |
| Required | no |
proxies:
- access:
update: public
proxies[].access.upload¶
Who may upload templates for this proxy. Absent leaves the global rule.
| Type | string or list of string |
| Required | no |
proxies:
- access:
upload: public
proxies[].body_limit¶
Bounds the request body a matched mock is allowed to buffer in order
to extract from it; phase 1 streams bodies deliberately, and reading
.content.items needs the whole thing in hand. See rule V33.
| Type | integer or string |
| Required | no |
| Rules | 1 to 1073741824 |
proxies:
- body_limit: 1048576
proxies[].headers¶
Headers injected into every outbound request, overriding whatever the client sent by the same name.
| Type | map of string to string |
| Required | no |
proxies:
- headers:
X-Request-Id: staging
proxies[].latency¶
Makes a share of requests take a chosen time. Applies to mocked responses too; a mock may override the figure.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies:
- latency:
percentage: 0.25
min: 0.25
max: 0.25
proxies[].latency.max¶
Upper bound of the delay, in seconds. Must be at least min.
| Type | number |
| Required | yes |
| Rules | 0 to 300 |
proxies:
- latency:
max: 0.25
proxies[].latency.min¶
Lower bound of the delay, in seconds. The delay is a target for the whole response: time the upstream already spent is subtracted.
| Type | number |
| Required | yes |
| Rules | 0 to 300 |
proxies:
- latency:
min: 0.25
proxies[].latency.percentage¶
The share of requests to delay, as a fraction.
| Type | number |
| Required | yes |
| Rules | 0 to 1 |
proxies:
- latency:
percentage: 0.25
proxies[].loss¶
Drops a share of requests rather than forwarding them. Not applied to a request a mock answered.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies:
- loss:
percentage: 0.25
status: 200
proxies[].loss.percentage¶
The share of requests to drop, as a fraction. 0.1 is one in ten.
| Type | number |
| Required | yes |
| Rules | 0 to 1 |
proxies:
- loss:
percentage: 0.25
proxies[].loss.status¶
The status a dropped request is answered with, rather than being left to hang.
| Type | integer |
| Required | yes |
| Rules | 100 to 599 |
proxies:
- loss:
status: 200
proxies[].mocks¶
Mocks in the order they are tried. First match wins, and patterns are unanchored, so a general one placed first shadows the rest.
| Type | list of object |
| Required | no |
proxies:
- mocks:
- name: billing-api
request:
method: GET
url: "/api/(?P<id>\\d+)/"
response:
status: 200
proxies[].mocks[].name¶
Names this mock in the mock log field and the hit counter. Unique
within the proxy.
| Type | string |
| Required | yes |
| Rules | matches ^[A-Za-z0-9_-]{2,64}$; 2 to 64 characters |
proxies:
- mocks:
- name: billing-api
proxies[].mocks[].proxy¶
Per-mock overrides of the proxy's replace, loss and latency.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies[].mocks[].proxy.latency¶
Replaces the proxy's latency for this mock's responses, rather than
adding to it.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies:
- mocks:
- proxy:
latency:
percentage: 0.25
min: 0.25
max: 0.25
proxies[].mocks[].proxy.latency.max¶
Upper bound of the delay, in seconds. Must be at least min.
| Type | number |
| Required | yes |
| Rules | 0 to 300 |
proxies:
- mocks:
- proxy:
latency:
max: 0.25
proxies[].mocks[].proxy.latency.min¶
Lower bound of the delay, in seconds. The delay is a target for the whole response: time the upstream already spent is subtracted.
| Type | number |
| Required | yes |
| Rules | 0 to 300 |
proxies:
- mocks:
- proxy:
latency:
min: 0.25
proxies[].mocks[].proxy.latency.percentage¶
The share of requests to delay, as a fraction.
| Type | number |
| Required | yes |
| Rules | 0 to 1 |
proxies:
- mocks:
- proxy:
latency:
percentage: 0.25
proxies[].mocks[].proxy.loss¶
Drops a share of the requests this mock would have answered. Not inherited from the proxy: a mock without it is never dropped.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies:
- mocks:
- proxy:
loss:
percentage: 0.25
status: 200
proxies[].mocks[].proxy.loss.percentage¶
The share of requests to drop, as a fraction. 0.1 is one in ten.
| Type | number |
| Required | yes |
| Rules | 0 to 1 |
proxies:
- mocks:
- proxy:
loss:
percentage: 0.25
proxies[].mocks[].proxy.loss.status¶
The status a dropped request is answered with, rather than being left to hang.
| Type | integer |
| Required | yes |
| Rules | 100 to 599 |
proxies:
- mocks:
- proxy:
loss:
status: 200
proxies[].mocks[].proxy.replace¶
Overrides the proxy's replace for requests this mock matches.
| Type | number |
| Required | no |
| Rules | 0 to 1 |
proxies:
- mocks:
- proxy:
replace: 0.25
proxies[].mocks[].request¶
What this mock matches, and what it takes out of the request.
| Type | object |
| Required | yes |
| Rules | no unknown keys |
proxies:
- mocks:
- request:
method: GET
url: "/api/(?P<id>\\d+)/"
proxies[].mocks[].request.body¶
Variable name -> selector such as .content.items.
| Type | map of string to string |
| Required | no |
proxies:
- mocks:
- request:
body:
key: .content.items
proxies[].mocks[].request.headers¶
Variable name -> request header name.
| Type | map of string to string |
| Required | no |
proxies:
- mocks:
- request:
headers:
key: X-Request-Id
proxies[].mocks[].request.method¶
The method this mock answers, matched exactly and upper case.
| Type | string |
| Required | yes |
| Rules | one of GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE, CONNECT, QUERY, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK |
proxies:
- mocks:
- request:
method: GET
proxies[].mocks[].request.query¶
Variable name -> selector such as .filter.
| Type | map of string to string |
| Required | no |
proxies:
- mocks:
- request:
query:
key: .content.items
proxies[].mocks[].request.url¶
A regex matched against the request path, unanchored. Named capture groups become template variables.
| Type | string |
| Required | yes |
proxies:
- mocks:
- request:
url: "/api/(?P<id>\\d+)/"
proxies[].mocks[].response¶
What it answers with.
| Type | object |
| Required | yes |
| Rules | no unknown keys |
proxies:
- mocks:
- response:
status: 200
proxies[].mocks[].response.body¶
A template rendered and sent as text/plain. Exclusive with json and
template.
| Type | string |
| Required | no |
proxies:
- mocks:
- response:
body: widget {{ id }} is ready
proxies[].mocks[].response.headers¶
Header name -> template producing the value.
The value is a template, not a header value: what it renders to is only a header value once a request has been served, which is checked there. The name is a name now.
| Type | map of string to string |
| Required | no |
proxies[].mocks[].response.json¶
A template whose rendered output must be valid JSON, sent as
application/json. Exclusive with body and template.
| Type | string |
| Required | no |
proxies:
- mocks:
- response:
json: "{\"id\": \"{{ id }}\"}"
proxies[].mocks[].response.status¶
The status to answer with.
| Type | integer |
| Required | yes |
| Rules | 100 to 599 |
proxies:
- mocks:
- response:
status: 200
proxies[].mocks[].response.template¶
A template file under this proxy's template directory. Read per request,
so it may be uploaded after the configuration was loaded. Exclusive with
body and json.
| Type | string |
| Required | no |
| Rules | at most 200 characters |
proxies:
- mocks:
- response:
template: put.json.j2
proxies[].name¶
Names this proxy in X-Proxy-Name, in metrics labels, in log lines
and as its template subdirectory. Unique within the document.
| Type | string |
| Required | yes |
| Rules | matches ^[A-Za-z0-9_-]{2,32}$; 2 to 32 characters |
proxies:
- name: billing-api
proxies[].replace¶
What share of requests a matching mock actually answers; the rest go upstream. Defaults to 1.0, so a matching mock answers.
| Type | number |
| Required | no |
| Rules | 0 to 1 |
proxies:
- replace: 0.25
proxies[].resolve¶
How a request is matched to this proxy: by header, or as the default.
| Type | object |
| Required | no |
| Rules | no unknown keys |
proxies[].resolve.header¶
The header carrying the proxy name. Required when type: header, and
meaningless otherwise.
| Type | string |
| Required | no |
proxies:
- resolve:
header: X-Request-Id
proxies[].resolve.type¶
default takes anything unclaimed; header takes requests naming
this proxy in header.
| Type | string |
| Required | no |
| Rules | one of default, header |
proxies:
- resolve:
type: default
proxies[].rewrite_redirects¶
Whether a redirect whose Location points back into the space this
proxy forwards is rewritten to point at Doppel instead. Absent means
enabled.
On by default because the alternative is a silent failure: Host is
replaced with the upstream's authority, so the upstream's Location
names the upstream, and a client following it leaves Doppel -- along with
every fault and every mock -- with nothing reported. Turn it off to have
the response relayed byte for byte, which is what a client being tested
against redirect handling itself needs.
| Type | boolean |
| Required | no |
proxies:
- rewrite_redirects: true
proxies[].rewrite_urls¶
Whether the upstream's own address is rewritten to Doppel's inside the bodies this proxy relays. Absent means enabled.
The same problem rewrite_redirects solves, one layer down: a page, a
script or a JSON document that names https://api.example.com/v2/ sends
the client straight there on the next request, past every fault and every
mock. Rewriting the body keeps it inside Doppel.
Only the exact host is replaced. https://cdn.api.example.com/ is a
different host and is left alone, because Doppel does not proxy it and
pointing it at itself would break a page rather than keep it working.
Only text bodies, and only ones the upstream did not compress. Turn it off for a client being tested against the bytes the upstream actually sent.
| Type | boolean |
| Required | no |
proxies:
- rewrite_urls: true
proxies[].timeout¶
Bounds the whole upstream exchange, in seconds. Exceeding it is
504 UPSTREAM_TIMEOUT. Defaults to 30.
| Type | integer |
| Required | no |
| Rules | 1 to 3600 |
proxies:
- timeout: 30
proxies[].type¶
What it forwards. http is the only value.
| Type | string |
| Required | yes |
| Rules | one of http |
proxies:
- type: http
proxies[].url¶
The upstream base. A request path is grafted underneath it, and the result can never escape it -- so a base with a path confines the proxy to that subtree.
| Type | string |
| Required | yes |
proxies:
- url: https://example.com/api/
sentry¶
Optional error reporting. Absent, or an empty DSN, disables it.
| Type | object |
| Required | no |
| Rules | no unknown keys |
sentry:
dsn: https://key@o0.ingest.sentry.io/0
sentry.dsn¶
The Sentry DSN to report to. Empty disables reporting, so a deployment can blank it without removing the section.
DOPPEL_SENTRY_DSN overrides it. A DSN carries the key that authorises
sending events, so a deployment that keeps credentials in the environment
can leave this field out entirely; an empty variable counts as unset and
leaves this value in force.
| Type | string |
| Required | yes |
sentry:
dsn: https://key@o0.ingest.sentry.io/0
server¶
Where the proxy listens for the traffic being forwarded or mocked.
| Type | object |
| Required | yes |
| Rules | no unknown keys |
server:
host: 127.0.0.1
port: 8080
server.external_url¶
Where clients reach this Doppel, when that is not host:port.
Behind a container port mapping, a load balancer or an ingress, the
address Doppel bound is not the address a client used -- and a rewritten
Location has to name the second. Doppel cannot infer it: Host is a
claim by the caller, and building a redirect out of it hands the caller
the redirect.
Absent, host and port are used, with a wildcard bind (0.0.0.0, ::)
read as loopback -- which is right for a laptop or a pod reached at its
own address, and wrong behind a port mapping or an ingress, where the
client used neither. Doppel logs the address it settled on at startup.
DOPPEL_EXTERNAL_URL overrides it. Part of server, so a change takes a
restart like the rest of that section.
| Type | string |
| Required | no |
server:
external_url: https://doppel.example.com/
server.host¶
An IP address, not a hostname: a name would have to be resolved,
and which address it resolves to is not the configuration's to
decide. utoipa has no schema for IpAddr, so it is described
here as the string it is written as.
| Type | string |
| Required | yes |
server:
host: 127.0.0.1
server.port¶
The TCP port proxied traffic arrives on. Must differ from
admin.port.
| Type | integer |
| Required | yes |
| Rules | 1 to 65535 |
server:
port: 8080
templates¶
Where mock template files are read from and uploaded to.
| Type | object |
| Required | no |
| Rules | no unknown keys |
templates.dir¶
Directory holding mock templates, one subdirectory per proxy. Created
at startup if absent.
A filesystem path. utoipa has no schema for PathBuf, so it is
described as the string it is written as.
| Type | string |
| Required | no |
templates:
dir: ./templates