To package software for Mac devices, lay out the files you want installed, build a component package with pkgbuild, wrap it in a product archive with productbuild, sign it with a Developer ID Installer certificate, notarise it with notarytool, and test it on a clean Mac before it reaches users. Do it once by hand and the process becomes repeatable, which is what makes software deployment through an MDM predictable.
This guide walks through each step with the commands we use. It is written for Mac admins and IT engineers. If you manage a fleet of Apple devices, our Apple and Mac services cover the wider picture.
What a macOS Installer Package Is
A .pkg file is an installer for the macOS Installer. It carries a payload (the files to install), an install location, and optional scripts that run before or after the install.
There are two kinds you will meet:
- Component package. What
pkgbuildproduces: one payload, one identifier, one version. It can be installed on its own. - Product archive. What
productbuildproduces: one or more component packages plus a distribution file that describes what the installer shows and does. This is the format most MDM tools and Apple’s own signing and notarisation workflow expect.
Both are flat packages, a single file you can copy, sign and send. Check your MDM’s documentation for the exact package format it accepts.
Before You Start
You need:
- A Mac with Xcode or the Xcode command line tools installed.
pkgbuild,productbuild,pkgutilandinstallership with macOS itself;notarytoolandstaplercome with Xcode and are run throughxcrun. - A Developer ID Installer certificate in your keychain, issued through an Apple Developer Program account. Its name looks like
Developer ID Installer: Your Company (TEAMID). - A clean test Mac or virtual machine, so a package that only works on your own machine does not slip through.
Signing and notarisation need your own Apple Developer account. If you only deploy packages inside your organisation, check what your MDM and your security policy require, and still sign: an unsigned package is harder to trust and to audit.
Step 1: Lay Out the Payload
The payload is a folder that mirrors where the files will land on the target Mac. Everything inside it is installed relative to the install location.
mkdir -p payload/usr/local/bin
cp ./mytool payload/usr/local/bin/mytool
chmod 755 payload/usr/local/bin/mytool
This payload installs mytool into /usr/local/bin. For an app bundle, put it in payload/Applications/ and use --install-location / when you build, so the path in the payload is the path on disk.
Step 2: Add Scripts When You Need Them
Scripts let you do work the payload cannot, such as creating a configuration file or loading a LaunchDaemon. Put them in a folder named scripts. A script called preinstall runs before the files are copied, and one called postinstall runs after.
#!/bin/bash
# scripts/postinstall
# Runs as root. Keep it idempotent: it may run again on an upgrade.
set -e
mkdir -p /Library/Application\ Support/Example
echo "installed $(date)" > /Library/Application\ Support/Example/install.log
exit 0
Make it executable (chmod 755 scripts/postinstall). A non-zero exit code makes the install fail, so exit 0 deliberately. According to the pkgbuild manual, a top-level script that needs to run longer than 10 minutes should be set up as a component-specific script with a timeout, so plan long jobs carefully.
Step 3: Build the Component Package
pkgbuild \
--root payload \
--identifier com.example.mytool \
--version 1.0.0 \
--install-location / \
--scripts scripts \
mytool-component.pkg
Two settings deserve care:
--identifieris how the Installer recognises the package. Reuse the same identifier for every version of the same software.--versionis how the Installer decides whether this is an upgrade or a downgrade. Raise it with every release.
Step 4: Build and Sign the Product Archive
productbuild \
--package mytool-component.pkg \
--sign "Developer ID Installer: Your Company (TEAMID)" \
mytool-1.0.0.pkg
productbuild --package creates the product archive and a synthesised distribution file for you. You only need to write your own distribution XML when you want custom installer choices or requirements.
Sign the product archive, not the component package inside it: the pkgbuild manual notes there is no reason to sign the individual package when you go on to create a signed product. Signing with a Developer ID identity includes a trusted timestamp by default, so the Mac needs internet access when you sign.
Step 5: Notarise and Staple
Notarisation sends the signed package to Apple’s notary service, which scans it and returns a ticket. Store your credentials in the keychain once. notarytool asks for an app-specific password for your Apple ID, which you create on your Apple Account page, and saves it under the profile name:
xcrun notarytool store-credentials "texarxs-notary" \
--apple-id "you@example.com" \
--team-id "TEAMID"
Then submit the package and wait for the result:
xcrun notarytool submit mytool-1.0.0.pkg \
--keychain-profile "texarxs-notary" \
--wait
If it is accepted, attach the ticket so the package can be checked offline:
xcrun stapler staple mytool-1.0.0.pkg
xcrun stapler validate mytool-1.0.0.pkg
If the submission is rejected, fetch the log with the submission ID that notarytool printed to see why:
xcrun notarytool log SUBMISSION-ID --keychain-profile "texarxs-notary"
A common cause is an executable inside the payload that is not itself signed with a Developer ID Application certificate, with the hardened runtime and a secure timestamp. Notarisation checks the contents of the package, not only the package signature.
Step 6: Test Before You Deploy
Test the package itself, then test an install.
Inspect the package without installing it:
pkgutil --check-signature mytool-1.0.0.pkg
pkgutil --payload-files mytool-1.0.0.pkg
pkgutil --expand mytool-1.0.0.pkg expanded-pkg
Then install it on a clean Mac or virtual machine:
sudo installer -pkg mytool-1.0.0.pkg -target / -verbose
Check these on the test Mac:
- The files landed where you expected, with the right owner and permissions.
- Your
postinstallran, and a second install over the first still works. - The package installs on the oldest and newest macOS versions you support, and on both Apple silicon and Intel Mac devices if you still have both.
/var/log/install.logshows no errors.
Only after that should you upload the package to your MDM and deploy it to a pilot group.
Common Mistakes to Avoid
- Changing the identifier between versions, so installs stack up instead of upgrading.
- Forgetting to raise the version, so the Installer treats the new package as the same one.
- Scripts that are not executable, or that depend on a logged-in user. Scripts run as root, often with no user session.
- Wrong paths in the payload, such as a stray
payload/folder that ends up on disk. - Testing only on your own machine, where dependencies already exist.
Where AutoPkg Fits
Once you can package by hand, you will want to automate packaging for apps that publish updates often. AutoPkg is a community tool that downloads, packages and prepares software from recipes. It builds on exactly these steps, so understand the manual process first.
How TexArxs Can Help
If you want help standardising packaging and deployment for your Mac devices, talk to TexArxs. We also run small online packaging workshops for Mac admins in India; see our training page.