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 — and a chore in an
iterative dev/test loop.
We open-sourced the recipes we use internally to avoid that chore: 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 problem: 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 — roughly ~785 MB each with the
buildpack pattern. 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:
| cf-buildpack image | Runtime crate | |
|---|---|---|
| Mendix™ runtime + JRE | baked | baked (once per version) |
| Application model | baked | bind-mounted |
| Reload | docker build --no-cache | docker compose restart |
| Disk per reload | ~785 MB dangling | 0 |
| Images per version | many (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 literally 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, 9, 10, and 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 one we do not have?
Mendix 8 is the current gap —
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.