Back to All Cheatsheet Libraries cheatsheets

Munki

Open-source managed software installation for macOS — repo structure, pkginfo and manifest reference, commands, and the AutoPkg patching loop.

Munki is a static web server and some plists — that's the whole trick

There is no Munki server application. The "server" is a directory of files served over plain HTTPS by nginx, Apache, or even S3. Clients fetch a manifest, read a catalog, download packages, and install what they're missing. That simplicity is why Munki has outlasted most commercial alternatives — there's very little to break, and nothing proprietary to be locked into.

Munki does software. It does not do configuration profiles, enrollment, or device inventory. Pair it with an MDM: MDM handles enrollment and settings, Munki handles applications.

Showing results
Area Concept What it is Notes
Repopkgs/The actual installer payloads — .pkg, .dmg, .mpkg files.The only large directory. Everything else in the repo is small text files.
Repopkgsinfo/One plist per item describing it: name, version, where the installer lives, how to detect it's installed, dependencies.The heart of Munki. Everything interesting is expressed here.
Repocatalogs/A compiled index of every pkginfo, generated by makecatalogs.Never hand-edit these. They're build output — edit pkgsinfo and regenerate.
Repomanifests/Per-machine or per-group lists declaring what a client should have.A manifest can include other manifests, which is how you build a role hierarchy.
Repoicons/PNG icons displayed in Managed Software Center.Populate with iconimporter. Cosmetic, but a catalogue without icons looks abandoned.
Repoclient_resources/Optional branding for Managed Software Center.Worth doing — a branded catalogue gets used, an unbranded one gets ignored.
Manifestmanaged_installsItems Munki keeps installed. If missing or outdated, it installs or updates them.Self-healing: delete the app and it comes back at the next run.
Manifestmanaged_uninstallsItems Munki actively removes and keeps removed.Requires the pkginfo to define a working uninstall method.
Manifestoptional_installsItems offered in Managed Software Center for users to install themselves.The self-service layer. Every non-mandatory app belongs here.
Manifestmanaged_updatesItems Munki updates if already present, but won't install fresh.Useful for "keep it current if they have it" software.
Manifestincluded_manifestsNested manifests — the mechanism for composing role-based hierarchies.e.g. a machine manifest includes design_team, which includes site_default.
Manifestconditional_itemsSections applied only when an NSPredicate condition is true.Branch on OS version, hostname, architecture, or a custom conditional script.
Pkginfoinstalls arrayExplicit list of files/apps and versions Munki checks to decide if an item is installed.More reliable than receipts for drag-and-drop apps. Usually what you want.
PkginforeceiptsPackage receipt IDs and versions recorded by the macOS installer.Works for real .pkg installs. Useless for apps dragged from a DMG.
Pkginfounattended_installAllows installation in the background without user interaction.Only set it when the installer genuinely is silent and the app isn't in use — otherwise you interrupt people.
Pkginfoblocking_applicationsApps that must be closed before installing.Prevents replacing a running application underneath the user — a real source of corruption.
Pkginfoupdate_forMarks this item as an update to another item rather than a standalone install.How you deliver a plugin or patch tied to a parent application.
PkginforequiresDependencies installed first.Munki resolves the order. Avoid deep chains — they're hard to debug.
Pkginfosupported_architecturesRestricts an item to arm64 or x86_64.Essential where a vendor ships separate Apple silicon and Intel builds.
Pkginfoforce_install_after_dateHard deadline after which Munki installs regardless, logging the user out if needed.Powerful and disruptive. Use for security updates, communicate it first.
ClientSoftwareRepoURLWhere the client fetches the repo from.The one setting that must be right. Deliver it via configuration profile, not a local defaults write.
ClientClientIdentifierWhich manifest this machine uses.If unset, Munki tries hostname, then serial, then site_default — see the Gotchas tab.
ClientManaged Software CenterThe user-facing app: browse optional installs, see pending updates, trigger installs.Munki's self-service front end and the main reason users tolerate managed software.

Standing up a repo from scratch

Munki 7 is current. The admin tools run on your workstation; the repo is just files on a web server.

1

Create the repo directory structure

Six directories. That's the entire server-side product.

mkdir -p /Users/Shared/munki_repo/{catalogs,manifests,pkgs,pkgsinfo,icons,client_resources}
2

Install the admin tools and configure munkiimport

Grab the current munkitools installer from the project's releases. Then point the tools at your repo — this writes ~/Library/Preferences/com.googlecode.munki.munkiimport.plist.

munkiimport --configure # Repo URL: file:///Users/Shared/munki_repo # pkginfo extension: .plist # pkginfo editor: /usr/bin/vi (or your editor of choice) # Default catalog: testing
3

Serve the repo over HTTPS

Any static web server. The one thing that matters: do not allow directory listing, and make sure .plist files are served as plain files rather than being interpreted.

server { listen 443 ssl; server_name munki.example.com; root /Users/Shared/munki_repo; autoindex off; # never list the repo # Munki only ever needs GET location / { limit_except GET HEAD { deny all; } # auth_basic "Munki"; # see the auth note below # auth_basic_user_file /etc/nginx/munki.htpasswd; } }
4

Create a default manifest

site_default is the fallback every client lands on if nothing more specific matches.

manifestutil new-manifest site_default manifestutil add-catalog production --manifest site_default manifestutil display-manifest site_default
5

Import your first item

munkiimport copies the installer into pkgs/, generates a pkginfo, and opens it for editing.

munkiimport ~/Downloads/Firefox.dmg makecatalogs /Users/Shared/munki_repo
6

Configure clients by profile, not by hand

Deliver ManagedInstalls preferences as a configuration profile from your MDM. A profile is enforced, versioned, and removable; a local defaults write is none of those things and will drift.

# The equivalent settings, for reference — prefer a profile in production sudo defaults write /Library/Preferences/ManagedInstalls SoftwareRepoURL "https://munki.example.com" sudo defaults write /Library/Preferences/ManagedInstalls ClientIdentifier "site_default" sudo defaults write /Library/Preferences/ManagedInstalls InstallAppleSoftwareUpdates -bool false
7

Test on one machine before anything else

Verbose output tells you exactly which manifest and catalog it resolved — which is the answer to most first-run problems.

sudo managedsoftwareupdate --checkonly -vv
Securing the repo — think about this early
  • Always HTTPS. Munki downloads and installs software with root privileges. Plain HTTP is a remote code execution path.
  • The repo is read-only to clients. Only GET is ever needed — deny everything else at the web server.
  • Basic auth is the common approach, with credentials delivered by configuration profile. Client certificates are stronger if you can manage the PKI.
  • Never allow directory listing. An indexable repo tells an attacker your entire software inventory and versions.
  • Munki supports middleware for signed requests — useful for S3 or CDN-backed repos where basic auth isn't practical.
  • Anyone who can write to the repo can run code as root on every managed Mac. Treat repo write access as production infrastructure access.
Showing results
Where Command What it does
Clientsudo managedsoftwareupdateCheck the repo and install anything pending. The core client command.
Clientsudo managedsoftwareupdate --checkonlyCheck what would be installed without installing. Safe to run any time.
Clientsudo managedsoftwareupdate --installonlyInstall what's already been downloaded, skipping the check.
Clientsudo managedsoftwareupdate --autoThe mode the LaunchDaemon runs — respects unattended rules and user presence.
Clientsudo managedsoftwareupdate -vvVerbose. Shows the manifest and catalogs it resolved — the first debugging step.
Clientmanagedsoftwareupdate --versionInstalled Munki version. Include it in any bug report.
Clientdefaults read /Library/Preferences/ManagedInstallsDump effective client config — repo URL, identifier, and all options.
Clienttail -f /Library/Managed\ Installs/Logs/ManagedSoftwareUpdate.logFollow the client log live. Where the real answer usually is.
Clientcat /Library/Managed\ Installs/InstallInfo.plistExactly what Munki has decided to do on the next run.
Adminmunkiimport /path/to/App.dmgImport an installer: copies to pkgs/, generates pkginfo, opens it to edit.
Adminmunkiimport --configureSet repo path, default catalog, pkginfo editor.
Adminmakecatalogs /path/to/repoRebuild catalogs from pkgsinfo. Run after every pkginfo change or clients won't see it.
Adminmakepkginfo /path/to/itemGenerate a pkginfo without importing — useful for inspecting what Munki detects.
Adminmakepkginfo -f /Applications/App.appGenerate an installs array entry from an existing app.
Adminiconimporter /path/to/repoExtract app icons into icons/ so Managed Software Center looks populated.
ManifestmanifestutilInteractive shell for manifest editing. Tab completion works.
Manifestmanifestutil new-manifest <name>Create a manifest.
Manifestmanifestutil add-catalog production --manifest <name>Attach a catalog. A manifest with no catalog resolves nothing.
Manifestmanifestutil add-pkg Firefox --section managed_installs --manifest <name>Add an item to a section.
Manifestmanifestutil add-included-manifest <child> --manifest <parent>Nest manifests — the basis of role hierarchies.
Manifestmanifestutil display-manifest <name>Print a manifest's full contents.
Manifestmanifestutil list-manifestsEvery manifest in the repo.
Manifestmanifestutil find <name>Find which manifests reference an item — before removing it.
AutoPkgautopkg run -v Firefox.munkiDownload, package, and import into Munki automatically.
AutoPkgautopkg repo-add recipesAdd the community recipe repository.
AutoPkgautopkg search <app>Find an existing recipe before writing one yourself.

A realistic pkginfo

Most of Munki's power lives in these keys. This is a drag-and-drop app using an installs array rather than receipts, which is the common case and the one people get wrong.

<?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>name</key> <string>Firefox</string> <key>display_name</key> <string>Mozilla Firefox</string> <key>description</key> <string>Web browser. Shown to users in Managed Software Center.</string> <key>version</key> <string>142.0</string> <key>catalogs</key> <array> <string>testing</string> </array> <key>installer_item_location</key> <string>apps/Firefox-142.0.dmg</string> <key>installer_item_hash</key> <string><!-- sha256, generated by munkiimport --></string> <key>installer_type</key> <string>copy_from_dmg</string> <key>items_to_copy</key> <array> <dict> <key>source_item</key> <string>Firefox.app</string> <key>destination_path</key> <string>/Applications</string> </dict> </array> <!-- How Munki decides it is already installed. For a dragged app this is far more reliable than receipts. --> <key>installs</key> <array> <dict> <key>type</key> <string>application</string> <key>path</key> <string>/Applications/Firefox.app</string> <key>CFBundleShortVersionString</key> <string>142.0</string> </dict> </array> <!-- Refuse to replace the app while the user has it open. --> <key>blocking_applications</key> <array> <string>Firefox</string> </array> <!-- Safe to install silently: no UI, and blocking_applications protects the in-use case above. --> <key>unattended_install</key> <true/> <key>uninstallable</key> <true/> <key>uninstall_method</key> <string>remove_copied_items</string> <key>minimum_os_version</key> <string>13.0</string> <key>category</key> <string>Browsers</string> <key>developer</key> <string>Mozilla</string> </dict> </plist>

A manifest hierarchy that scales

Compose, don't duplicate

Per-machine manifests that each list every app become unmaintainable at about thirty Macs. Nest instead: a machine manifest includes a role manifest, which includes the site baseline.

site_default ← everyone: browser, AV, VPN, MSC branding ├── role_engineering ← includes site_default │ └── C02XK1TVJGH5 ← one engineer's Mac, includes role_engineering ├── role_design ← includes site_default, adds Adobe/Figma └── role_frontline ← includes site_default, minimal + kiosk apps # Build it: manifestutil new-manifest role_engineering manifestutil add-included-manifest site_default --manifest role_engineering manifestutil add-pkg Docker --section managed_installs --manifest role_engineering # Machine manifest named by serial — see the ClientIdentifier note in Gotchas manifestutil new-manifest C02XK1TVJGH5 manifestutil add-included-manifest role_engineering --manifest C02XK1TVJGH5
Catalogs as promotion stages
  • Standard pattern is three catalogs: testingstagingproduction.
  • An item's catalogs array controls which stage it's visible in. Promotion is editing that array and running makecatalogs.
  • Your own Mac's manifest points at testing. A pilot group points at staging. Everyone else is on production.
  • New imports default to testing — so nothing reaches users until you deliberately promote it.
  • This costs nothing to set up and is the single biggest protection against shipping a broken package fleet-wide.

The weekly patching loop

1

Let AutoPkg do the fetching

Manually downloading installers doesn't scale past a handful of apps. AutoPkg's .munki recipes download, verify, package, and import in one run.

autopkg repo-add recipes autopkg run -v GoogleChrome.munki Firefox.munki Slack.munki Zoom.munki
2

Schedule it and get notified

Run AutoPkg on a schedule via AutoPkgr or a LaunchDaemon, with Slack or email notification so you know when something new landed.

3

Test on your own machine first

New items land in testing. Your Mac is on that catalog, so you get it immediately. Install it. Open the app. Confirm it actually works.

4

Promote to staging, wait, then production

Edit the pkginfo's catalogs array, run makecatalogs. Give the pilot group a few days before going wide. Most bad packages surface within 48 hours.

5

Prune old versions

The repo grows quietly. Keep the current and one previous version of each item; delete older pkgs and their pkgsinfo, then makecatalogs again.

Munki alongside an MDM

Job MDM Munki
Enrollment (ADE / zero-touch)✔ Only MDM can
Configuration profiles✔ Only MDM can— (can deliver, but MDM is correct)
PPPC / privacy approvals✔ MDM-delivered profiles only
FileVault key escrow
Third-party app deploymentLimitedMunki's core strength
Self-service catalogueVaries✔ Managed Software Center
Complex install logic / conditionsLimited✔ Far more expressive
macOS updates✔ Prefer DDM enforcementPossible, but MDM is better now
The standard pairing
  • MDM enrolls the Mac, applies profiles, escrows the FileVault key, and installs the Munki client package.
  • Munki takes over all third-party software from there.
  • Deliver Munki's ManagedInstalls settings as an MDM configuration profile — enforced and versioned, rather than a local write that drifts.
  • This works with any MDM. SimpleMDM even hosts a Munki repo for you; Jamf, Mosyle, and Intune all pair with a self-hosted one.

Gotchas

Forgetting makecatalogs
  • By far the most common Munki mistake. You edit a pkginfo, nothing changes on clients, and you assume something is broken.
  • Catalogs are compiled output. Clients read catalogs, never pkgsinfo directly.
  • Run makecatalogs after every pkginfo change — import, edit, promotion, or deletion.
  • Make it muscle memory, or wrap your workflow in a script that always calls it.
ClientIdentifier fallback order surprises people
  • If ClientIdentifier isn't set, Munki tries in order: fully-qualified hostname, then short hostname, then hardware serial, then site_default.
  • That's a useful default — name a manifest after a serial and that Mac picks it up with no client config at all.
  • It also means a renamed Mac can silently change manifests. If a machine suddenly gets the wrong software, check this first.
  • managedsoftwareupdate -vv prints exactly which manifest was resolved. Always check before theorising.
Receipts vs installs array
  • Receipts only work for real .pkg installs. An app dragged from a DMG leaves no receipt, so Munki can't tell it's installed and reinstalls it every run.
  • For anything copied rather than installed, use an installs array pointing at the app bundle and its version key.
  • Generate one with makepkginfo -f /Applications/App.app rather than hand-writing it.
  • Symptom to recognise: an item that installs successfully on every single run. That's a detection problem, not an installer problem.
unattended_install can interrupt real work
  • Only set it where the installer is genuinely silent and non-disruptive.
  • Always pair it with blocking_applications so Munki won't swap an app out from under someone using it.
  • Anything requiring a restart or logout should not be unattended — surface it in Managed Software Center instead.
  • force_install_after_date will log a user out to meet the deadline. Communicate before using it.
The repo is a root-code-execution channel
  • Munki installs as root on every managed Mac. Anyone who can write to the repo can run arbitrary code fleet-wide.
  • Serve over HTTPS only, never plain HTTP.
  • Restrict repo write access as tightly as production server access — because that's what it is.
  • Disable directory listing so your software inventory isn't publicly enumerable.
Apple silicon and macOS updates
  • Use supported_architectures where a vendor ships separate arm64 and x86_64 builds — shipping the wrong one is a confusing failure.
  • Some installers need Rosetta; deploy it as a dependency rather than assuming it's present.
  • For macOS updates themselves, prefer MDM. Declarative Device Management enforcement is more reliable than Munki's Apple-update handling on modern macOS, especially given Secure Token requirements on Apple silicon.

Debugging a client, in order

Symptom Check
Nothing installs at allmanagedsoftwareupdate --checkonly -vv — does it resolve a manifest? A 404 on the manifest means ClientIdentifier is wrong or the manifest doesn't exist.
Item is in the manifest but ignoredIs the item in a catalog the manifest actually references? Did you run makecatalogs?
Reinstalls on every runDetection problem — receipts on a drag-install app. Switch to an installs array.
Downloads then fails to installManagedSoftwareUpdate.log for the installer's own error. Often a blocking app or an unmet minimum OS.
403 / 401 fetching the repoBasic auth credentials missing or wrong in the client config. Test with curl -u user:pass <repo>/catalogs/production.
Works manually, not on scheduleManual runs are root; the LaunchDaemon may hit a different network or proxy state. Check the log timestamps for the scheduled attempts.
Optional install missing from MSCConfirm it's in optional_installs, in a referenced catalog, and passes minimum_os_version and architecture checks.

Resources