Skip to content

Persistence

Mockifyr always serves from memory. A durable provider does not change the hot path — it is a write-through on every mutation plus a loader at startup. Matching performance is the same whether you run in-memory or against PostgreSQL.

Choosing a provider

ProviderSelected byStorage shape
None (in-memory)defaultNothing survives a restart
File--root-dir <dir>One id-stamped <id>.json per stub under <dir>/mappings; environments under <dir>/environments/<tenant>/
LiteDB--litedb <path>One document {Id, Tenant, Json} in an embedded single-file database
PostgreSQL--postgres <connstr>Row (id uuid, tenant text, json text), upserted; the table is created if absent
Redis--redis <connstr>Hash mockifyr:stubs:{tenant} keyed by stub id
Terminal window
docker run -p 8080:8080 -v mockifyr-data:/work ghcr.io/omercelikdev/mockifyr
docker compose -f docker-compose.postgres.yml up
docker compose -f docker-compose.redis.yml up

If a datastore flag is passed at run time it takes precedence over the file store.

Stub ids are stamped into the stored JSON, so a stub keeps the same id across a restart on every backend. Anything holding a stub id — a test, a script, a dashboard bookmark — keeps working.

What --root-dir gives you beyond mappings

The root dir is more than a stub folder. It is where the host looks for and writes several other files:

PathContents
<dir>/mappingsStub files
<dir>/__filesWebSocket message bodies
<dir>/grpc/*.dscgRPC descriptors
<dir>/environments/Environment values, per tenant
<dir>/outbound-trust.jsonOutbound certificate trust

Multi-instance coherence

--change-feed keeps several Mockifyr instances backed by the same datastore in agreement:

DatastoreMechanism
RedisPub/sub on channel mockifyr:changes
PostgreSQLLISTEN / NOTIFY on mockifyr_changes

On a notification the instance reconciles every tenant — upsert, then prune — so an instance that missed a message still converges on the next one.

Limitations

WireMock’s per-stub persistent: false opt-out is not supported. When a root dir is set, every admin mutation persists — there is no way to create a stub that is deliberately transient.

Docker

The image bakes --root-dir /work, so everything in the table above lives under /work — stubs in /work/mappings, environments in /work/environments, and so on. Mount a volume at /work and all of it survives restarts and container recreation:

Terminal window
# named volume — stubs, environments, __files and grpc descriptors all survive
docker run -p 8080:8080 -v mockifyr-data:/work ghcr.io/omercelikdev/mockifyr
# plus a bind overlay for the stubs you edit by hand — they live in ./mappings on your host
docker run -p 8080:8080 -v mockifyr-data:/work -v "$PWD/mappings:/work/mappings" ghcr.io/omercelikdev/mockifyr

The repository ships three Compose files:

FileWhat it sets up
docker-compose.ymlNamed volume at /work + ./mappings bind overlay
docker-compose.postgres.ymlPostgreSQL service, --change-feed, healthchecks, named volume
docker-compose.redis.ymlRedis service, --change-feed, healthchecks, named volume

.NET Aspire

Aspire recreates containers on every app-host run, so a file-backed Mockifyr without a volume resets each run — imported stubs and environment configuration included. Mount a named volume at /work (and optionally keep the container alive between runs):

var mockifyr = builder.AddContainer("mockifyr", "ghcr.io/omercelikdev/mockifyr")
.WithHttpEndpoint(port: 8080, targetPort: 8080)
.WithVolume("mockifyr-data", "/work") // survives restarts and recreation
.WithLifetime(ContainerLifetime.Persistent); // optional: reuse the container across runs

Or hand persistence to a durable datastore instead: .WithArgs("--postgres", connectionString).

Git sync

Git sync is layered on top of the root-dir working copy — it commits, pushes and pulls the same files the file provider writes. It is host-level, not tenant-scoped.

Pinning a remote at startup

Terminal window
dotnet run --project src/Mockifyr.Server -- \
--root-dir . --git-remote https://github.com/acme/mock-stubs.git --git-branch main

--git-branch defaults to main.

--git-work-dir <dir> overrides the default working copy, which is <cwd>/mockifyr-data. A host started with no storage flags that finds a .git in that location adopts it as the root dir.

Connecting at runtime

Terminal window
curl -X POST http://localhost:8080/__admin/git/configure \
-d '{"remote":"https://github.com/acme/mock-stubs.git","branch":"main"}'

Credentials

Supply them through the environment:

VariablePurpose
MOCKIFYR_GIT_TOKENAccess token
MOCKIFYR_GIT_USERNAMEUsername, where the remote needs one

Or post them at runtime:

Terminal window
curl -X POST http://localhost:8080/__admin/git/credentials \
-d '{"token":"…","username":"…"}'

Runtime credentials are held in process memory only. They are never echoed back by the API, never passed in argv, and never written to disk — so they do not leak into ps output, a shell history, or the working copy.

Endpoints

MethodRouteBody
GET/__admin/git/status
POST/__admin/git/configureremote and branch
POST/__admin/git/credentialstoken and username
POST/__admin/git/pushoptional {"message":"…"}
POST/__admin/git/pull

How a pull behaves

A pull validates every mappings/**/*.json before applying anything, so a malformed file in the remote does not leave you with a half-applied working copy. It is fast-forward only: Mockifyr never auto-merges and never force-pushes. Divergent history is reported, not resolved for you.

In the dashboard

The Settings page has a Git sync panel — remote status, push and pull.