You will probably be able to run the Linux version of Mplus on a Mac when macOS 28 drops: Setup and Testing

Author

Rich Jones with Claude 4.6

Published

Invalid Date

Mplus in a Container on macOS 28

This guide has three aims. First, it sets up the Linux version of Mplus inside a Docker container on an Apple Silicon Mac. Second, it tests that container without Rosetta emulation to approximate post-macOS 28 behavior. Third, it installs a wrapper script that keeps the container compatible with an MplusAutomation workflow in which .inp files contain absolute macOS paths that a naive Docker mount cannot resolve.

This guide was developed by Claude, and tested by me. I can confirm the steps through part 7 work, the speed tests are what I get on a M3 MacBook Air with 16GM RAM Tahoe 26.5.1.

This matters because with the transition to Apple Silicon (an ARM — Advanced RISC Machine — architecture chip), programs compiled only for Intel x86-64 will not run natively. Apple bridged this gap with a software workaround called Rosetta 2, but will remove general support for it with macOS 28, expected in late 2027. Mplus is affected: it ships only as an x86-64 binary, and Muthén & Muthén have stated on their website that there are no plans to release an ARM version. Once Rosetta support ends, Mac users — including students who arrive at workshops on an Apple Silicon Mac — need another path. This guide tests one: running the x86-64 Linux version of Mplus inside a Docker container, where QEMU would provide the x86-64 emulation layer if Rosetta for Linux is also removed — though whether that happens with macOS 28 remains uncertain. The central question is whether this solution works at all, and what it costs in speed.

1 Glossary

Apple Silicon
Apple’s family of ARM-based processors (M1, M2, M3, and later). They use a different instruction set than the Intel chips in older Macs, so software compiled only for Intel cannot run on them directly. Mplus has not been compiled for Apple Silicon.
Intel x86-64
The processor architecture used by older Macs and most Windows PCs. Mplus is distributed only as an x86-64 binary. This mismatch between what Mplus requires and what Apple Silicon provides is the core problem this guide addresses.
macOS
Apple’s operating system for Mac computers. Current versions include Rosetta 2, which allows x86-64 software to run on Apple Silicon. macOS 28, expected in late 2027, will remove that compatibility layer.
Rosetta 2
A translation layer built into macOS that converts x86-64 instructions into ARM instructions on the fly, allowing older software to run on Apple Silicon. It is the reason Mplus currently works on M-series Macs, and its removal in macOS 28 is what this guide prepares for.
Linux
An open-source operating system used widely on servers and research computing systems. Mplus is available as a native x86-64 Linux binary — separate from the macOS version. This guide runs that Linux version inside a Docker container on your Mac.
Docker
Software that packages an application and its dependencies into a portable, self-contained environment called a container. In this guide, Docker creates a small Linux environment on your Mac in which the x86-64 Linux version of Mplus runs. Everything runs locally on your own machine — no data leaves your computer, and no cloud or remote server is involved. You interact with it from the terminal as you would with a locally installed program.
Rosetta emulation (Docker)
A feature of Docker Desktop on Apple Silicon that uses Apple’s translation technology to run x86-64 Linux containers efficiently. This is distinct from Rosetta 2, which translates macOS apps — they are separate features. When macOS 28 removes general Rosetta support, Docker may lose this option and fall back to QEMU (Quick Emulator). Whether Rosetta for Linux — a technically distinct feature of Apple’s Virtualization framework — survives macOS 28 is uncertain; Apple has not addressed this directly.
QEMU
Quick Emulator. An open-source emulator that simulates an x86-64 processor in software on an ARM chip. Despite the name, “Quick” here is relative — QEMU is slower than Rosetta emulation because it performs translation without hardware assistance. After macOS 28, QEMU will be the fallback for running x86-64 Docker containers on Apple Silicon if Rosetta for Linux is also not available (which is uncertain at this time). This guide measures the resulting speed penalty.

2 What you need before starting

3 Part 1: Install Docker Desktop

If Docker Desktop is not already installed:

  1. Go to docker.com/products/docker-desktop and download the Apple Silicon version.
  2. Install and open Docker Desktop. Wait for startup to finish (the whale icon in the menu bar stops animating).
  3. Go to Settings -> General. Find the checkbox labeled “Use Rosetta for x86/amd64 emulation on Apple Silicon”. Leave it checked for now. You will uncheck it later for the primary test.
  4. Click Apply & Restart if you changed any settings.

4 Part 2: Set up your project directory

Open a terminal and run:

mkdir -p ~/mplus-docker/data
cd ~/mplus-docker

Move install_mpluslinux_demo64.bin into ~/mplus-docker/, e.g.,

cp ~/Downloads/install_mpluslinux_demo64.bin ~/mplus-docker/

5 Part 3: Write the Dockerfile

Create an empty file named Dockerfile in ~/mplus-docker/:

touch ~/mplus-docker/Dockerfile

Open it in nano:

nano ~/mplus-docker/Dockerfile

nano opens in your terminal window. The file is empty and the cursor is at the top. Copy the Dockerfile contents below, then paste into the nano window with Cmd+V. The text should appear exactly as shown. Do not add blank lines before the first FROM line.

Save the file: press Ctrl+O, then press Return to confirm the filename.

Exit nano: press Ctrl+X.

The Dockerfile contents to paste:

FROM --platform=linux/amd64 ubuntu:22.04

# Install the runtime libraries Mplus requires
RUN apt-get update && apt-get install -y \
    libstdc++6 \
    libgcc-s1 \
    && rm -rf /var/lib/apt/lists/*

# Copy the installer and run it silently using the default install path (/opt/mplusdemo)
COPY install_mpluslinux_demo64.bin /tmp/
RUN bash /tmp/install_mpluslinux_demo64.bin -i silent \
    && rm /tmp/install_mpluslinux_demo64.bin

# Working directory inside the container
WORKDIR /data

ENTRYPOINT ["/opt/mplusdemo/mpdemo"]

After exiting, you can check the contents with:

cat ~/mplus-docker/Dockerfile

The installer runs during docker build and places the Mplus binary at /opt/mplusdemo/mpdemo inside the image. The -i silent flag accepts defaults without prompts. The installer is removed afterward to reduce image size.

The --platform=linux/amd64 line declares an x86-64 image. On Apple Silicon, Docker emulates x86-64 to run it, using Rosetta or QEMU depending on your Docker Desktop settings. This makes it possible to test both scenarios.

Do not run the installer directly on macOS Apple Silicon. The installer bundles an x86-64 JRE (Java Runtime Environment) to drive setup, and that JRE is incompatible with Apple Silicon even under Rosetta 2. You will get this error and the installer will exit without producing an installation:

Preparing to install
Extracting the JRE from the installer archive...
Unpacking the JRE...
Extracting the installation resources from the installer archive...
Configuring the installer for this system's environment...

Launching installer...

JRE libraries are missing or not compatible....
Exiting....

The Dockerfile avoids this failure by running the installer inside Docker, where the bundled JRE runs on x86-64 Linux.

6 Part 4: Create test files

Two test files are required. The first is a connectivity check that confirms Mplus runs inside the container. The second is a stress test used to quantify the performance difference between Rosetta emulation and QEMU.

6.1 Connectivity test

Generate the data in R:

set.seed(42)
n <- 100
f <- rnorm(n)
dat <- data.frame(
  y1 = f + rnorm(n, sd = 0.5),
  y2 = 0.80 * f + rnorm(n, sd = 0.5),
  y3 = 0.90 * f + rnorm(n, sd = 0.5),
  y4 = 0.70 * f + rnorm(n, sd = 0.5),
  y5 = 0.85 * f + rnorm(n, sd = 0.5),
  y6 = 0.75 * f + rnorm(n, sd = 0.5)
)
write.table(
  dat,
  file = "~/mplus-docker/data/test.dat",
  sep = " ",
  row.names = FALSE,
  col.names = FALSE
)

Create ~/mplus-docker/data/test.inp:

touch ~/mplus-docker/data/test.inp
nano ~/mplus-docker/data/test.inp

Copy and paste the contents below, then save with Ctrl+O + Return and exit with Ctrl+X.

TITLE: Docker test - single-factor CFA;

DATA:
  FILE IS test.dat;

VARIABLE:
  NAMES ARE y1 y2 y3 y4 y5 y6;
  USEVARIABLES ARE y1-y6;

MODEL:
  f BY y1-y6;

OUTPUT:
  TECH1 SAMPSTAT;

6.2 Stress test

The connectivity test completes in under one second on most hardware and cannot resolve performance differences. The stress test uses a two-factor MLR (Maximum Likelihood Robust)/PROBIT CFA (Confirmatory Factor Analysis) with categorical indicators and 2-dimensional Gauss-Hermite numerical integration. This represents a model class that is most sensitive to QEMU overhead, specifically MLR/PROBIT estimation with multiple continuous latent variables.

Generate the data in R:

set.seed(42)
n <- 5000
f1 <- rnorm(n)
f2 <- 0.5 * f1 + sqrt(1 - 0.5^2) * rnorm(n)  # correlated r ~ 0.5 with f1
dat <- data.frame(
  u1 = rbinom(n, 1, pnorm(0.8 * f1 - 0.5)),
  u2 = rbinom(n, 1, pnorm(0.7 * f1)),
  u3 = rbinom(n, 1, pnorm(0.9 * f1 + 0.3)),
  u4 = rbinom(n, 1, pnorm(0.8 * f2 - 0.3)),
  u5 = rbinom(n, 1, pnorm(0.7 * f2 + 0.1)),
  u6 = rbinom(n, 1, pnorm(0.6 * f2 - 0.2))
)
write.table(dat, "~/mplus-docker/data/stress2.dat",
            sep = " ", row.names = FALSE, col.names = FALSE)

Create ~/mplus-docker/data/stress2.inp:

TITLE: Docker stress test - 2-factor MLR/PROBIT with 2D integration;

DATA:
  FILE IS stress2.dat;

VARIABLE:
  NAMES ARE u1 u2 u3 u4 u5 u6;
  USEVARIABLES ARE u1-u6;
  CATEGORICAL ARE u1-u6;

ANALYSIS:
  ESTIMATOR = MLR;
  LINK = PROBIT;
  INTEGRATION = 15;
  STARTS = 20 10;
  PROCESSORS = 8;

MODEL:
  f1 BY u1-u3;
  f2 BY u4-u6;
  f1 WITH f2;

OUTPUT:
  TECH8;

INTEGRATION = 15 sets 15 Gauss-Hermite quadrature points per dimension, which yields 225 evaluation points per E-step in this 2D model. PROCESSORS = 8 distributes random starts and integration across cores. Set this equal to the CPUs available to Docker in Docker Desktop -> Settings -> Resources -> CPUs. The timing tests in Parts 6 and 7 run this file as written, then with PROCESSORS = 1, to estimate multiprocessing gain in each condition.

7 Part 5: Build the Docker image

From ~/mplus-docker/, run:

cd ~/mplus-docker
docker build --platform linux/amd64 -t mplus-demo .

Docker downloads Ubuntu 22.04, installs required libraries, copies the installer, and runs the silent installation. The installer bundles its own JRE, so no Java install is needed. The first build generally takes 3-5 minutes because of installer size. Later builds are faster due to layer caching.

A successful build ends with a line like:

Successfully tagged mplus-demo:latest

8 Part 6: Test with Rosetta Emulation (best case)

First confirm Rosetta emulation is enabled: Docker Desktop -> Settings -> General -> “Use Rosetta for x86/amd64 emulation” should be checked.

Run the connectivity test to confirm Mplus runs:

docker run --rm \
  --platform linux/amd64 \
  -v ~/mplus-docker/data:/data \
  mplus-demo test.inp

Check ~/mplus-docker/data/test.out for standard Mplus output with factor loadings and fit statistics. If output is clean, proceed to the stress test.

Run the stress test with timing:

time docker run --rm \
  --platform linux/amd64 \
  -v ~/mplus-docker/data:/data \
  mplus-demo stress2.inp

Flag definitions:

  • --rm removes the container after completion.
  • --platform linux/amd64 explicitly requests x86-64 emulation.
  • -v ~/mplus-docker/data:/data mounts the local data directory so Mplus can read inputs and write outputs.

The timing line appears at the bottom of terminal output after Mplus exits. In zsh it has this form:

docker run ...  X.XXs user Y.YYs system Z% cpu W.WW total

The number before total is elapsed wall-clock time. Record it.

9 Part 7: Test without Rosetta Emulation (worst-case test)

This step tests the worst-case scenario for Apple Silicon users after macOS 28. By disabling Docker’s Rosetta setting, we force Docker to fall back to QEMU, an open-source emulation layer that does not depend on Rosetta.

Whether this worst case actually arrives is uncertain. Apple’s deprecation announcement targets Rosetta 2 as a general-purpose tool for macOS apps. Docker’s emulation relies on a technically distinct feature — Rosetta for Linux — built into Apple’s Virtualization framework. Apple has not stated whether this separate feature will survive macOS 28. The question has been raised explicitly in Apple’s own open-source container project and remains unanswered: Will Rosetta stay for Linux?. It is possible that Docker’s Rosetta option continues to work even after macOS 28, in which case the speed penalty measured here never materializes. Together, Parts 6 and 7 bound the range of outcomes. If Rosetta for Linux is preserved in macOS 28, students will see performance close to the Part 6 results. If it is removed, they will see the results below.

  1. Go to Docker Desktop -> Settings -> General.
  2. Uncheck “Use Rosetta for x86/amd64 emulation on Apple Silicon”.
  3. Click Apply & Restart and wait for Docker to restart.

Delete the previous stress output to verify a fresh run:

rm ~/mplus-docker/data/stress2.out

Run the same stress test:

time docker run --rm \
  --platform linux/amd64 \
  -v ~/mplus-docker/data:/data \
  mplus-demo stress2.inp

9.1 Results

Condition 1 processor 8 processors
Rosetta emulation enabled ~17 seconds ~10 seconds
QEMU (Rosetta disabled) ~156 seconds ~59 seconds

Multiprocessing helps more under QEMU (2.6x speedup) than under Rosetta (1.7x speedup). Under Rosetta, models complete quickly enough that parallel overhead limits gain. Under QEMU, each unit of work is slower because emulation overhead is larger, so distribution across cores yields greater benefit. With 8 processors, the QEMU penalty drops from roughly 9x to roughly 6x compared with single-processor Rosetta.

10 Part 9: Re-enable Rosetta Emulation

After testing, return to Docker Desktop -> Settings -> General, re-check “Use Rosetta for x86/amd64 emulation”, then click Apply & Restart.

11 Interpreting the results

Both conditions complete successfully across processor configurations. The x86-64 Linux Mplus binary runs correctly in Docker with and without Rosetta emulation. The performance penalty varies by model type and processor count.

For models without numerical integration, such as WLSMV (Weighted Least Squares Mean and Variance adjusted) CFA, standard LCA (Latent Class Analysis), and basic path models, QEMU imposes roughly a 3x slowdown regardless of processor count. A job that takes 1 second takes about 3 seconds. In many workflows this is negligible.

For models with multi-dimensional numerical integration, specifically MLR/PROBIT with multiple continuous latent variables, the single-processor QEMU penalty is roughly 9x. Adding processors narrows this gap. With 8 processors, QEMU takes roughly 6x longer than single-processor Rosetta and roughly 3.5x longer than 8-processor Rosetta. A job that takes 10 seconds under Rosetta with 8 processors takes about 59 seconds under QEMU with 8 processors. A job that takes 5 minutes under Rosetta takes about 30 minutes under QEMU.

The practical recommendation is to set PROCESSORS equal to the CPUs assigned to Docker (check Docker Desktop -> Settings -> Resources -> CPUs) in either mode. The benefit is largest where it is most needed — under QEMU after macOS 28.

The only way to remove this penalty entirely is a native ARM64 Linux Mplus binary, which would run in Docker on Apple Silicon without emulation.

Deferred test note: I am not running Part 8 until macOS 28 drops, expected in late 2027. See Epilogue for context on what comes next.

12 Part 8: MplusAutomation wrapper script (production use)

Parts 7 and 8 test Docker and Mplus connectivity using a hand-written .inp with a relative data path. The production workflow differs. MplusAutomation-generated .inp files typically include absolute macOS paths in the DATA: FILE = line. For example:

FILE = "/Users/rnj/Library/CloudStorage/Dropbox/Work/CREATES/R/MPLUS_OUTPUT/
030-GCP-Scale-H/gcp_scale_h_wlsmv_169b37b23e24fb4382cc68395e764783.dat";

A container that mounts only /data/ cannot resolve this path. The fix is a wrapper script that mounts the current working directory at the same absolute path inside the container, so the path Mplus expects exists in-container.

12.1 Create the wrapper script

mkdir -p ~/bin

Create ~/bin/mplus with this content:

#!/bin/bash
# Wrapper: runs Linux Mplus in Docker, preserving absolute paths.
# MplusAutomation setwd()s to the .inp file directory before calling this
# script. Mounting $(pwd) at $(pwd) ensures the absolute DATA: FILE = path
# in the generated .inp resolves correctly inside the container.
docker run --rm \
  --platform linux/amd64 \
  -v "$(pwd):$(pwd)" \
  --workdir "$(pwd)" \
  mplus-demo "$@"

Make it executable:

chmod +x ~/bin/mplus

Add ~/bin to your PATH if needed. In ~/.zshrc:

export PATH="$HOME/bin:$PATH"

Reload:

source ~/.zshrc

Verify wrapper discovery:

which mplus
# should print: /Users/rnj/bin/mplus

12.2 How MplusAutomation finds the wrapper

MplusAutomation looks for a binary named mplus on PATH. Because the wrapper is named mplus and is on PATH, no R code changes are required. Existing runModels() calls continue to work. MplusAutomation calls setwd() to the .inp directory, then executes mplus filename.inp; the wrapper intercepts that call, and Docker runs /opt/mplusdemo/mpdemo with that directory mounted at the same absolute path. Output files, factor-score .dat files, and .h5 results are written where the existing R workflow expects them.

12.3 Test with a real analysis file

Navigate to an MPLUS_OUTPUT directory with an existing .inp and .dat, then run Mplus through the wrapper:

cd "/Users/rnj/Library/CloudStorage/Dropbox/Work/CREATES/R/MPLUS_OUTPUT/030-GCP-Scale-H"
mplus gcp_scale_h_mlr.inp

Check the resulting .out for standard Mplus output without path errors. If the model previously ran on macOS, output should match.

12.4 Repeat the QEMU test with the wrapper

To verify wrapper behavior without Rosetta, disable Rosetta emulation in Docker Desktop (Part 7, step 1), delete the .out file, and rerun the same command above. This is the direct production-workflow test for post-macOS 28 conditions.

13 Epilogue: next steps toward native Linux

The Docker container is a practical bridge. For models without numerical integration, the QEMU penalty is roughly 3x — often negligible for short runs. For MLR/PROBIT models with multiple continuous latent variables, the penalty is roughly 9x. A run that takes 17 seconds under Rosetta takes about 156 seconds under QEMU. That remains workable for many workflows, but adds up in iterative or multi-model pipelines.

For users whose work is dominated by computationally demanding models, running Mplus natively on Linux is the cleaner long-term solution: no emulation, no overhead, full performance. I am strongly considering making that move myself. For now, the Docker approach provides a viable path for Mac users through the macOS 28 transition and beyond.

One option that avoids a full migration is a dedicated Linux mini-PC that runs Mplus natively and receives jobs from your Mac over a direct network connection. Machines like the Beelink SER8 Pro ship with Ubuntu installed, run native x86-64 Linux, and cost around $400. You connect the mini-PC to your Mac with a standard Ethernet cable and a USB-C to Ethernet adapter, then access it via SSH (Secure Shell), a standard protocol for running commands on a remote computer. MplusAutomation submits input files and retrieves results over that connection, and Mplus runs on native Linux with no emulation layer. The 9x penalty that integration-heavy models incur under QEMU disappears. Your Mac stays on macOS; only where the computation runs changes.