Generic
Branch any stateful service with mirrord by supplying your own container image
This page covers generic DB branching - for databases, caches, and other stateful services that mirrord has no built-in engine for (InfluxDB, Valkey, an internal service, and so on). For the general concepts, the full list of config fields, and how a session behaves, see the DB Branching overview.
A generic branch runs the container image you supply and always starts empty: there are no copy modes, no IAM authentication, and a single redirected port. It is the fallback that unblocks you when no first-class engine exists - engines with built-in support offer copy modes, schema handling, and better errors, so prefer them when available.
Generic branching requires operator 3.183.0, mirrord CLI 3.232.0, and operator Helm chart 3.183.0 with the operator.genericBranching value set to true.
If the operator doesn't support generic branching (older version, or the Helm value is off), the session fails immediately with a clear "operator does not support generic db branching" error rather than timing out.
How It Works
Because mirrord doesn't know the engine, you declare the connection parameters your service needs under connection.params - the fixed slots (host, port, user, password, database) plus any custom key (for example token, org, vhost). The operator resolves each declared parameter from the target pod and injects it into the branch container as an env var named MIRRORD_PARAM_<NAME> (a Secret-backed parameter arrives as a secretKeyRef - the operator never reads its value).
You reference these in command, args, and env using Kubernetes' native $(VAR) syntax, so the branch bootstraps itself with the same values the app already uses. mirrord then only rewrites the app's host/port vars to point at the branch - the app's credential vars stay untouched, and they're valid against the branch because it was bootstrapped with those same values.
Concretely, if the target pod has:
DATABASE_URL=postgresql://root@cockroach-main:26257/defaultdb?sslmode=disablethe operator creates the branch pod from your config, and the local process sees:
DATABASE_URL=postgresql://root@10.244.0.42:26257/defaultdb?sslmode=disableOnly the fragments captured by your value_pattern (here the host) are replaced with the branch pod's address - the scheme, user, database, and query params are untouched, so the app parses the URL exactly as before but now talks to the branch. The override exists only inside the mirrord session: the source database, the target pod, and every other user of it are unaffected.
Two built-in variables are always available alongside the parameters: MIRRORD_BRANCH_ID, and MIRRORD_DATABASE_NAME when the shared name field is set. Use $$(...) for a literal $(...).
Basic Configuration
image
(Required) Full image reference for the branch container, including the tag. The shared version field is not allowed - the tag lives here. Admins can restrict which images are accepted via genericBranchConfig.dbPod.allowedImages - see Restricting Branch Images.
port
(Required) The port the branched service listens on. Used for the default readiness probe and as the port the app is redirected to.
command / args
(Optional) Entrypoint override for the branch container. Values may reference $(MIRRORD_PARAM_<NAME>).
env
(Optional) Extra environment variables for the branch container, with the same $(VAR) references. Keys must not start with MIRRORD_PARAM_.
readiness
(Optional) Readiness check for the branch container. Defaults to a TCP probe on port. See Readiness.
How you feed the params into the branch depends entirely on how your image bootstraps itself - some images take startup flags (use args), others read well-known env vars on first boot (use env), and some need nothing at all. The examples below cover one of each style, from the most minimal to the most involved.
Configuration Examples
CockroachDB - the minimal config (no credentials)
Insecure single-node dev mode needs no bootstrap at all: just the image, the SQL port, the startup args, and host/port patterns. The app reads a postgres-style URL - note the host pattern anchors on the @ that ends the userinfo part, and the rewrite leaves the scheme, user, database, and query params intact.
Target pod env: COCKROACH_URL=postgresql://root@cockroach-main:26257/defaultdb?sslmode=disable - rewritten locally exactly as shown in How It Works.
Also demonstrates: an http_get readiness probe on a different port than the redirected one - health is checked on CockroachDB's admin API (8080) while the app is redirected to the SQL port (26257).
Aerospike - minimal config, binary wire protocol
The image ships a default in-memory test namespace, so like CockroachDB there is nothing to bootstrap. The app talks Aerospike's native binary protocol - generic branching is protocol-agnostic; only the env var rewrite matters:
No command, args, env, or readiness at all - the image's defaults plus the default TCP probe on port are enough.
Valkey - bootstrap via args
The app reads a composite VALKEY_ADDR=valkey-main:6379 env var and a VALKEY_PASSWORD that comes from a Kubernetes Secret. Valkey (like Redis) is configured through command-line flags, so the branch passes the app's real password to --requirepass:
When the session starts:
The operator resolves
VALKEY_PASSWORDfrom the target pod - since the pod reads it from a Secret, it arrives on the branch container as asecretKeyRefunder the nameMIRRORD_PARAM_PASSWORD. The kubelet expands$(MIRRORD_PARAM_PASSWORD)in the args when the container starts, so the branch requires the same password the app already uses - and the secret value never appears in the pod spec.The default TCP probe on
6379passes and the branch turns Ready.Locally, only the host and port fragments of
VALKEY_ADDRare rewritten in place (thevalue_patterncaptures), so the app still sees thehost:portshape it expects - now pointing at the branch.VALKEY_PASSWORDis untouched and just works.
InfluxDB - bootstrap via env (first-boot setup mode)
The app reads INFLUXDB_URL plus token/org/bucket vars. InfluxDB's image isn't configured through flags - it bootstraps through its own first-boot setup mechanism: when the official influxdb image starts with DOCKER_INFLUXDB_INIT_MODE=setup, its entrypoint creates the initial admin user, organization, bucket, and API token from the other DOCKER_INFLUXDB_INIT_* env vars before serving traffic.
The DOCKER_INFLUXDB_INIT_* names are InfluxDB's convention, not mirrord's - they're documented on the influxdb Docker image. mirrord's job is only to fill them: the three $(MIRRORD_PARAM_*) references carry the app's real token/org/bucket into the setup, while MODE/USERNAME/PASSWORD are plain literals (a throwaway admin login the app never uses). Check your own image's docs for its equivalent bootstrap knobs.
When the session starts:
The operator creates (or reuses, by
id) a branch pod runninginfluxdb:2.7, with the setup env filled from the params resolved off the target pod:tokenstraight from theinflux-credsKubernetes Secret (as asecretKeyRef),organdbucketfrom the pod's env vars. The image's setup mode then creates that same org, bucket, and admin token on first start.The
http_getprobe on/healthpasses once setup finished and the server is up - only then does the branch turn Ready.Locally, the host/port fragments of
INFLUXDB_URLare rewritten to point at the branch (the URL shape and scheme survive).INFLUXDB_TOKEN,INFLUXDB_ORG, andINFLUXDB_BUCKETare untouched - and they're valid against the branch, because it was bootstrapped with those exact values.
Also demonstrates: a direct Kubernetes Secret param source ({ "secret": ..., "key": ... }) - the token never needs to appear in the target pod's env at all.
Elasticsearch - bootstrap via ELASTIC_PASSWORD (ES 8 security)
Elasticsearch 8 ships with security enabled. The official image reads ELASTIC_PASSWORD as the elastic user's bootstrap password, so the branch is fed the app's real password through it. Dotted env names become elasticsearch.yml settings - here TLS is turned off so the app talks plain HTTP with basic auth:
Also demonstrates: an exec readiness probe - the command runs inside the branch container and can read the container's env with plain shell $VAR syntax, so Ready means "authentication actually works". JVM services need more memory than the branch pod default - see the resources note below the examples.
OpenSearch - HTTPS + security bootstrap
Same shape as Elasticsearch, using OpenSearch's OPENSEARCH_INITIAL_ADMIN_PASSWORD bootstrap. The demo security config keeps HTTPS on (self-signed certs), so the app connects with TLS verification disabled and the readiness probe curls with -k:
Note the password must satisfy OpenSearch's initial-admin-password strength rules - which is fine, because it's the app's real password resolved from its Secret, not something mirrord invents.
Cassandra - JVM tuning via env, exec probe via cqlsh
Cassandra's default config has no authentication, so no credentials flow to the branch - the env here tunes the image itself (heap size), and readiness uses cqlsh inside the container. Note the probe command can be slow to start (cqlsh is a Python CLI); generic probes run with a 10-second timeout for exactly this reason:
Cassandra needs roughly 2Gi even with a small heap (off-heap structures + JVM overhead) - see the resources note below.
Couchbase - command-wrapper bootstrap + host-only redirection (multi-port)
Couchbase is the hardest bootstrap class: the image has no first-boot env mechanism - a fresh node must be initialized with couchbase-cli after the server starts. The branch handles this with a command wrapper: start the server in the background, wait for the management API, then run cluster-init and bucket-create with the app's real password.
Two things to notice:
The wrapper references the param with plain shell syntax (
$MIRRORD_PARAM_PASSWORD) - the injected params are ordinary env vars on the branch container, so a shell script can read them directly; kubelet$(...)expansion is only needed when there is no shell in between.Couchbase is multi-port (management 8091, query 8093, KV 11210). mirrord redirects a single port - but if the app derives all its URLs from one hostname var, declaring only a
hostparam (noport) redirects every port at once, since they all live on the same branch pod.
The readiness probe checks that the sandbox bucket exists with the bootstrapped credentials, so the branch only turns Ready once the whole init sequence succeeded.
Resource-hungry images: branch pods default to a 512Mi memory limit, which OOM-kills heavier services like Elasticsearch, OpenSearch, Cassandra, and Couchbase. Admins can raise it for all generic branches via dbPod.resources in the generic branch config (Helm genericBranchConfig), next to allowedImages. When a branch container does die, the branch fails immediately with the termination reason (e.g. OOMKilled, exit code 137) instead of hanging until the creation timeout.
Readiness
The branch only turns Ready - and your session only proceeds - once the branch container's readiness probe passes. Because mirrord doesn't know your service, you can pick the probe that actually proves it's up:
tcp (default)
{ "type": "tcp" }
The branch port accepts a TCP connection. No config needed - this is what you get when readiness is omitted.
http_get
{ "type": "http_get", "path": "/health", "port": 8086 }
An HTTP GET on path returns a 2xx/3xx. port defaults to the branch port.
exec
{ "type": "exec", "command": ["valkey-cli", "ping"] }
The command, run inside the branch container, exits 0.
The probe runs every 2 seconds (first check after 3 seconds) with a 10-second timeout per attempt, so slow probe commands like cqlsh don't false-fail. The first success marks the branch Ready; if it never succeeds, the session fails once creation_timeout_secs elapses.
Prefer a probe that captures "actually usable", not just "process started": a plain TCP probe can pass while an image is still initializing. The InfluxDB example uses http_get on /health precisely so the branch isn't Ready until the setup bootstrap has completed - with only the TCP default, the app could connect before its org and token exist.
Connection
The connection must use params mode. URL mode is rejected for generic branches, because rewriting a whole URL requires knowing the engine's scheme and credential layout - for URL-shaped env vars, extract host and port with value_pattern as in the example above. All the other sources from Connection Modes work (Kubernetes Secrets, literal values, multiple sources), except gcp_secret_manager, which is not supported for generic branches.
Declaring neither host nor port is allowed - the branch is still created and bootstrapped, but nothing is redirected (mirrord warns about this at config load).
What Generic Branches Don't Do
No data or schema copy - branches always start empty. If your service is useless empty, it needs a first-class engine.
No IAM authentication.
A single redirected port - if the app derives all its endpoints from one hostname variable, declaring only a
hostparam redirects every port to the branch pod (see the Couchbase example above); but cluster-shaped services that advertise their own addresses to clients are out of scope.No SaaS-only services - a generic branch runs a container image in your cluster, so the service must be self-hostable. Fully managed platforms with no container distribution (Databricks, Snowflake, and the like) have nothing to run. Two escape hatches: if a faithful local emulator image exists for the protocol, branch that instead - this is exactly how the built-in DynamoDB engine works (
amazon/dynamodb-localin-cluster stands in for the AWS service); and if the managed service speaks a standard protocol - for example, Databricks Lakebase is Postgres-compatible - use the matching first-class engine (here, PostgreSQL branching) or a stock image of that engine as the stand-in.
If you find yourself writing a generic config for a common engine, that's a strong signal for us to build first-class support for it - let us know!
Security
Generic branching is off by default (
operator.genericBranchingin the Helm chart) because it lets users who can create branches run arbitrary container images as branch pods. Enabling it is an explicit admin decision per cluster.Admins can restrict which images users may run with an
allowedImagesglob list in the generic branch config (HelmgenericBranchConfig). When the field is absent, all images are allowed. A branch using an image outside the list fails immediately with an error naming the image:
Generic branch pods run under the namespace default service account, not the target's and not the operator's. The service account token is not mounted into the branch pod at all - the container has zero Kubernetes API access even in clusters where roles were bound to the default SA. Branch pods are plain unprivileged pods, so cluster admission policies (PSA, OPA/Kyverno) apply as usual.
Values written directly into
argsare visible in the pod spec to anyone with pod-read access. Reference secrets via$(MIRRORD_PARAM_*)instead of inlining them - thesecretKeyRefmechanism exists precisely so secret values never appear in the pod spec.For images in private registries, set
imagePullSecretsin the generic branch config, as with the other engines.
Last updated
Was this helpful?

