Repository navigation
Why ncmake
Packaging a Nextcloud app is a solved problem, and there are good tools for it already. ncmake started as a different take on that one job and has grown into a tool for the whole life of an app. This page names the design bets behind it, how it approaches the steps it shares with the established krankerl, and which files an ncmake app no longer has to carry.
Nothing on your host. make build runs composer and npm in throwaway containers picked from the app's own declared PHP and Node versions. You need no PHP, no Node, no version manager, only podman or docker (or RUNTIME=bare if you insist on host tools). You can build a PHP 8.1 app on a machine that has only PHP 8.4, or no PHP at all. The images are kept current and removed again on their own, so the host does not fill up with them either.
Nothing to configure. ncmake reads what it needs from the files an app has anyway: appinfo/info.xml, composer.json, package.json, .gitignore and the lock file (see How ncmake understands your app). A standard app needs no build config of its own: drop in the bootstrap stub, run make, done. An app that genuinely deviates sets a single variable in ncmake.mk (see Per-app tuning).
Keep model, not ignore model. What ships is defined as an allowlist of the standard runtime paths, each only when it exists (see Building and packaging). A new dev file cannot leak into a release, because it is not on the list; a missing runtime directory fails loudly instead of shipping a broken app. .nextcloudignore still exists, but only to trim within that set (test ballast inside a shipped vendor package), not as the sole line of defence.
One tool, shared and self-updating. The real Makefile lives once in a per-machine cache and is fetched by a dozen-line stub. Update ncmake and every app on the machine follows, with no copy of build logic to maintain per repository. NCMAKE_REF pins a version when you want to freeze it.
Measure, then report. The analysers look at what an app actually is: whether its metadata files agree, whether the committed build output comes from the source beside it, whether it runs on every PHP its servers run, and what each page costs the browser and why (see Checkout audit and Bundle analysis). They report findings with the app's own numbers and change nothing.
The lifecycle, not just packaging. ncmake installs your CI workflows from their upstream templates, tracks which are yours and which drifted, and opens a pull request to update them on a schedule (see Workflows and Workflow updater). Releasing is a flow of its own: a validated version bump, pre-releases, a changelog generated from your conventional commits and collected between releases, a signed tag, the integrity signature a Nextcloud server checks, maintenance lines for older releases, and App Store publishing that signs the exact bytes the store will fetch (see Releasing, Integrity signature and App Store).
krankerl is the established CLI for packaging and publishing Nextcloud apps, a Rust binary by Christoph Wurst. Both tools build, package, sign and publish an app, and they go about each of those steps differently:
| Step | ncmake | krankerl |
|---|---|---|
| Getting the tool |
make, already present on every Unix system, plus a committed stub ↗
|
a binary you install and update first (AUR, .deb, cargo install) |
| Toolchain for the build | composer and npm in throwaway containers on the app's declared PHP and Node version ↗ | PHP, Composer and npm installed and correct on the host |
| Build steps | derived from composer.json, package.json and the lock file, overridable in ncmake.mk ↗
|
hand-listed in krankerl.toml (before_cmds) |
| Shipped file set | an allowlist of runtime paths, .nextcloudignore trims within it ↗
|
a copy of the tree minus a .nextcloudignore exclude list |
| Staging | one staged set feeds dist, rsync and cp alike ↗
|
stages into build/artifacts
|
| Version bump |
make version validates against the latest tag, opens the release branch, bumps info.xml, package.json and composer.json with their lockfiles, and commits ↗
|
krankerl version major/minor/patch rewrites the version in info.xml
|
| Publishing |
make publish downloads the release asset and signs exactly those bytes ↗
|
krankerl publish |
| Updates | one cached Makefile, updated once for every app on the machine ↗ | upgrade the binary per machine |
| License | MIT (REUSE compliant) | GPL-3.0 |
krankerl's scope ends with these steps; the changelog, the App Store onboarding and queries, the CI workflows and the analysers are ncmake's alone. krankerl is enough if all you want is a native binary that packages and publishes, and you are content to maintain a krankerl.toml and build on a local toolchain. Neither is a fork or reimplementation of the other.
The whole point of deriving everything from the app is that the app stops carrying redundant scaffolding. With ncmake in place, these are gone:
-
krankerl.tomlor any equivalent build-recipe file — the build steps come fromcomposer.jsonandpackage.json. -
A hand-written
Makefilefull of build logic — the repository carries only the dozen-line stub, identical across every app. -
Bespoke packaging or exclude scripts — the keep model plus
.nextcloudignoredefine the shipped set. -
A per-app
release.ymlthat re-lists the build steps, the includes and the excludes — the copy-paste that sits, and quietly rots, in countless Nextcloud apps: the CI packaging drifts away from what the maintainer builds locally, and one day a release ships a file set nobody deployed. ncmake'srelease.ymlcarries no app-specific data at all. The shipped set comes from the keep model plus.nextcloudignore, the build ismake build && make dist, and the tarball is found by glob, so the workflow is byte-identical across every ncmake app. Local build and CI release are the same code path by construction — they cannot disagree, because there is only one of them. The packaging is reasoned out once and lives in one place, instead of being re-derived, and silently diverging, in every repository. -
Hand-maintained
.github/workflows/*— they are installed from upstream templates and kept current by the workflow updater; only genuinely app-specific workflows are written by hand. -
The
github-actionsecosystem on.github/workflows/independabot.yml— the updater owns that folder, so the entry comes out, or moves to the app's own.ncmake/workflows/(npm and Composer stay under Dependabot). -
Routine
CHANGELOG.mdediting — the release section is generated from the conventional commits since the last tag, then yours to extend by hand.
What stays is the app itself, and one stub. That is the DRY principle the whole of ncmake is built on: a fact lives in exactly one place, and everything else is derived from it.
© 2026 [ernolf] Raphael Gradenwitz · MIT-licensed · Report an issue
For app developers
- Getting started
- How ncmake understands your app
- Building and packaging
- Bundle analysis
- Build tools
- Checkout audit
- Lint
- Releasing
- Integrity signature
- Translations
- Per-app tuning
- Target reference
- AI assistant
CI workflows
App Store
For app users
Git
Background
Reference