For the complete documentation index, see llms.txt. This page is also available as Markdown.
AlphaTeamEnterprise

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=disable

the operator creates the branch pod from your config, and the local process sees:

DATABASE_URL=postgresql://root@10.244.0.42:26257/defaultdb?sslmode=disable

Only 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

Field
Description

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:

  1. The operator resolves VALKEY_PASSWORD from the target pod - since the pod reads it from a Secret, it arrives on the branch container as a secretKeyRef under the name MIRRORD_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.

  2. The default TCP probe on 6379 passes and the branch turns Ready.

  3. Locally, only the host and port fragments of VALKEY_ADDR are rewritten in place (the value_pattern captures), so the app still sees the host:port shape it expects - now pointing at the branch. VALKEY_PASSWORD is 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:

  1. The operator creates (or reuses, by id) a branch pod running influxdb:2.7, with the setup env filled from the params resolved off the target pod: token straight from the influx-creds Kubernetes Secret (as a secretKeyRef), org and bucket from the pod's env vars. The image's setup mode then creates that same org, bucket, and admin token on first start.

  2. The http_get probe on /health passes once setup finished and the server is up - only then does the branch turn Ready.

  3. Locally, the host/port fragments of INFLUXDB_URL are rewritten to point at the branch (the URL shape and scheme survive). INFLUXDB_TOKEN, INFLUXDB_ORG, and INFLUXDB_BUCKET are 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 host param (no port) 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:

Type
Config
Ready when

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 host param 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-local in-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.genericBranching in 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 allowedImages glob list in the generic branch config (Helm genericBranchConfig). 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 args are visible in the pod spec to anyone with pod-read access. Reference secrets via $(MIRRORD_PARAM_*) instead of inlining them - the secretKeyRef mechanism exists precisely so secret values never appear in the pod spec.

  • For images in private registries, set imagePullSecrets in the generic branch config, as with the other engines.

Last updated

Was this helpful?