Skip to content
cccskills
FreePublished on GitHub

macos-app-distribution-dmg

Use when shipping a macOS app outside the App Store — Developer ID signing with hardened runtime, notarization with notarytool, stapling, building a clean drag-to-Applications DMG, and verifying the result launches on a stranger's Mac.

The English summary is being prepared. Showing the original description.

Run score Not tested

See how to install

Files included (1)

  • SKILL.md8.3 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

macOS App Distribution (DMG)

Ship a macOS app outside the App Store so it opens first-try on a stranger's Mac: Developer ID signature, hardened runtime, notarization, staple, DMG. Miss a step and the user gets "App is damaged and can't be opened" — which most users read as your app is broken.

When to use

Use this skill when the user says:

  • distribute outside the App Store
  • DMG / disk image / drag to Applications
  • codesign / Developer ID
  • notarize / notarization / notarytool
  • staple / Gatekeeper
  • "app is damaged and can't be opened"
  • "cannot be opened because the developer cannot be verified"
  • hardened runtime

Do not use this skill for Mac App Store submission (different signing + review pipeline), iOS distribution, or pkg-installer authoring beyond the note below.

Core rule

Sign inside-out with Developer ID + hardened runtime,
notarize the final artifact, staple both the app and the DMG,
and verify on a machine that has never seen your certificates.

Prerequisites

  • Apple Developer Program membership.
  • Developer ID Application certificate in the login keychain (Xcode > Settings > Accounts > Manage Certificates, or developer.apple.com). This is a different certificate from "Apple Development" and "Apple Distribution".
  • Once, store notarization credentials (App Store Connect API key, or Apple ID + app-specific password):
xcrun notarytool store-credentials "notary-profile" \
  --key ~/keys/AuthKey_ABC123.p8 --key-id ABC123 --issuer <issuer-uuid>

The pipeline

archive → export (Developer ID signed, hardened runtime)
        → build DMG → sign DMG
        → notarize DMG → staple DMG (and the app inside)
        → verify → publish

1. Archive and export

xcodebuild -project MyApp.xcodeproj -scheme MyApp -configuration Release \
  archive -archivePath build/MyApp.xcarchive

xcodebuild -exportArchive \
  -archivePath build/MyApp.xcarchive \
  -exportOptionsPlist ExportOptions.plist \
  -exportPath build/export

ExportOptions.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>method</key>            <string>developer-id</string>
    <key>teamID</key>            <string>YOURTEAMID</string>
    <key>signingStyle</key>      <string>automatic</string>
</dict>
</plist>

The developer-id export re-signs everything correctly and enables hardened runtime per your target settings. In the target: Signing & Capabilities > Hardened Runtime must be on; add exception entitlements only for what the app truly does (e.g. com.apple.security.cs.allow-jit for JS engines — not "all of them to be safe"; every exception weakens notarization's meaning).

If you sign manually instead (SPM/Xcode-less builds), sign inside-out — frameworks and helpers first, app bundle last:

codesign --force --options runtime --timestamp \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  MyApp.app/Contents/Frameworks/Sparkle.framework
# …every nested framework, XPC service, helper, CLI…
codesign --force --options runtime --timestamp \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  --entitlements MyApp.entitlements \
  MyApp.app

Never use --deep for distribution signing: it applies the app's entitlements to nested code and mis-signs frameworks. It exists for quick local checks only.

2. Build the DMG

create-dmg (brew install create-dmg) gives the canonical drag-to-Applications layout:

create-dmg \
  --volname "MyApp" \
  --window-size 540 380 \
  --icon-size 128 \
  --icon "MyApp.app" 130 190 \
  --app-drop-link 410 190 \
  --background "dmg-background.png" \
  "build/MyApp-1.2.0.dmg" \
  "build/export/MyApp.app"

Or plain hdiutil with no styling:

mkdir -p build/dmg && cp -R build/export/MyApp.app build/dmg/
ln -s /Applications build/dmg/Applications
hdiutil create -volname "MyApp" -srcfolder build/dmg -ov \
  -format UDZO "build/MyApp-1.2.0.dmg"

DMG etiquette: version in the filename, app + Applications symlink and nothing else, a background that says "drag this there" at a glance, total size sane (check what you're bundling if it's suddenly 300 MB).

Sign the DMG too:

codesign --force --timestamp \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  "build/MyApp-1.2.0.dmg"

3. Notarize and staple

Notarize the DMG — Apple scans it and the app inside:

xcrun notarytool submit "build/MyApp-1.2.0.dmg" \
  --keychain-profile "notary-profile" --wait

--wait blocks until the verdict (usually minutes). On Invalid, get the reasons:

xcrun notarytool log <submission-id> --keychain-profile "notary-profile"

Then staple, so Gatekeeper passes even offline:

xcrun stapler staple "build/MyApp-1.2.0.dmg"

If you also distribute a bare .zip/.app (e.g. for Sparkle updates), staple the app itself and re-zip after stapling.

4. Verify — before publishing, every release

# Signature deep-check
codesign --verify --deep --strict --verbose=2 build/export/MyApp.app

# What Gatekeeper will say
spctl --assess --type open --context context:primary-signature -v build/MyApp-1.2.0.dmg
spctl --assess --type execute -v build/export/MyApp.app
#   want: "accepted · source=Notarized Developer ID"

# Staple present
xcrun stapler validate "build/MyApp-1.2.0.dmg"

# The real test: simulate a download (quarantine bit), then open
xattr -w com.apple.quarantine "0081;$(printf %x $(date +%s));Safari;" \
  "build/MyApp-1.2.0.dmg"
open "build/MyApp-1.2.0.dmg"

The quarantine test on a clean machine (or at least a fresh user account / VM) is the one that catches what your dev Mac can't — your machine trusts your certificates and will happily open things a stranger's Mac will block.

Common failures

SymptomCauseFix
"damaged and can't be opened"Not notarized, or broken signatureFull pipeline; check notarytool log
"developer cannot be verified"Ad-hoc or Apple Development certSign with Developer ID Application
Notarization Invalid: "not signed with a valid Developer ID" on a frameworkNested code unsigned/ad-hoc (SPM binaries, Sparkle, helpers)Sign inside-out; no --deep
Invalid: "hardened runtime not enabled"A nested binary missing --options runtimeAdd it everywhere, re-sign
Invalid: "contains get-task-allow"Debug build exportedRelease config, proper export
Works on your Mac, blocked elsewhereNever tested with quarantineThe xattr test above
App runs from DMG but breaks (bookmarks, login item)Translocation — running in-place from the imageUsers must drag to /Applications; that's what the layout is for

Updates and the release record

  • Sparkle is the standard update framework for non-App-Store apps. Budget for it from v1 — retrofitting updates after users exist is painful. Its XPC services are exactly the kind of nested code that must be correctly signed (inside-out, hardened runtime).
  • Bump CFBundleShortVersionString (marketing) and CFBundleVersion (build) every release; notarization treats each build as new.
  • Keep the pipeline as a script in the repo (scripts/release.sh) — a release process that lives in someone's shell history isn't a process. Record per release: version, date, archive hash, notarization submission ID.

Checklist

[ ] Developer ID Application certificate (not Apple Development)
[ ] Hardened runtime on, minimal exception entitlements
[ ] Nested code signed inside-out; no --deep in the pipeline
[ ] Release configuration; no get-task-allow in the exported app
[ ] DMG: app + /Applications symlink only, versioned filename, signed
[ ] Notarized via notarytool; log checked on any Invalid
[ ] Stapled: DMG (and bare .app if distributed separately)
[ ] spctl says "accepted · Notarized Developer ID"
[ ] Quarantine-bit open test passed on a machine without your certs
[ ] Pipeline scripted in the repo, not in shell history

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when creating a new project folder, starter pack, app scaffold, or agent-ready workspace — AGENTS.md, README, TASKS, DECISIONS, PROMPTS, an idempotent bootstrap script, and starter-pack modes.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing or implementing states for AI agent tasks, background jobs, local model runs, queues, and notifications — one shared set of states, only legal transitions, and a recovery path out of every failure.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when exposing app features to App Intents, Shortcuts, Siri, Spotlight, widgets, controls, or system actions — small safe AppIntents, AppShortcutsProvider, AppEnum/AppEntity, parameters, and open-vs-background behavior.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing, reviewing, or implementing a command-palette / ⌘K layer for an Apple-platform app — keyboard-first global search, ranked and grouped results, native floating panel, and protected destructive commands.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing or implementing empty, first-run, no-result, offline, error, or waiting states in Apple-platform apps — every empty state explains what is missing, why it matters, and what to do next.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing or implementing feedback, status, progress, alerts, errors, success states, and live activity indicators in Apple-platform apps — inline status, banners, alerts, notifications, and loading states.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

See all skills by aka-kika

Report a problem with this skill