Docker Compose

For projects using Docker Compose where multiple services need their own proxy ports, use the ports mapping with auto port assignment:

# mdp.yaml
services:
  frontend:
    command: npm run dev
    proxy: 3000

  infra:
    command: docker compose up
    log_split: compose     # split per-container logs into their own colored lanes
    env:
      API_PORT: auto       # mdp assigns a free port
      AUTH_PORT: auto
    ports:
      - env: API_PORT
        proxy: 4000        # registered as "<branch>/infra" on the 4000 proxy
      - env: AUTH_PORT
        proxy: 5000        # registered as "<branch>/infra" on the 5000 proxy

In your docker-compose.yml, reference the environment variables:

# docker-compose.yml
services:
  api:
    build: ./api
    ports:
      - "${API_PORT:-8080}:8080"
  auth:
    build: ./auth
    ports:
      - "${AUTH_PORT:-8081}:8080"

When you run mdp run, mdp assigns free ports, sets them as environment variables, and registers each port mapping with the appropriate proxy.

Per-container log splitting: log_split: compose parses compose's combined-stream output (<name> | <message>) and gives each container its own colored prefix. Lines that don't match (compose's own status output like Attaching to api-1, auth-1…) stay under the outer service's prefix. For ad-hoc commands outside mdp.yaml, pass --log-split=compose:

mdp run --log-split=compose -- docker compose up

For non-compose multiplexers (kubectl, honcho/foreman, bracket-prefixed tools), use the regex form — see log_split in the reference.

Non-HTTP ports (no proxy): omit proxy: on a ports: entry to allocate a free port for ${svc.env.VAR} interpolation without starting a reverse-proxy listener for it. Useful for databases, caches, and other non-HTTP services other services just need to connect to directly.

db:
  command: docker compose up db --wait
  env:
    DB_PORT: auto
  ports:
    - env: DB_PORT    # allocated & interpolatable, no proxy

UDP ports: add protocol: udp to a ports: entry so mdp allocates the host port with a UDP-aware free-port check and skips it in the depends_on readiness probe (TCP probes never succeed on UDP). This is what lets multiple worktrees run the same UDP-publishing compose stack in parallel — each gets its own random host port, and nothing collides.

# mdp.yaml
infra:
  command: docker compose up --wait
  env:
    JAEGER_AGENT_PORT: auto
  ports:
    - env: JAEGER_AGENT_PORT
      protocol: udp
# docker-compose.yml
services:
  jaeger:
    image: jaegertracing/all-in-one
    ports:
      - "${JAEGER_AGENT_PORT}:6831/udp"

UDP mappings are allocation-only: proxy: is rejected at config load.

HTTPS

mdp inherits TLS certificates from the services it proxies. When a service registers with --tls-cert and --tls-key, the proxy automatically starts accepting HTTPS connections using that certificate. Each proxy port serves both HTTP and HTTPS on the same port.

# Service provides its own cert — proxy inherits it
mdp run --tls-cert ./certs/localhost.pem --tls-key ./certs/localhost-key.pem -- npm run dev

# Auto-detect mkcert certs
mdp run --auto-tls -- npm run dev

Generate a local cert with mkcert:

mkcert localhost
mdp run --tls-cert localhost.pem --tls-key localhost-key.pem -- npm run dev

← Back to docs index