Docker Build & Run Skill

SkillCloud & infra

Lets your agent build, run and validate a Mendix app in Docker using this docker claude skill.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Docker Build & Run Skill skill

About this capability

Build, run and validate a Mendix app in Docker from inside a devcontainer. Use when asked to run the app in Docker or produce a deployable image. For the faster Docker-free loop, prefer run-local.

What this skill tells your AI

The instructions your AI receives, as published by mendixlabs/mxcli in .claude/skills/mendix/docker-workflow/SKILL.md and read by ahel’s review.

This skill guides you through building, running, and testing a Mendix application using Docker inside a devcontainer.

When to Use This Skill

Use this when:

  • The user wants to run their Mendix app locally in Docker
  • The user wants to build a deployable container image
  • The user asks about testing or validating their app
  • The user needs to set up the Docker workflow for the first time

Prerequisites

The devcontainer created by mxcli init includes:

  • JDK 21 (Adoptium temurin-21-jdk) — required by MxBuild
  • Docker-in-Docker (or Podman-in-Podman) — container runtime inside the devcontainer
  • Port forwarding — ports 8080 (app) and 8090 (admin) auto-forwarded

Podman Support

mxcli auto-detects Docker or Podman. To force Podman:

export MXCLI_CONTAINER_CLI=podman

When using mxcli init, pass --container-runtime podman to generate a devcontainer with Podman-in-Podman instead of Docker-in-Docker. Requires Podman 4.7+ (ships podman compose natively).

Architecture

host machine (browser at localhost:8080)
  └── Docker or Podman (host daemon)
      └── Devcontainer (VS Code)
          ├── mxcli, JDK 21, project files
          └── Docker/Podman daemon (docker-in-docker or podman-in-podman)
              └── docker/podman compose stack
                  ├── mendix container (8080, 8090)
                  │     └── /mendix ← volume mount from .docker/build/
                  └── postgres container (5432)

The Mendix container uses a volume mount (not COPY) — .docker/build/ is mounted directly into the container at /mendix. This means after rebuilding the PAD, you only need to restart the container (no Docker image rebuild). The generated Dockerfile is kept in the build output for production image builds.

Quick Start: One Command

The easiest way to get a Mendix app running in Docker:

# Setup, build, and start in one command
mxcli docker run -p app.mpr

# with startup confirmation (waits for "runtime successfully started")
mxcli docker run -p app.mpr --wait

# Fresh start (removes database volumes first)
mxcli docker run -p app.mpr --fresh --wait

docker run handles everything: downloads MxBuild and runtime (if not cached), initializes the Docker stack (if needed), builds the PAD package, starts the containers, and optionally waits for the runtime to report successful startup.

Creating an Empty Mendix App

Recommended: Use mxcli new to create a project with all tooling in one step:

mxcli new MyApp --version 11.8.0

This downloads MxBuild, creates a blank project, sets up AI tooling and Dev Container, and installs the correct Linux mxcli binary. Open the resulting folder in VS Code and reopen in Dev Container.

Manual approach (if you need more control):

# Download mxbuild if not already cached
mxcli setup mxbuild --version 11.6.4

# create a blank project
mkdir -p /path/to/my-app
~/.mxcli/mxbuild/{version}/modeler/mx create-project --app-name MyApp --output-dir /path/to/my-app

# Initialize AI tooling
mxcli init /path/to/my-app

The mx create-project command creates an MPR v2 project with the standard Mendix module structure. You can then use the Docker workflow to build and run it.

Caveat: Blank projects have no demo users — login will fail until you configure security via MDL or Studio Pro. See manage-security for setting up demo users.

Step-by-Step Workflow

If you prefer more control, use the individual commands:

1. Setup MxBuild and Runtime (first time only)

MxBuild and the Mendix runtime are auto-downloaded when needed, but you can pre-download:

# Download MxBuild matching the project version
mxcli setup mxbuild -p app.mpr

# Download Mendix runtime matching the project version
mxcli setup mxruntime -p app.mpr

# or specify version explicitly (without a project)
mxcli setup mxbuild --version 11.6.4
mxcli setup mxruntime --version 11.6.4

# or preview what would be downloaded
mxcli setup mxbuild -p app.mpr --dry-run
mxcli setup mxruntime -p app.mpr --dry-run

MxBuild is cached at ~/.mxcli/mxbuild/{version}/ and the runtime at ~/.mxcli/runtime/{version}/. Both are reused across builds.

Runtime-to-MxBuild Copying (PAD Build Prerequisite)

MxBuild 11.6.3+ expects runtime files (pad/, lib/, launcher/, agents/) inside its own runtime/ directory, but mxcli setup mxbuild only downloads the build tools (not the full runtime). If the PAD build fails with StudioPro.conf.hbs does not exist or ClassNotFoundException, copy the runtime directories into mxbuild:

version=11.6.4  # replace with your version

# after downloading both mxbuild and mxruntime:
cp -r ~/.mxcli/runtime/$version/runtime/pad ~/.mxcli/mxbuild/$version/runtime/pad
cp -r ~/.mxcli/runtime/$version/runtime/lib ~/.mxcli/mxbuild/$version/runtime/lib
cp -r ~/.mxcli/runtime/$version/runtime/launcher ~/.mxcli/mxbuild/$version/runtime/launcher
cp -r ~/.mxcli/runtime/$version/runtime/agents ~/.mxcli/mxbuild/$version/runtime/agents

Important: The PAD build output may only include partial runtime bundles (5 jars instead of 354). If the runtime fails to start with ClassNotFoundException: com.mendix.container.support.EventProcessor, copy the full runtime into the PAD build output:

rm -rf /path/to/project/.docker/build/lib/runtime
cp -r ~/.mxcli/runtime/$version/runtime /path/to/project/.docker/build/lib/runtime

2. Initialize Docker stack (first time only)

# generate docker-compose.yml, .env, Dockerfile in .docker/
mxcli docker init -p app.mpr

This creates a .docker/ directory with Docker Compose configuration for the Mendix app + PostgreSQL.

Port conflicts: If default ports (8080/8090/5432) are already in use, check with ss -tlnp | grep -E '808|809|543' and use --port-offset N to shift all ports:

# check which ports are occupied
ss -tlnp | grep -E '808[0-9]|809[0-9]|543[0-9]'

# use offset to avoid conflicts (e.g., offset 5 → 8085/8095/5437)
mxcli docker init -p app.mpr --port-offset 5

3. Check project for errors

# Quick validation using mx check
mxcli docker check -p app.mpr

4. Build the Portable App Distribution (PAD) package

# build (auto-downloads MxBuild if not cached)
mxcli docker build -p app.mpr

# Preview what would happen
mxcli docker build -p app.mpr --dry-run

# Skip pre-build check
mxcli docker build -p app.mpr --skip-check

This:

  1. Detects the Mendix version (requires >= 11.6.1)
  2. Locates or downloads MxBuild and JDK 21
  3. Runs MxBuild to produce a PAD package (ZIP)
  4. Extracts the PAD ZIP (cleans up old ZIPs after extraction)
  5. Generates a Dockerfile if MxBuild didn't produce one (11.6.3+)
  6. Downloads and injects the Mendix runtime if not in PAD output
  7. Applies version-aware patches (CMD fix, base image, healthcheck, etc.)

5. Start the application

# Start in foreground (see logs directly)
mxcli docker up -p app.mpr

# Start in background
mxcli docker up -p app.mpr --detach

# Start in background and wait for runtime startup confirmation
mxcli docker up -p app.mpr --detach --wait

# Fresh start (removes database volumes)
mxcli docker up -p app.mpr --fresh

# Custom wait timeout (default: 300 seconds)
mxcli docker up -p app.mpr --detach --wait --wait-timeout 600

The --wait flag (requires --detach) tails the container logs and waits until the Mendix runtime reports "Runtime successfully started" or a timeout/failure is detected.

The app is available at:

6. Query data with OQL

# run OQL queries against the live runtime (read-only preview mode)
mxcli oql -p app.mpr "select Name, Email from MyModule.Customer"

# json output for piping
mxcli oql -p app.mpr --json "SELECT count(c.ID) FROM MyModule.Order AS c" | jq '.[0]'

# Test a view entity query before embedding it in MDL
mxcli oql -p app.mpr "select datepart(YEAR, o.Date) as Year, sum(o.Total) as Revenue from Sales.Order as o GROUP by datepart(YEAR, o.Date)"

7. Monitor and manage

# view container status
mxcli docker status -p app.mpr

# view logs
mxcli docker logs -p app.mpr
mxcli docker logs -p app.mpr --follow
mxcli docker logs -p app.mpr --tail 50

# open a shell in the container
mxcli docker shell -p app.mpr
mxcli docker shell -p app.mpr --exec "ls -la /mendix"

8. Stop the application

# Stop containers
mxcli docker down -p app.mpr

# Stop and remove database volumes
mxcli docker down -p app.mpr --volumes

Common Workflow: Edit, Rebuild, Test

After making MDL changes:

# 1. apply MDL changes
mxcli exec changes.mdl -p app.mpr

# 2. Rebuild and restart (one command)
mxcli docker run -p app.mpr --fresh --wait

Or step by step:

# 1. apply MDL changes
mxcli exec changes.mdl -p app.mpr

# 2. Validate
mxcli docker check -p app.mpr

# 3. Rebuild
mxcli docker build -p app.mpr

# 4. restart with fresh database and wait for startup
mxcli docker up -p app.mpr --fresh --detach --wait

# 5. check it's running
mxcli docker status -p app.mpr

Hot Reload

The Mendix runtime supports hot reloading via the M2EE admin API. Because mxcli's Docker setup uses a bind mount (.docker/build/ → /mendix/), rebuilt PAD output is immediately visible to the running runtime — no Docker image rebuild or container restart needed.

How It Works

  1. mxcli docker build compiles the project into .docker/build/ (the bind-mounted directory)
  2. mxcli docker reload rebuilds the PAD (same as docker build) then calls the M2EE reload_model action on port 8090
  3. The runtime re-reads the model from disk in ~100ms
  4. Connected browsers auto-refresh via the /mxdevtools/ WebSocket

Note: By default, docker reload includes a full build step (~55s). Use --model-only to skip the build when you've already run docker build separately:

Typical hot reload cycle (with separate build):

mxcli exec script.mdl -p app.mpr                   # ~1s   — update model
mxcli docker build -p app.mpr                       # ~55s  — compile PAD
mxcli docker reload -p app.mpr --model-only         # ~100ms — reload only
# Total: ~56s (vs ~75s with full container restart)

One-step reload (build + reload combined):

mxcli exec script.mdl -p app.mpr                    # ~1s   — update model
mxcli docker reload -p app.mpr                      # ~56s  — build + reload

Model Reload

For logic changes (microflows, nanoflows, pages, security):

# 1. apply changes
mxcli exec changes.mdl -p app.mpr

# 2. Rebuild PAD (skip pre-check for speed)
mxcli docker build -p app.mpr --skip-check

# 3. Hot reload the runtime (--model-only skips redundant rebuild)
mxcli docker reload -p app.mpr --model-only

Or combine build + reload in one command (no --model-only):

# 1. apply changes
mxcli exec changes.mdl -p app.mpr

# 2. build and reload in one step
mxcli docker reload -p app.mpr --skip-check

The --direct flag connects to the admin API via HTTP instead of routing through docker compose exec (faster, requires the admin.addresses = ["*"] build patch which is applied automatically):

mxcli docker reload -p app.mpr --direct

CSS-Only Reload

For theme/styling changes, SCSS must first be compiled by MxBuild into the PAD output. The --css flag skips the model reload step — it only pushes already-compiled CSS to browsers via WebSocket.

# Correct workflow for SCSS/theme changes:
mxcli docker build -p app.mpr            # compile SCSS into PAD (~55s)
mxcli docker reload -p app.mpr --css     # push compiled CSS to browsers (~instant)

Note: --css does NOT compile SCSS. If you skip the build step, the browser will not reflect your SCSS changes. The "instant" refers to the browser update via WebSocket, not end-to-end time.

This calls the M2EE update_styling action, which pushes CSS changes to all connected browsers via the /mxdevtools/ WebSocket. Browsers update their stylesheets without a full page reload.

The mxdevtools WebSocket

The Mendix runtime exposes a WebSocket at ws://localhost:8080/mxdevtools/ (on the app port). This is a server-push-only channel — the runtime pushes instructions to connected browsers:

InstructionEffect
set_deployment_idIf the ID changes, triggers full browser reload
reloadForces full browser reload
update_stylingHot-reloads CSS without page reload

When reload_model is called, a new set_deployment_id is pushed to browsers, triggering automatic refresh. This is why you don't need to manually refresh the browser after docker reload.

When to Use reload vs run

ScenarioCommandWhy
Microflow/nanoflow logicdocker reloadNo schema change, keeps data
Page layout or widget changesdocker reloadRuntime reloads pages from model
CSS/theme changes onlydocker reload --cssInstant, no MxBuild needed
Security rule changesdocker reloadRuntime reloads security config
New entity or attribute (additive)docker reloadRuntime applies DDL on reload
Destructive schema change (drop column, type change)docker up --freshRuntime can't apply destructive DDL
First-time setupdocker runNeed containers + database
Database corruption or resetdocker run --freshRecreates volumes

When Reload is NOT Sufficient

reload_model cannot handle destructive database schema changes. Signs you need a full restart with --fresh:

  • Removed an entity — the runtime won't drop the table
  • Removed an attribute — the runtime won't drop the column
  • Changed an attribute type (e.g., String → Integer) — the runtime can't ALTER COLUMN type
  • Runtime logs show DDL errors — look for "Could not execute DDL" or schema mismatch messages

In these cases:

mxcli docker up -p app.mpr --fresh --detach --wait

Environment Variables

The docker-compose.yml generated by mxcli docker init sets these environment variables for the Mendix container:

VariableDefaultDescription
ADMIN_ADMINPASSWORDAdminPassword1!Admin console password (read by runtime)
RUNTIME_DEBUGGER_PASSWORDAdminPassword1!Debugger password (required in non-Development mode)
RUNTIME_PARAMS_DATABASETYPEPOSTGRESQLDatabase type
RUNTIME_PARAMS_DATABASEHOSTdb:5432Hostname and port of the db service
RUNTIME_PARAMS_DATABASENAMEmendixDatabase name
RUNTIME_PARAMS_DATABASEUSERNAMEmendixDatabase user
RUNTIME_PARAMS_DATABASEPASSWORDmendixDatabase password
MX_LOG_LEVELinfoLog level

All defaults can be overridden in .docker/.env.

Note: The runtime reads ADMIN_ADMINPASSWORD, not M2EE_ADMIN_PASS. The .env file defines M2EE_ADMIN_PASS as the single source, and docker-compose.yml maps it to both ADMIN_ADMINPASSWORD and RUNTIME_DEBUGGER_PASSWORD.

Troubleshooting

ProblemSolution
docker: command not foundRebuild devcontainer — docker-in-docker feature needs rebuild to activate. Or use Podman: export MXCLI_CONTAINER_CLI=podman
mxbuild not foundRun mxcli setup mxbuild -p app.mpr to download from CDN
JDK 21 not foundRebuild devcontainer — JDK 21 should be pre-installed
Build fails with version errorRequires Mendix >= 11.6.1 for PAD support
No Dockerfile in PAD outputNormal for MxBuild 11.6.3+ — mxcli docker build auto-generates one
Runtime not found / runtimelauncher.jar missingRun mxcli setup mxruntime -p app.mpr or let docker build auto-download
StudioPro.conf.hbs does not existRuntime not linked into mxbuild — see "Runtime-to-MxBuild Copying" above
ClassNotFoundException: EventProcessorPAD has partial runtime bundles — copy full runtime into .docker/build/lib/runtime/ (see above)
Port already allocatedCheck ports with ss -tlnp | grep 808 and use docker init --port-offset N --force
' etc/default' is not a fileDockerfile CMD passes config arg — docker build patches this automatically
DatabasePassword has no valueEnsure RUNTIME_PARAMS_DATABASE* env vars are in docker-compose.yml — re-run mxcli docker init --force
password should not be empty (debugger)Add RUNTIME_DEBUGGER_PASSWORD — re-run mxcli docker init --force
security level should be set to CHECKEVERYTHINGApp in Production mode without security — set to Development mode or configure security
Port 8080 not accessibleCheck forwardPorts in devcontainer.json includes 8080
Database errors on startupTry mxcli docker up -p app.mpr --fresh to reset volumes
OQL: "Action not found: preview_execute_oql"Runtime needs -Dmendix.live-preview=enabled JVM flag — re-run mxcli docker init --force to get the updated docker-compose.yml

Signals

GitHub stars
122
Forks
49
Last commit
Sep 2026

ahel review

  • S4info
    community integration, published by mendixlabs, not docker

Automated review, not a security audit. Ruleset v1.

Advanced
Catalog kind
skill
Gateway key
docker-workflow
Source
github.com/mendixlabs/mxcli
Docker Build & Run Skill (docker-workflow): Skill · ahel