Many teams share a single MockServer deployment across multiple CI pipelines, test suites, or squads rather than running one instance per test. This page describes the deployment models available and helps you choose the right one for your needs.

 

Option 1: Single Shared Instance

When to use

This is the right choice when you have a small number of teams sharing a stable set of expectations in a shared test environment, and you can tolerate the server being unavailable during a restart. It has no extra dependencies and zero operational overhead.

Architecture

flowchart LR LB["Load Balancer / Ingress\n(optional, single upstream)"] MS["MockServer\n(single node)"] T1["Team A CI"] T2["Team B CI"] T3["Team C CI"] T1 --> LB T2 --> LB T3 --> LB LB --> MS

One MockServer process (or Kubernetes Deployment with replicas: 1) receives all traffic. Expectations, scenario state, and CRUD entities live in-memory on that single node. No state-backend module is needed.

Setup

Run the Docker image directly:

docker run --rm -p 1080:1080 mockserver/mockserver:7.0.0

Or on Kubernetes with the Helm chart:

replicaCount: 1

app:
  serverPort: "1080"
  logLevel: INFO
helm upgrade --install --namespace mockserver --create-namespace \
  --version 7.0.0 \
  mockserver oci://ghcr.io/mock-server/charts/mockserver

All configuration properties for log level, TLS, and performance tuning apply exactly as documented in Configuration Properties.

Parallel test isolation

When multiple teams or pipelines share a single instance simultaneously, use a unique session identifier (such as a UUID) in every expectation's request matcher — for example as a header or cookie value — so expectations from one test run do not match requests from another. See Running Tests In Parallel.

Trade-offs

ConcernDetail
AvailabilitySingle point of failure. A restart or crash makes the server unavailable to all teams until it comes back up.
ScaleMockServer handles very high throughput from a single node (see Scalability & Latency), but there is no horizontal scale path without clustering.
State durabilityAll expectations are lost on restart unless you add filesystem persistence (see Option 3).
SimplicityLowest operational overhead: one process, default configuration, no extra dependencies.
 

Option 2: Clustered HA Fleet

When to use

Use this when you need high availability (no single point of failure), horizontal scale beyond a single node, or zero-downtime rolling updates. Clustering requires the mockserver-state-infinispan module on the classpath and a JGroups-capable network between nodes.

The -clustered image variant

The default mockserver/mockserver Docker image does not include the Infinispan and JGroups libraries to keep its size lean for single-node use. For clustering, use the -clustered image variant which bundles the mockserver-state-infinispan module and all of its transitive dependencies:

docker pull mockserver/mockserver:clustered-7.0.0

This image is published to Docker Hub and ECR Public alongside every release, built for both linux/amd64 and linux/arm64. It also includes netty-tcnative-boringssl-static for native TLS performance, the same as the base image.

Important: setting MOCKSERVER_STATE_BACKEND=infinispan with the default (non-clustered) image will fail at startup with a ClassNotFoundException. Always use the clustered- image tag when enabling clustering.

What is clustered

The following state is replicated synchronously across all cluster nodes via JGroups REPL_SYNC:

  • Expectations (add, update, delete)
  • Scenario state (atomic compare-and-set transitions across nodes)
  • CRUD entity stores
  • Chaos profiles (HTTP service, TCP, gRPC)

The following state is node-local and is NOT clustered:

  • The event log and request verification ring buffer. A verify() call (or MockServerClient.verify()) checks only the node that received that call. In a load-balanced cluster, requests may be spread across nodes, so verification must be sent to the specific node that received the request being verified — or verification must be performed per-node and the results combined in your test logic. This is the most significant operational constraint of the clustered model.

Architecture

flowchart LR LB["Load Balancer / Ingress"] NA["MockServer Node A"] NB["MockServer Node B"] NC["MockServer Node C"] INF["JGroups REPL_SYNC\n(Infinispan embedded)"] T1["Team A CI"] T2["Team B CI"] T1 --> LB T2 --> LB LB --> NA LB --> NB LB --> NC NA <--> INF NB <--> INF NC <--> INF

Infinispan runs embedded inside each MockServer node — there is no separate data grid to operate. When one node receives an expectation write, Infinispan replicates it synchronously to all other cluster members before the write call returns. Each node then updates its local matcher cache so it can match incoming requests immediately.

Configuration properties

PropertyEnv varDefaultDescription
mockserver.stateBackend MOCKSERVER_STATE_BACKEND memory Set to infinispan to enable the Infinispan backend. Requires the mockserver-state-infinispan module on the classpath. Fails at startup if the module is missing.
mockserver.clusterEnabled MOCKSERVER_CLUSTER_ENABLED false Set to true to activate JGroups transport and REPL_SYNC caches. When false, Infinispan runs in LOCAL mode (no replication) — useful for testing the code path without a multi-node cluster.
mockserver.clusterName MOCKSERVER_CLUSTER_NAME mockserver-cluster JGroups cluster identifier. All nodes that should share state must use the same value.
mockserver.clusterTransportConfig MOCKSERVER_CLUSTER_TRANSPORT_CONFIG Built-in loopback stack Path to a JGroups XML transport configuration file. The built-in default is a loopback stack suitable only for in-JVM tests. For any real multi-node deployment you must supply a UDP or TCP stack here.

See Configuration Properties for the full property reference.

Setup: Docker Compose (two-node example)

Create a JGroups TCP stack file (jgroups-tcp.xml) — JGroups ships several ready-to-use stacks; the snippet below is representative only. See the JGroups documentation for production-grade stacks with discovery appropriate for your network.

<config xmlns="urn:org:jgroups">
    <TCP bind_port="7600" />
    <TCPPING initial_hosts="mockserver-a[7600],mockserver-b[7600]" />
    <MERGE3/>
    <FD_SOCK/>
    <FD_ALL/>
    <VERIFY_SUSPECT/>
    <pbcast.NAKACK2/>
    <UNICAST3/>
    <pbcast.STABLE/>
    <pbcast.GMS/>
    <pbcast.STATE_TRANSFER/>
</config>
services:
  mockserver-a:
    image: mockserver/mockserver:clustered-7.0.0
    environment:
      MOCKSERVER_STATE_BACKEND: infinispan
      MOCKSERVER_CLUSTER_ENABLED: "true"
      MOCKSERVER_CLUSTER_NAME: shared-cluster
      MOCKSERVER_CLUSTER_TRANSPORT_CONFIG: /config/jgroups-tcp.xml
    volumes:
      - ./jgroups-tcp.xml:/config/jgroups-tcp.xml:ro
    ports:
      - "1080:1080"

  mockserver-b:
    image: mockserver/mockserver:clustered-7.0.0
    environment:
      MOCKSERVER_STATE_BACKEND: infinispan
      MOCKSERVER_CLUSTER_ENABLED: "true"
      MOCKSERVER_CLUSTER_NAME: shared-cluster
      MOCKSERVER_CLUSTER_TRANSPORT_CONFIG: /config/jgroups-tcp.xml
    volumes:
      - ./jgroups-tcp.xml:/config/jgroups-tcp.xml:ro
    ports:
      - "1081:1080"

Setup: Kubernetes (Helm chart)

The MockServer Helm chart has built-in support for clustering via the clustering.enabled value. When enabled, the chart automatically:

  1. Sets the MOCKSERVER_STATE_BACKEND, MOCKSERVER_CLUSTER_ENABLED, MOCKSERVER_CLUSTER_NAME, and MOCKSERVER_CLUSTER_TRANSPORT_CONFIG environment variables
  2. Creates a headless Service for JGroups DNS_PING pod discovery
  3. Sets the JGROUPS_DNS_QUERY environment variable so JGroups discovers all pod IPs automatically
  4. Exposes the JGroups TCP port (default 7800) as a container port

You only need to set clustering.enabled=true, point the image to the -clustered variant, and increase replicaCount. A minimal values file:

replicaCount: 3

image:
  repositoryNameAndTag: "mockserver/mockserver:clustered-7.0.0"

clustering:
  enabled: true
  # clusterName: "mockserver-cluster"   # default
  # transportConfig: "jgroups-kubernetes.xml"  # default — uses DNS_PING
  # jgroupsPort: 7800                   # default
helm upgrade --install --namespace mockserver --create-namespace \
  --version 7.0.0 \
  -f cluster-values.yaml \
  mockserver oci://ghcr.io/mock-server/charts/mockserver

How DNS_PING discovery works: the chart creates a headless Service (ClusterIP: None) with publishNotReadyAddresses: true. JGroups DNS_PING queries the headless Service DNS name (<release>-headless.<namespace>.svc.cluster.local) and receives A records for all pod IPs, including pods still starting up. This allows the cluster to form without additional RBAC or Kubernetes API access.

Advanced: custom JGroups transport

The built-in jgroups-kubernetes.xml transport uses TCP + DNS_PING and works out of the box with the headless Service. For non-Kubernetes environments or advanced tuning, you can supply a custom JGroups XML via the chart's app.config.extraFiles value:

replicaCount: 3

image:
  repositoryNameAndTag: "mockserver/mockserver:clustered-7.0.0"

clustering:
  enabled: true
  transportConfig: /config/jgroups-custom.xml

app:
  config:
    enabled: true
    extraFiles:
      jgroups-custom.xml: |
        <config xmlns="urn:org:jgroups">
            <TCP bind_port="7800" />
            <dns.DNS_PING dns_query="my-headless-svc.my-ns.svc.cluster.local" />
            <MERGE3/>
            <FD_SOCK/>
            <FD_ALL/>
            <VERIFY_SUSPECT/>
            <pbcast.NAKACK2/>
            <UNICAST3/>
            <pbcast.STABLE/>
            <pbcast.GMS/>
            <pbcast.STATE_TRANSFER/>
        </config>

See the JGroups documentation for the full protocol reference. Alternative discovery protocols include KUBE_PING (uses the Kubernetes API; requires a ServiceAccount with pod list/get RBAC) and TCPPING (static member list).

Trade-offs and limitations

ConcernDetail
Availability No single point of failure. Losing one node does not affect the rest of the cluster. Nodes can be rolled without downtime when behind a load balancer.
Horizontal scale Requests can be distributed across nodes. Expectations, scenarios, CRUD entities, and chaos profiles are immediately visible on all nodes after any write, and per-expectation Times match limits are enforced cluster-wide (a Times(3) limit serves exactly 3 total matches across the whole fleet, not 3 per node).
Verification The event log is node-local. verify() checks only the node it is sent to. When a load balancer distributes requests, a request handled by Node A is not visible in Node B's log. Either pin verification calls to specific nodes, run verification against all nodes, or use a single-node deployment when cross-node assertion is required.
Eviction Infinispan uses approximate LRU eviction. The node-local matcher cache may briefly contain one extra entry between an eviction event and the next reconcile cycle.
JGroups transport The built-in loopback transport is for in-JVM tests only. A production cluster requires a proper UDP or TCP JGroups stack configured via clusterTransportConfig.
Operational complexity Adds the Infinispan module, JGroups network configuration, and (on Kubernetes) RBAC for pod discovery. More moving parts than Option 1.
CrossProtocol event bus Scenario event bus registrations (which scenarios fire on which triggers) are node-local. The downstream scenario state changes are clustered, but the trigger registrations themselves are not replicated.
Chaos TTL clock skew TTL-based chaos profile expiry uses the node-local clock. If you advance the clock via PUT /mockserver/clock on individual nodes, TTL expiry may differ between nodes. For deterministic cross-node cleanup, use the REST DELETE endpoint instead.
 

Option 3: Persistence-Only Durability

A middle ground between Options 1 and 2: a single non-clustered node that persists expectations to the filesystem so they survive a restart. No extra modules are needed, and there is no clustering overhead. This is the right choice when you want restart durability but do not need HA or horizontal scale.

Enable filesystem persistence:

docker run --rm \
  -p 1080:1080 \
  -v /var/mockserver:/persistence \
  -e MOCKSERVER_PERSIST_EXPECTATIONS=true \
  -e MOCKSERVER_PERSISTED_EXPECTATIONS_PATH=/persistence/persistedExpectations.json \
  -e MOCKSERVER_INITIALIZATION_JSON_PATH=/persistence/persistedExpectations.json \
  mockserver/mockserver:7.0.0

On Kubernetes use the Helm chart's built-in persistence support:

helm upgrade --install --namespace mockserver --create-namespace \
  --version 7.0.0 \
  --set app.persistence.enabled=true \
  mockserver oci://ghcr.io/mock-server/charts/mockserver

See Persisting Expectations for full details.

Limitations: State is still in-memory at runtime — all active requests hit one node. The same single-point-of-failure and no-horizontal-scale constraints as Option 1 apply. If the process crashes mid-write, persisted expectations may be incomplete until the next successful write cycle.

 

Cloud Blob Storage

When to use

By default MockServer reads and writes persisted expectations, recorded proxy traffic, and fixture files on the local filesystem (blobStoreType=filesystem). When several nodes — or several short-lived CI runners — need to share that durable data without a shared POSIX volume (NFS / EFS / Azure Files), MockServer can store it in a cloud object store instead. This is useful for centralized fixture libraries, record-and-replay cassettes captured by one job and replayed by another, and snapshots that must outlive any individual pod.

Cloud blob storage is an opt-in module: mockserver-core ships with no cloud SDK on its classpath. You add the module for the backend you want, and the backend registers itself automatically when blobStoreType is set to its name. It is orthogonal to the clustering option above — you can use it with a single node, a persisted single node, or an Infinispan cluster.

Choosing a backend module

blobStoreTypeBackendMaven module
s3 Amazon S3 (and S3-compatible stores such as MinIO / LocalStack) org.mock-server:mockserver-blob-s3
gcs Google Cloud Storage org.mock-server:mockserver-blob-gcs
azure Azure Blob Storage org.mock-server:mockserver-blob-azure

Add the relevant dependency (matching your MockServer version) so the cloud SDK is on the classpath:

<dependency>
    <groupId>org.mock-server</groupId>
    <artifactId>mockserver-blob-s3</artifactId>
    <version>7.0.0</version>
</dependency>

Configuration properties

PropertyEnv varApplies toDescription
mockserver.blobStoreType MOCKSERVER_BLOB_STORE_TYPE all Backend selector. Defaults to filesystem. Set to s3, gcs, or azure (the matching module must be on the classpath, otherwise startup fails).
mockserver.blobStoreBucket MOCKSERVER_BLOB_STORE_BUCKET s3, gcs Bucket name. Required for s3 and gcs.
mockserver.blobStoreRegion MOCKSERVER_BLOB_STORE_REGION s3 Region for the S3 bucket (e.g. eu-west-1).
mockserver.blobStoreEndpoint MOCKSERVER_BLOB_STORE_ENDPOINT s3, gcs Endpoint override URL for S3-compatible stores (MinIO, LocalStack) or GCS emulators. Optional.
mockserver.blobStoreKeyPrefix MOCKSERVER_BLOB_STORE_KEY_PREFIX all cloud Optional key prefix so multiple deployments can share one bucket/container without colliding.
mockserver.blobStoreAccessKeyId MOCKSERVER_BLOB_STORE_ACCESS_KEY_ID s3 Explicit AWS access key ID. Optional — when empty the default AWS credential chain (environment, profile, instance/IRSA role) is used.
mockserver.blobStoreSecretAccessKey MOCKSERVER_BLOB_STORE_SECRET_ACCESS_KEY s3 Explicit AWS secret access key. Optional — paired with the access key ID above.
mockserver.blobStoreProjectId MOCKSERVER_BLOB_STORE_PROJECT_ID gcs Google Cloud project ID. Optional — when empty it is inferred from Application Default Credentials.
mockserver.blobStoreContainer MOCKSERVER_BLOB_STORE_CONTAINER azure Azure Blob Storage container name. Required for azure.
mockserver.blobStoreConnectionString MOCKSERVER_BLOB_STORE_CONNECTION_STRING azure Azure connection string (account name, key, and endpoint). Required for azure.

All blob-store properties default to an empty string. See Configuration Properties for the full reference, and Persisting Expectations → Cloud Blob Storage for how persisted and recorded expectations map onto the blob store.

Example: S3 backend

docker run --rm -p 1080:1080 \
  -e MOCKSERVER_BLOB_STORE_TYPE=s3 \
  -e MOCKSERVER_BLOB_STORE_BUCKET=my-mockserver-fixtures \
  -e MOCKSERVER_BLOB_STORE_REGION=eu-west-1 \
  -e MOCKSERVER_BLOB_STORE_KEY_PREFIX=ci-shared/ \
  mockserver/mockserver:7.0.0

Note: the default mockserver/mockserver image does not bundle the cloud SDK modules. To use a cloud blob store from a container, build an image that adds the relevant mockserver-blob-* jar to the classpath, or run via the -no-dependencies jar with the module (and its transitive cloud SDK) on the classpath.

 

Choosing an Option

Need Option 1
Single node
Option 2
Clustered HA
Option 3
Persisted single node
No extra dependencies Yes No (Infinispan + JGroups) Yes
Survives a single node failure No Yes No
Survives a restart without losing expectations No Yes (state replicated) Yes (filesystem persistence)
Horizontal scale (multiple nodes behind LB) No Yes No
verify() works across all nodes Yes (one node) No — node-local log Yes (one node)
Times match limits are global Yes Yes — cluster-wide CAS Yes
Lowest operational complexity Yes No Yes
 

Related Pages