An independent, .NET-based API mock engine + platform. A transport-agnostic request-matching and response engine with first-class multi-tenancy, pluggable persistence, and thin facades — in-process library · HTTP server · admin REST · gRPC · GraphQL · WebSocket — plus message mocking: an SMTP capture server and Twilio-compatible SMS emulation with a tenant-scoped inbox and one-call OTP verification. Clean-room codebase with its own IP and no third-party mock-engine dependencies.
📖 Documentation → mockifyr.omercelik.dev — guides, the full CLI and admin API reference, and known limitations.
Just run it — in-memory, zero config, the same one line on macOS, Linux and Windows:
docker run -p 8080:8080 ghcr.io/omercelikdev/mockifyr- Mock surface —
http://localhost:8080 - Admin API —
http://localhost:8080/__admin - Dashboard —
http://localhost:8080/__mockifyr
Create stubs in the dashboard, or import a WireMock bundle. Runs on linux/amd64 and linux/arm64
(Apple Silicon included).
Keep your data across restarts — docker compose up, or a named volume (both identical on every OS):
docker compose up # stubs live in ./mappings, next to you
docker run -p 8080:8080 -v mockifyr-data:/work ghcr.io/omercelikdev/mockifyr # named volumeMount /work, not just /work/mappings — the file store also keeps environment configuration
(/work/environments), response body files (/work/__files) and gRPC descriptors (/work/grpc)
there, and a mappings-only mount silently loses those when the container is recreated.
Preload / edit stub files on your host (advanced) — bind-mount a folder of WireMock *.json. Only
the path syntax differs per shell; nothing else changes:
docker run -p 8080:8080 -v "$PWD/mappings:/work/mappings" ghcr.io/omercelikdev/mockifyr # macOS / Linux
# PowerShell: -v "${PWD}/mappings:/work/mappings" CMD: -v "%cd%/mappings:/work/mappings"Files load into the default tenant; for a named tenant (e.g. maestro) use the dashboard Import,
or POST to /__admin/mappings/import with an X-Mockifyr-Tenant header. Durable datastores:
docker compose -f docker-compose.postgres.yml up # PostgreSQL persistence
docker compose -f docker-compose.redis.yml up # Redis persistenceAspire recreates containers on every app-host run, so without a volume the file store — stubs and
environment configuration — resets each time. 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 runsOr point it at a durable datastore instead: .WithArgs("--postgres", connectionString).
dotnet run --project src/Mockifyr.Server -- --port 8080 --root-dir . # stubs load from ./mappingsThe dashboard is opt-in via --dashboard; omit it to serve just the mock surface + admin API.
# Local
dotnet run --project src/Mockifyr.Server -- --port 8080 --root-dir . # stubs load from ./mappings
# From the image (override the entrypoint to drop the built-in --dashboard)
docker run -p 8080:8080 -v "$PWD/mappings:/work/mappings" --entrypoint dotnet \
ghcr.io/omercelikdev/mockifyr:latest Mockifyr.Server.dll --port 8080 --root-dir /workOr embed the engine directly in-process with Mockifyr.Facade.Library — no HTTP at all. It is not
published to NuGet yet; reference the project from a checkout of this repository.
Everything is a CLI flag — there is no config file. Because the host builds its configuration with the
standard .NET builder, every flag is also readable as an environment variable of the same name,
which is why -e admin-user=alice works on docker run; arguments win when both are present.
The common flags, with the full reference on the docs site:
| Flag | Effect |
|---|---|
--port <n> |
mock-serving HTTP port (default 8080) |
--https-port <n> |
enable HTTPS / HTTP2 |
--root-dir <dir> |
load and persist stubs as JSON files |
--smtp-port <n> |
capture real SMTP mail into the tenant-scoped message inbox (/__admin/messages); the AUTH username names the tenant |
--sms-profile twilio |
emulate Twilio's send-message API: realistic responses the official SDK accepts, every SMS captured into the message inbox |
--message-limit <n> |
per-tenant message inbox bound (default 1000, oldest evicted first) |
--dashboard <dir> |
serve the built dashboard under /__mockifyr |
--admin-user <u> · --admin-pass <p> |
require HTTP Basic auth on the admin API (/__admin/*); the dashboard shows a login screen |
--postgres <connstr> · --redis <connstr> · --litedb <path> |
durable persistence backend |
--change-feed |
keep multiple instances coherent |
--outbound-host-fallback false |
deliver callbacks and proxies to exactly the address written, never retrying via the host gateway |
--trust-proxy-target <host> |
trust that host's certificate on outbound calls (repeatable) |
--trust-all-proxy-targets |
trust every outbound certificate |
The hot path is always in-memory; a durable backend is opt-in and writes through.
Running in Docker, localhost inside the container means the container — so a callback or proxy
aimed at http://localhost:5004 cannot reach a service on your machine, even though the same URL
works from Postman. Mockifyr handles this: a loopback target whose connection is refused is
retried once via host.docker.internal (a callback records both attempts in the journal; a proxy that
still cannot be reached answers 502 with the reason). Targeting host.docker.internal yourself works
too, and --outbound-host-fallback false turns the retry off (--webhook-host-fallback is a kept alias).
On Linux, host.docker.internal only exists if the container is started with
--add-host=host.docker.internal:host-gateway.
An endpoint served by your organisation's internal CA is trusted by your machine's keychain but not
by the container, so an outbound call to it fails where Postman succeeds. The journal names the
reason (RemoteCertificateChainErrors, a name mismatch, an expiry). To allow it, trust that endpoint
by name — the same flags WireMock uses, applied to callbacks and proxying alike:
docker run … mockifyr --trust-proxy-target api.dev.mycorp.intraTrusting one host grants nothing to any other. --trust-all-proxy-targets disables verification for
every target; the host prints a warning at startup when either flag is in effect. Without them,
certificates are verified normally.
You can also manage trusted hosts from Settings → Outbound certificate trust in the dashboard,
which takes effect on the next call with no restart and survives one. Passing a --trust-* flag
pins the configuration instead and the dashboard shows it read-only — the same two-mode design as
Git sync. --trust-all-proxy-targets stays flag-only: the dashboard can trust individual hosts but
cannot turn verification off. Full detail:
HTTPS, HTTP/2 and mTLS.
Using Mockifyr — mockifyr.omercelik.dev
- Getting started · the dashboard
- Stubs — request matching · responses · templating
- Behaviour — scenarios · delays and faults · proxying · record and playback · webhooks
- Platform — multi-tenancy · environments · persistence · HTTPS and mTLS
- Messages — email & SMS mocking (SMTP capture, Twilio profile, OTP verify)
- Reference — CLI · admin API · extending
- Migrating from WireMock, and the known limitations worth reading first
Working on Mockifyr — in this repository
- Architecture & design — ARCHITECTURE.md
- Roadmap — docs/roadmap.md · decisions — docs/decisions/
- Learned WireMock behaviour, per feature group — docs/parity/
- Testing strategy (the binding test contract) — docs/testing.md
- Brand assets and their usage rules — brand/
- This is an AI-driven repository; how work is done here — CLAUDE.md
Contributions are welcome. Read CLAUDE.md for the development workflow and conventions,
then open a PR against main. Builds must stay green — dotnet build and dotnet test, plus the
dashboard's pnpm build.
Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.