This is the end-user install guide for the Open Digital Product Factory on Apple Silicon Macs (M1 or later).
Status: GA — validated on real Apple Silicon hardware.
The macOS installer has been run end-to-end on a real Apple Silicon Mac (M-series, macOS 14+) and troubleshot to a working portal, including the voice (STT + native-host TTS) path. The findings from that validation are folded into the steps and the Troubleshooting section below.
CI still only exercises the
--dry-runpath (themacos-14GHA runner can’t nest-virtualize Docker Desktop), so additional verification reports on other Mac models / macOS versions remain welcome — see Verify your install. Both Windows and macOS Apple Silicon are GA install surfaces today.
For the architectural background, see the installer-parity roadmap and the deployment doctrine.
Supported environment
| Component | Required |
|---|---|
| OS | macOS 14 (Sonoma) or newer |
| Architecture | Apple Silicon (arm64). Intel Macs are not supported. |
| Disk | ~10 GB free for the application images; buy at least 1 TB, or 2 TB when local models will run on the host. |
| RAM | 32 GB recommended with an external AI provider; 64 GB practical for local-first use; choose 128 GB for larger models or combined operations and development. |
The installer refuses to run on unsupported hosts (Intel Mac, older
macOS) unless you pass --force-unsupported-host — see
Preflight refusals.
Recommended hardware
Choose the profile that matches where primary AI inference will run:
| Deployment model | Recommended Apple Silicon configuration | Best fit |
|---|---|---|
| Provider-assisted operations | 32 GB unified memory, 1 TB SSD | Routine DPF operations using approved external AI providers |
| Local-first operations | 64 GB unified memory practical; 128 GB for larger models or combined operations and development; 2 TB SSD | Local tool-using coworkers, larger models, and longer context |
| Contributor/development workstation | 128 GB unified memory, 4 TB SSD | Operations plus source work, builds, tests, browser automation, and local model evaluation |
Apple Silicon lets the CPU and GPU share one memory pool. DPF’s planning rule reserves 25% for macOS and the application stack, leaving about 48 GB on a 64 GB Mac and 96 GB on a 128 GB Mac for local AI workloads. Memory cannot be upgraded after purchase, so buy 128 GB when the Mac must handle larger local models or serve as both the DPF host and a development workstation.
See Choosing Hardware for DPF for current Mac and Windows machine examples, NVIDIA speed tradeoffs, model sizing, and purchase checks.
For a compact business host, consider a Mac mini with M6, 32 GB unified memory and a 1 TB SSD for provider-assisted operations, or a Mac mini with M5 Pro, 64 GB unified memory and a 2 TB SSD for local-first operations. These are planning configurations, not exact-model certifications; validate the actual business workload and any enabled build workload before purchase. Embedded Build Studio supports governed changes on customizable installs without separate Claude or Codex apps, but builds, tests, previews, and local coding models need additional capacity wherever they execute.
For larger local models or document-heavy professional work, including law-firm workloads, evaluate a Mac Studio with M5 Max, 128 GB unified memory and a 2–4 TB SSD. Apple offers 128 GB with the 40-core GPU configuration. Choose an M5 Ultra only when measured model capacity or throughput needs justify it. See the hardware guide for sizing and data-handling checks.
Prerequisites
Docker memory during installation
The installer sizes local Docker Desktop memory before dependency and image work: one quarter of physical RAM rounded down to a GiB, with an 8 GiB floor and 32 GiB cap. For example, a 64 GiB Mac gets a 16 GiB upper allocation and a 128 GiB Mac gets 32 GiB. A saved allocation already between 8 GiB and that upper allocation is preserved. Hosts below 16 GiB are refused. Native AI models share the Mac’s memory and need their own capacity allowance.
Changing the allocation requires a Docker Desktop restart, which interrupts
all Docker containers and host model runner work. The installer asks permission
when containers are running or Docker AI is enabled. Unattended installation
requires --allow-docker-restart for that interruption; --headless alone
uses the default answer of no. Empty setup with Docker AI disabled converges
automatically. Previously running containers are restored, including containers
whose restart policy is no.
The installer checks both the saved limit and the running VM, so a saved change that has not taken effect also requires restart. It backs up Docker’s settings before replacing the allocation and reports the backup path. Malformed, symlinked, missing or administrator-managed settings stop setup before shutdown. Remote engines and non-Desktop runtimes are untouched; dry-run only describes the plan. The Docker settings documentation describes the settings file and VM memory limit.
This applies to new installations and explicit installer re-runs using the updated source. It does not silently resize existing installations during normal portal upgrades. Restarting Docker normally keeps its saved allocation.
The installer auto-installs Docker Desktop. You bring:
- Xcode Command Line Tools —
xcode-select --install(forgit,make,curl). - Node.js 20+ and pnpm — install via
brew install node pnpm,nvm, or your preferred manager. The installer refuses to run with older Node or no pnpm; it does not auto-install Node-runtime tooling (out of scope per Contract 3).
That’s it. No Homebrew dependency for Docker Desktop itself — the
installer downloads the official .dmg directly from desktop.docker.com.
Quick start
Clone the repo and run the installer:
git clone https://github.com/OpenDigitalProductFactory/opendigitalproductfactory ~/dpf
cd ~/dpf
bash install-dpf.sh
The installer is interactive by default and first asks how you want to use DPF:
[1] Ready to go(customer) — runs the pre-built release images. No contributor tooling. This is the default.[2] Customizable(contributor) — builds the full stack from your local source, enables the in-repo git hooks, and runs the agent-toolchain bootstrap so Claude Code / Codex are wired up.
Office document conversion works in both modes with no extra step. A
Customizable install does not build the converter image: the portal pins
the dpf-doctools image published for the release your clone descends
from, by digest, and pins it again after every upgrade. Only when no
published image can be reached (offline or air-gapped) does it build
Dockerfile.doctools from your clone instead.
Your choice is saved to ~/.dpf/install-state.json (installMode) and reused
on re-runs without re-prompting. To skip the prompt, pass --customer or
--contributor. For an unattended (CI / scripted) install:
# Customer (release images) is the headless default:
bash install-dpf.sh --headless
# Or pick explicitly:
bash install-dpf.sh --headless --contributor
--customer/--contributor set the compose mode automatically (release vs
source build); --release/--dev still override it if you need to force one.
Contributor: separate dev workspace from install (recommended, BI-0856A4CE Phase 1)
Contributor installs default to single-tree mode — the cloned repo at the
install path doubles as your dev workspace where git worktree add creates
feature branches. That works, but the dev tree can collide with the running
portal and the self-upgrade merge loop (BI-A8A7CCFD).
Pass --dev-workspace-path to register a separate clone as the dev
workspace. The dev-loop scripts (scripts/new-dev-worktree.sh) then branch
new worktrees from THERE instead of the install path, so the production
install is left alone:
# 1. Install (production tree at $REPO_ROOT — the cwd):
bash install-dpf.sh --headless --contributor --dev-workspace-path ~/dpf-dev
# 2. Clone the dev workspace separately (one-time):
git clone git@github.com:OpenDigitalProductFactory/opendigitalproductfactory.git ~/dpf-dev
# 3. From now on, run dev-loop scripts from the dev workspace:
cd ~/dpf-dev
bash scripts/new-dev-worktree.sh my-feature
# → creates ~/dpf-dev-worktrees/my-feature
When --dev-workspace-path is omitted (or resolves to the install path),
single-tree mode persists — current behavior, full back-compat.
What the installer does
- Preflight — refuses to run on Intel Macs / older macOS / WSL2 / rootless Docker / Podman.
~/.dpf/install-state.json— initializes or migrates the install state file (schema-versioned).- Install mode — prompts for customer vs contributor (or honors
--customer/--contributor), saves it toinstall-state.json, and derives the compose mode (customer → release images; contributor → source build) unless--release/--devforces one. - Compose chain — assembles
docker-compose.yml+docker-compose.macos.yml(+docker-compose.release.ymlin release/customer mode). The Edge Node is opt-in: pass--with-edgeto also bundle a localdocker-compose.edge.ymlnode. By default no Edge Node is installed — map a network from another machine instead via Admin > Platform Development > Edge Nodes. - Docker Desktop — installs the
.dmgif missing (hdiutil attach+cp -R /Applications), then starts it and waits for the daemon. - Node / pnpm sanity check — refuses if Node < 20 or pnpm missing.
- Workspace dependencies —
pnpm install. In contributor mode the installer also enables the in-repo.githooks/(git config core.hooksPath .githooks), auto-installs the GitHub CLI (gh) into~/.dpf/tools/bin(no Homebrew/sudo — downloaded from the official release and checksum-verified) and adds it to yourPATH, then runs the agent-toolchain bootstrap so Claude Code / Codex are wired up. It does not sign you in — finish withgh auth login --git-protocol https --web(the OAuth flow, which avoids the fine-grained-PAT lifetime limits some orgs enforce) andgh auth setup-git. Customer mode skips all of this. - Host hardware profile — runs
scripts/detect-hardware-host.tsand emitsDPF_HOST_PROFILE(Apple Silicon reportsarchitecture: "unified"for memory). Model selection reads the sharedscripts/installer/local-model-policy.jsonpolicy. A 32 GB unified Mac selects the curatedai/qwen3-codertier; fresh installs do not auto-provision mutable third-party model references. .envgeneration — only on first install; existing.envis preserved. A re-run only adds generated secrets the file is missing:DPF_GIT_WEBHOOK_SECRET, the GPP permit signing keyDPF_GPP_PERMIT_SECRET, the reach-link and delegation-receipt signing keysDPF_ATTENTION_REACH_SECRETandDPF_DELEGATION_RECEIPT_SECRET, the self-upgrade target-binding and delivery-task cursor signing keysDPF_SELF_UPGRADE_TARGET_BINDING_SECRETandDPF_DELIVERY_TASK_CURSOR_SECRET(soAUTH_SECRETno longer signs any of them; anything signed withAUTH_SECRETbefore the upgrade keeps verifying until 9 November 2026), and the InngestINNGEST_SIGNING_KEYandINNGEST_EVENT_KEY. A real value already set is never rotated. The installer also replaces an Inngest key that still holds the old public default, because anyone who knows the signing key can start background jobs. A self-upgrade does the same for an install that is never re-installed.- Release image availability (customer mode only) — probes the GHCR
portal image and, if it’s gated behind early-access auth, points you at
docker login ghcr.iobefore bring-up. - HTTPS at the canonical address — the install becomes its own
certificate authority and serves the portal over https at one address:
https://localhostwhen it serves only this machine, or the machine’s DNS name when the network resolves it here. It writesPUBLIC_URLand trusts the certificate for your user (macOS asks for your keychain password once). AI clients such as Claude Code sign in with OAuth only over https. It also saves the AI client address (DPF_MCP_URL) and the certificate bundle (NODE_EXTRA_CA_CERTS) in~/.dpf/agent-toolchain.env, loaded by your shell profile, on every run; apps opened from the Dock see the same values until the Mac restarts. If this step fails, the portal stays athttp://localhost:3000and the installer says so. docker compose up -don the macOS overlay.- Health check — polls
http://localhost:3000/api/healthfor up to 5 minutes (configurable viaDPF_HEALTH_TIMEOUT). - Edge Node bootstrap (only with
--with-edge) — mints a single-use auto-approve token and installs the checksum-verified Go Edge Node aslocal.dpf-edge-node, a launchd-supervised host process. It enrolls as a trustednativenode and owns the Mac’s physical multicast interfaces, so nearby DPF discovery is no longer trapped inside Docker Desktop’s Linux VM. The installer uses the active private IPv4 portal address by default. Discovery works over HTTP; automatic pairing remains blocked untilDPF_LAN_AUTHORITY_URLnames a certificate-valid HTTPS origin trusted by the other installation. That certificate is necessary but not sufficient: an install pairs a peer automatically only when the chain validates against the pinned organization root, this install has completed an organization join import, both installs name the same estate, and the relationship is same-organization. Otherwise pairing proceeds through the short code both operators confirm, and the screen names the condition that could not be proved. - Voice / TTS sidecar (Apple Silicon only) — runs
scripts/tts/setup-chatterbox-tts-macos.shto provision the native-host text-to-speech sidecar (port8771) and wire itsTTS_PROVIDER/DPF_TTS_URL/DPF_TTS_REFERENCE_HOST_ROOTvalues into.env, so spoken output works out of the box. Idempotent and non-fatal (warns and continues if it fails); skipped on Linux/Windows, which use the bundleddpf-ttscontainer. See Voice (STT + TTS). - Persist state — records
lastSuccessfulInstallVersionandlastHealthCheck. - LaunchAgent — installs
~/Library/LaunchAgents/local.dpf-autostart.plistso the stack auto-starts at login (skip with--no-autostart).
Total wall time: ~10 minutes including the AI-model download (varies with model size and connection).
Login
Login credentials are written to .env in the install directory:
- Email:
admin@dpf.local - Password:
ADMIN_PASSWORDin.env(randomly generated on first install). Change it after first login.
Network exposure after install
A default install listens on this machine only. Every published port (the
portal on 3000 and 1455, postgres 5432, redis 6379, inngest 8288, the sandbox
on 3035) binds to 127.0.0.1, so nothing on your LAN can reach the portal
or its admin login. This is set by one value in the install’s .env:
DPF_HOST_BIND_ADDRESS=127.0.0.1
To serve the LAN deliberately, set it to 0.0.0.0 and run docker compose
up -d again. An install created before this key existed keeps the exposure
it already had: the upgrade writes 0.0.0.0 into its .env and says so in a
comment, so LAN access does not vanish silently. Remote access without LAN
exposure goes through PUBLIC_URL and the edge path.
Day-to-day
| Task | Command |
|---|---|
| Start the stack | bash dpf-start.sh |
| Stop the stack | bash dpf-stop.sh |
| Tail logs | docker compose -f docker-compose.yml -f docker-compose.macos.yml logs -f |
| Diagnostic bundle | bash install-dpf.sh doctor |
| Wipe + reinstall (destructive) | bash dpf-reinstall.sh |
| Tag + push a release | bash dpf-release.sh --bump minor |
| Soft uninstall (keep data) | bash uninstall-dpf.sh |
| Full uninstall (wipe data) | bash uninstall-dpf.sh --purge |
bash install-dpf.sh --help (and the other scripts) document every flag.
LLM provider
On Apple Silicon, Docker Desktop ships Docker Model Runner, which
hosts the local LLM behind an OpenAI-compatible endpoint at
http://model-runner.docker.internal/engines/v1. The installer
auto-detects this and sets DPF_LLM_PROVIDER=model-runner per the
provider contract.
After a model pull is verified, first boot discovers that model and enables
both the local provider and its routing connection automatically; no provider
toggle is required before using an AI coworker.
To use an external endpoint (Anthropic, OpenAI, hosted Ollama, etc.)
instead, set LLM_BASE_URL in .env before re-running the installer:
LLM_BASE_URL=https://api.example.com/v1
DPF_LLM_PROVIDER=external
Voice (STT + TTS)
DPF coworkers support both voice input (speech-to-text) and voice output (text-to-speech). On macOS the two halves are provisioned differently because Docker Desktop can’t reach the Apple Neural Engine.
Speech-to-text (STT) — connect a provider. DPF ships no speech
container. Voice input becomes available as soon as you configure a
provider that can transcribe, under Platform Tools → Communications.
Any provider serving an OpenAI-compatible /v1/audio/transcriptions
endpoint works, including OpenAI and Groq. If you would rather audio
never left your own machine, run your own speech server — speaches and
whisper.cpp server are both MIT-licensed — and give DPF its address under
the self-hosted speech provider. DPF stopped bundling one so that a third
party’s registry housekeeping could no longer block platform releases.
This is identical across macOS, Linux, and Windows.
Text-to-speech (TTS) — provisioned automatically on Apple Silicon.
Spoken output needs a synthesis engine with hardware acceleration.
Docker Desktop can’t expose the Mac’s Neural Engine to a container, so
on Apple Silicon the TTS engine runs as a native-host sidecar on
the Mac instead of in Docker. The docker-compose.macos.yml overlay is
already wired to talk to it (TTS_PROVIDER=mlx,
DPF_TTS_URL=http://host.docker.internal:8771).
bash install-dpf.sh provisions this sidecar for you — no manual
step. On an Apple Silicon host the installer runs
scripts/tts/setup-chatterbox-tts-macos.sh, which installs a launch
agent so the sidecar restarts at login, listens on port 8771, and
writes the matching TTS_PROVIDER / DPF_TTS_URL /
DPF_TTS_REFERENCE_HOST_ROOT values into .env. A founder seed
voice ships in the install seed (with a consent record), so spoken
output works as soon as the install finishes — no voice recording
required to get started.
Both customer (
bash install-dpf.sh) and contributor (scripts/setup.sh) installs provision the sidecar automatically on Apple Silicon; the step is idempotent and skips cleanly on re-runs. If the sidecar setup fails the install still completes — it just warns and points you at the manual command below. On Linux / Windows hosts TTS uses the bundleddpf-ttsDocker container instead and needs no host-side step.To (re-)provision by hand at any time:
bash scripts/tts/setup-chatterbox-tts-macos.sh
Once the sidecar is running, voice output features — per-profile speed / enthusiasm tuning and sentence-level streaming for low time-to-first-audio — are available in the coworker panel. Real-person voice cloning always requires an explicit consent record; voice never changes approval, confidence, or audit rules.
If the coworker transcribes but won’t speak back, the TTS sidecar isn’t running. Re-run the setup script and confirm the agent loaded:
bash scripts/tts/setup-chatterbox-tts-macos.sh
launchctl list | grep -E 'mlx-tts|chatterbox-tts'
curl -fsS http://localhost:8771/health # sidecar health
Autostart
The installer registers a LaunchAgent at:
~/Library/LaunchAgents/local.dpf-autostart.plist
It invokes a generated launch script (~/.dpf/dpf-autostart.sh) that
embeds the exact compose -f chain captured at install time, so future
overlay edits don’t silently break autostart.
To inspect or disable the agent:
launchctl list | grep dpf-autostart # is it loaded?
launchctl bootout gui/$UID/local.dpf-autostart # stop the agent
rm ~/Library/LaunchAgents/local.dpf-autostart.plist
bash uninstall-dpf.sh removes the agent and stops the stack but
preserves volumes / .env / ~/.dpf state for re-install. --purge
nukes those too.
Preflight refusals
The installer refuses to proceed on configurations that don’t match the supported matrix:
- Intel Mac. Force with
--force-unsupported-host, but Phase 0’s multi-arch GHCR images targetlinux/arm64for Apple Silicon — Intel runs under Rosetta with a hard performance hit. - macOS 13 or older. Older Docker Desktop versions miss
host-gatewayand Model Runner. - Docker Desktop < 4.40. Refused because Model Runner isn’t available; upgrade Docker Desktop first.
- Podman / rootless Docker / Colima. Not on the supported matrix;
the bind-mounts and
host-gatewaymode the platform relies on aren’t validated against these runtimes.
Troubleshooting
Portal didn’t come up after --headless install.
Run bash install-dpf.sh doctor to capture a diagnostic bundle at
~/.dpf/doctor-<timestamp>.tar.gz and check
docker compose -f docker-compose.yml -f docker-compose.macos.yml logs portal --tail 100.
Docker Desktop quarantined the LaunchAgent plist.
Strip the quarantine xattr: xattr -d com.apple.quarantine ~/Library/LaunchAgents/local.dpf-autostart.plist.
The installer attempts this automatically.
Model Runner not detected even though Docker Desktop ≥ 4.40 is
installed.
Open Docker Desktop → Settings → Features in development → enable
“Use Docker Compose v2” and “Docker Model Runner”, then re-run
bash install-dpf.sh.
Install log says “Docker Model Runner isn’t available … skipping the AI
model download”.
Expected on Docker Desktop older than 4.40 (the docker model CLI isn’t
present). The portal installs and runs normally — only the local AI model
is skipped, and the installer no longer leaks a raw docker: 'model' is not
a docker command error. Update Docker Desktop and re-run
bash install-dpf.sh to pull the model, or point the portal at an external
LLM provider under Admin → Providers.
/api/health returns 500.
The portal’s database migrations may not have completed. Tail the
portal-init container:
docker compose -f docker-compose.yml -f docker-compose.macos.yml logs portal-init.
Browser doesn’t open after bash dpf-start.sh.
Pass --no-browser (or set NO_BROWSER=1) for SSH / headless sessions.
The portal is still running at http://localhost:3000.
Uninstall
| Command | What it removes |
|---|---|
bash uninstall-dpf.sh |
LaunchAgent, running containers. Preserves volumes, .env, ~/.dpf state. Re-install resumes cleanly. |
bash uninstall-dpf.sh --purge |
Above + all DPF docker volumes (filtered by the com.docker.compose.project=dpf label so other stacks on the same host are untouched), .env, ~/.dpf. Destructive — irreversible. |
bash uninstall-dpf.sh --purge --keep-env |
Purge but retain .env. |
bash uninstall-dpf.sh --purge --keep-state |
Purge but retain ~/.dpf install history. |
Verify your install
macOS Apple Silicon is GA, but CI still can’t run the installer on real
hardware (the GHA macos-14 runner can’t nest-virtualize Docker
Desktop), so verification reports from additional Mac models / macOS
versions remain valuable for widening the tested matrix. If you ran the
install above, a quick report is appreciated — happy paths and
failures are equally useful.
One-command report (fastest path):
# Already installed — just run the verifier:
bash scripts/verify-install-edge.sh
# Fresh host — bootstrap + verify in one shot:
bash scripts/verify-install-edge.sh --bootstrap
The script captures a ~/.dpf/verify-bundle-<timestamp>.tar.gz with a
host fingerprint, portal health, Prometheus targets, Edge Node lifecycle
log, and a paste-able summary — including macOS-specific checks for
architecture (arm64), LaunchAgent autostart, and Docker Model Runner.
Open a new GitHub issue
titled Install verification — macOS <version> <arch>
(example: Install verification — macOS 14.5 arm64 (M2 Pro)) and
attach the tarball. Secrets in the bundle are redacted automatically.
Manual report (if the verifier itself fails to run):
- Paste the output of:
sw_vers && uname -srm docker --version 2>/dev/null echo "DPF version: $(grep DPF_INSTALLER_VERSION install-dpf.sh)" - Note which steps you completed from the verification runbook §2 and which (if any) failed.
- Attach the doctor bundle:
bash install-dpf.sh doctor # Attach ~/.dpf/doctor-<timestamp>.tar.gz to the issue.
The verification runbook also covers:
- LaunchAgent surviving an actual reboot
- Docker Desktop
.dmginstall flow on a Gatekeeper-fresh machine - Discovery collectors emitting real
pkgutil/brewdata - LLM provider round-trip (Model Runner)
Any subset is useful. We don’t need every checkbox before reading your report — we’ll integrate findings as they arrive.
Going further
- Linux install guide — same platform, different host.
- Installer-parity roadmap
- Deployment doctrine — the 10 canonical contracts every install path wraps.
- CONTRIBUTING.md — for contributing back to the platform.
Connecting Claude Code or VS Code via MCP
Once the platform is running, you can connect Claude Code, Codex CLI, or VS Code to your install’s MCP server at /api/mcp/v1.
Option A — CLI (fastest, no browser needed):
pnpm --filter web exec tsx apps/web/scripts/issue-mcp-token.ts > .mcp.json
This issues a read-only token with the coding-agent scope set and writes a ready-to-paste .mcp.json in one step. Restart Claude Code to pick up the dpf connector.
# VS Code instead:
pnpm --filter web exec tsx apps/web/scripts/issue-mcp-token.ts --format vscode > .vscode/mcp.json
Option B — Admin UI: Log in → Admin > Platform Development > MCP Token Manager → generate a token → paste the displayed snippet into .mcp.json.