A product by Ontology Labs
Blog  /  Engineering

Running any Mendix™ app in Docker (7–11) without bloating your disk

If you run Mendix™ apps in Docker, you have probably met this: the official cf-mendix-buildpack image bakes the application model into the image. That is exactly what you want for an immutable production artifact, but heavier than you need in an iterative dev/test loop.

We open-sourced the recipes we use internally for that dev loop: github.com/ontologylabs/mendix-runtime-crates (Apache-2.0). They run any Mendix app, versions 7 through 11, without baking the model into the image, and they ship no Mendix binaries.

The dev-loop cost: a new image (and a dangling one) every reload

Because the model is baked in, every model change means a fresh docker build, and the previous image becomes a dangling layer: ~785 MB each (measured on the 11.6.4 buildpack image). Reload a few dozen times across a working day and you have quietly burned tens of GB. You end up running docker image prune as a reflex.

The inversion: bake the runtime once, bind-mount the model

The fix is to separate what changes from what does not:

Two patterns for running a model in Docker
cf-buildpack imageRuntime crate
Mendix™ runtime + JREbakedbaked (once per version)
Application modelbakedbind-mounted
Reloaddocker build --no-cachedocker compose restart
Disk per reload~785 MB dangling0
Images per versionmany (one per commit)1

Same Mendix version → same image. Ten apps on 11.6.4 → one image, ten bind-mounts.

No binaries: pull the runtime from the CDN at build time

The recipes ship no Mendix™ runtime files. The Dockerfile curls the runtime from the Mendix CDN at build time, on your machine:

ARG MENDIX_VERSION=11.6.4
ARG RUNTIME_CDN_BASE=https://cdn.mendix.com/runtime
RUN curl -fsSL "${RUNTIME_CDN_BASE}/mendix-${MENDIX_VERSION}.tar.gz" ...

So the scaffolding stays clean Apache-2.0 (no redistribution of Mendix IP), and the runtime you bake is the one you are licensed for. The repo even has a CI guard that fails if anyone commits a *.tar.gz / *.mda / *.jar or an oversized file. A runtime binary cannot be merged.

Try it

Build a crate image, then bring it up with Postgres next to it. Step 1 is a one-time docker build that pulls the runtime from the CDN (a couple of minutes); after that, reloading a model change is just docker compose restart.

1. Build the runtime image (once per version)

# clone, then build the Mendix 11 crate
git clone https://github.com/ontologylabs/mendix-runtime-crates.git
cd mendix-runtime-crates/crates/mendix-11
docker build --platform linux/amd64 \
  --build-arg MENDIX_VERSION=11.6.4 \
  -t ontologylabs/mendix-runtime:11.6.4 .

2. Drop in your unzipped MDA and bring it up

# place an unzipped MDA where the runtime can read it
unzip /path/to/your.mda -d ./mda/

# docker-compose.yml — runtime + Postgres on one network
services:
  postgres:
    image: postgres:14-alpine
    environment:
      POSTGRES_USER: mendix
      POSTGRES_PASSWORD: mendix
      POSTGRES_DB: mendix
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mendix"]
      interval: 5s
      timeout: 3s
      retries: 10

  mendix-runtime:
    image: ontologylabs/mendix-runtime:11.6.4
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8080:8080"
      - "8090:8090"
    environment:
      DATABASE_ENDPOINT: "postgres://mendix:mendix@postgres:5432/mendix"
      ADMIN_PASSWORD: "a-strong-password"
      DEVELOPMENT_MODE: "true"
    volumes:
      - ./mda:/opt/mendix/app
      - mendix-data:/opt/mendix/data

volumes:
  mendix-data:
docker compose up
# then: curl -fsS http://localhost:8080/

The contract. The runtime reads DATABASE_ENDPOINT (parsed for host, port, user, password and database), requires a strong ADMIN_PASSWORD, and bind-mounts the unzipped MDA at /opt/mendix/app. DEVELOPMENT_MODE=true relaxes the MX 11 project-security gate for local runs. To reload a model change: replace ./mda and run docker compose restart mendix-runtime. No rebuild, no dangling layer.

Which versions

Crates exist for Mendix™ 7 through 11 (the runtimelauncher.jar + m2ee boot path is broadly the same across them; the per-version differences, mostly how model constants are supplied, are handled in each crate’s start.sh). Each crate’s versions.yaml lists the verified patch versions. Need a version or patch we don’t cover yet? open a “Request a Mendix version” issue, or PR it (build + smoke-test + a versions.yaml row).

Why we built it

We are building mxto, the AI delivery engine for the Mendix™ platform, which reads, authors and ships Mendix apps headless. Clean, reproducible local runtimes are table stakes for that, so these recipes already existed; open-sourcing them was easy. If they save you some disk, a star helps other Mendix developers find them.