Back to Blog
All for One, One for All: mockd Deployment Modes
Tutorials 7 min read

All for One, One for All: mockd Deployment Modes

One container or three. Same config, same mocks, same dashboard. Docker Compose files for both — copy, paste, done.

ZS
Zach Snell
March 7, 2026

I spent a week last year trying to decide whether our team’s mock server needed its own admin process or if everything should live in one container. We were three developers. It was not a decision that warranted a week.

With mockd, you can just pick one and switch later. Here’s the all-in-one:

services:
  mockd:
    image: ghcr.io/getmockd/mockd:v0.5.1
    ports:
      - "4280:4280"
      - "4290:4290"
    volumes:
      - ./mocks:/config:ro
    environment:
      MOCKD_CONFIG: "/config/mocks.yaml"
      MOCKD_LOG_LEVEL: "info"
    healthcheck:
      test: ["CMD", "mockd", "health", "--admin-port", "4290"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 5s
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 256M
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp:size=10M,mode=1777

Copy that into a docker-compose.yml. Run docker compose up. Mock server on port 4280, admin API on port 4290. Read-only filesystem, no privilege escalation, 256MB memory cap. Done.

What’s in the container

One static binary. 43MB. Built on gcr.io/distroless/static-debian12:nonroot — no shell, no package manager, no libc. There’s nothing in the container to exploit because there’s almost nothing in the container at all. The default CMD is ["start", "--port", "4280", "--admin-port", "4290"].

Loading mocks

Drop a config file in your ./mocks directory:

version: "1.0"
mocks:
  - id: list-users
    type: http
    http:
      matcher:
        method: GET
        path: /api/users
      response:
        statusCode: 200
        headers:
          Content-Type: application/json
        body: |
          [{"id": "{{uuid}}", "name": "{{faker.name}}", "email": "{{faker.email}}"}]

Every config needs a version, every mock needs an id. The template expressions generate fresh data on each request. No static fixtures going stale.

curl http://localhost:4290/health
# {"status":"ok","uptime":...}

curl http://localhost:4280/api/users
# [{"id": "a3f1...", "name": "Jordan Rivera", "email": "jordan@example.com"}]

mockd import mocks.yaml
# Parsed 3 mocks from mocks.yaml (format: mockd) / Imported 3 mocks to server

mockd list
# Table with ID, TYPE, PATH, METHOD, STATUS, ENABLED

This single-container mode handles everything most teams need. I ran it this way for months before we outgrew it.

When one container isn’t enough

The all-in-one mode starts to feel tight when your QA team wants isolated mocks per environment, when you’re simulating multiple backend services on separate ports, or when you need to scale mock capacity without duplicating admin state.

The answer: one admin, multiple engines. The admin holds mock definitions. The engines serve traffic. Create a mock through the admin, every connected engine picks it up.

The split: admin + engines

mockd up replaces mockd start. Each process gets its own config file.

Admin config (admin.yaml):

version: "1.0"
admins:
  - name: main
    port: 4290
    auth:
      type: none

Engine config (engine.yaml) — the url field is the only link between them:

version: "1.0"
admins:
  - name: main
    url: http://admin:4290
engines:
  - name: worker
    httpPort: 4280
    host: engine
    admin: main

Here’s the docker-compose.yml — one admin, two engines:

services:
  admin:
    image: ghcr.io/getmockd/mockd:v0.5.1
    command: ["up", "-f", "/etc/mockd/admin.yaml", "--log-level=info"]
    volumes:
      - ./admin.yaml:/etc/mockd/admin.yaml:ro
    ports:
      - "4290:4290"
    healthcheck:
      test: ["CMD", "curl", "-sf", "http://localhost:4290/health"]
      interval: 5s
      timeout: 3s
      start_period: 5s
      retries: 5

  engine:
    image: ghcr.io/getmockd/mockd:v0.5.1
    command: ["up", "-f", "/etc/mockd/engine.yaml", "--log-level=info"]
    volumes:
      - ./engine.yaml:/etc/mockd/engine.yaml:ro
    depends_on:
      admin:
        condition: service_healthy
    ports:
      - "4280:4280"

  engine-2:
    image: ghcr.io/getmockd/mockd:v0.5.1
    command: ["up", "-f", "/etc/mockd/engine-2.yaml", "--log-level=info"]
    volumes:
      - ./engine-2.yaml:/etc/mockd/engine-2.yaml:ro
    depends_on:
      admin:
        condition: service_healthy
    ports:
      - "4281:4280"

Engine 1 on 4280, engine 2 on 4281. Both connect to the same admin. Create a mock via the CLI, both engines serve it. No syncing, no shared volumes.

Workspaces add logical isolation within the same admin:

mockd workspace create -n "Payment API"
mockd workspace use <id>
# Switched to workspace...

Each workspace has its own mocks. Point one engine at your payment workspace, another at your user workspace — single admin, full isolation.

Which mode to pick

All-in-one when you’re a solo dev or small team, running mocks in CI, or you want the simplest setup.

Split architecture when multiple teams share mock definitions, you need engines on different ports, or you want to scale mock capacity independently from admin state.

The config format is identical either way — switching costs you nothing. I started with all-in-one and stayed there for six months. The split only became necessary when different squads wanted their own environments without maintaining separate config files.

The honest part

mockd doesn’t include a service mesh or sidecar proxy. If you need traffic routing, mTLS, or circuit breaking at the network level, you still need Envoy, Linkerd, or similar. mockd mocks your APIs — it doesn’t replace your infrastructure.

There’s no built-in auto-scaling. Scaling engines means Kubernetes HPA, Docker Swarm, or whatever orchestrator you use.

In the split architecture, if the admin goes down, existing engines keep serving their last-known mocks — but you can’t create or update mocks until the admin is back. It’s a single point of management, not a single point of serving.

The distroless base image means no shell. You can’t docker exec in and poke around. Use curl http://localhost:4290/health and log output instead.

Learn more

Links

#docker#deployment#architecture#mock server#devops

Try mockd

Multi-protocol API mock server. HTTP, gRPC, GraphQL, WebSocket, MQTT, SSE, SOAP.

curl -fsSL https://get.mockd.io | sh