Building a standalone app¶
nativella build turns a project into a single executable: a copy of the nativella command, PHP included, with the project packed inside. Run it and the application starts, with no PHP needed on the machine.
cd path/to/notes
composer install --no-dev --optimize-autoloader
cd ..
nativella build notes
Packaging Notes 1.2.0 into dist/notes…
Done: dist/notes
nativella build [<project-dir>] [-o <output>]
The project directory defaults to the current one. The executable goes to dist/ in the current directory, named after the application's name, in lower case with dashes, using ASCII letters and digits. Notes/Pro makes dist/notes-pro (.exe on Windows). -o picks another path.
Tip
nativella build from the project directory works too. The dist/ it writes to is never packed, so earlier builds do not ship. With -o to another directory of the project, only the executable being written is left out of the archive.
What ships¶
The whole project directory, vendor/ included, as a compressed archive, except:
| Left out | |
|---|---|
.git, .idea, dist, node_modules, tests |
At the project root. dist is where nativella build writes, so a dist directory of your own there does not ship either, but public/dist does. |
.env.local, and every path ending with .local |
Local overrides, which tend to hold secrets. The build lists the ones it leaves out. |
.pem, .key and .p8 files that hold a private key; .p12, .pfx, .jks, .keystore and .mobileprovision files; auth.json, at the project root |
Private keys, keystores, provisioning profiles, and Composer's tokens. These file types are left out anywhere in the project, but the certificates of a .pem file without a private key ship, like those of composer/ca-bundle. The build lists the ones it leaves out. |
.db, .sqlite and .sqlite3 files, and their -journal, -wal and -shm files |
Your machine's SQLite databases. The application makes its own, in its data directory. The build lists the ones it leaves out. |
| Shipped empty | |
|---|---|
var |
What Symfony writes, its cache and its logs, and what the application writes there, like a SQLite database. Symfony makes the directories it needs. |
storage/app, storage/framework/cache, storage/framework/sessions, storage/framework/views, storage/logs |
What Laravel writes: its files, caches, sessions and logs. |
What your application wrote on your machine stays behind, but the directories ship, because the frameworks expect them to exist.
Packaging Notes 1.2.0 into dist/notes…
Left out .env.local: local overrides never ship. Set the production environment in a file that does, like the .env.local.php of `composer dump-env prod` with Symfony.
Left out auth.json, config/jwt/private.pem: private keys and credentials never ship, as anyone who has the application could read them.
Done: dist/notes
Symbolic links are followed, so the packages Composer links from path repositories ship as real files.
Warning
Everything else ships, and anyone who has the executable can read it: .env files, and the keys and credentials in any other file. A desktop application has no secrets from its users. Keep anything they should not see out of the project. Credentials says where each kind goes.
Preparing the project¶
- Install the dependencies without the development ones:
composer install --no-dev --optimize-autoloader. Whatever is invendor/ships. - Build the front-end assets, if you have any.
node_modulesstays behind,public/builddoes not. - Set the production environment. With Symfony,
composer dump-env prodwrites.env.local.php, which ships (its name does not end with.local) and wins over the.envfiles. - Avoid caches that hold absolute paths from your machine, like Laravel's
config:cache, because once extracted, the project runs from another directory.
Credentials¶
The application's users can read whatever ships, because the project is extracted in clear into their cache directory and no packaging hides it. Where a credential comes from decides where it goes:
| Credential | Where it goes |
|---|---|
The secret the framework signs and encrypts with: Symfony's APP_SECRET, Laravel's APP_KEY |
Nowhere. Each installation has its own, NATIVELLA_SECRET. The Laravel integration gives it to the framework; with Symfony, a line in framework.yaml does. |
| The user's credentials: a token, an account password, a key they bring | Asked for in the application, and kept in the system keychain with Secrets. |
| Your own credentials: a paid API's key, a token with rights on your services | On a server of yours, not in the application. The server keeps them, makes the calls, and authenticates the application's users. |
| Keys made to be public: a Sentry DSN, a publishable Stripe key, a key restricted to one use | In the configuration that ships, like .env.local.php. |
A release built by a CI can write the .env.local.php of composer dump-env prod from the CI's secrets, just before nativella build. What it holds still ships, in clear, so keep it for what may be public.
At startup¶
The executable finds the project it carries and extracts it, once per build, into the user's cache directory:
| Platform | Extracted to |
|---|---|
| Linux | ~/.cache/<identifier>/<build> (or $XDG_CACHE_HOME) |
| macOS | ~/Library/Caches/<identifier>/<build> |
| Windows | %LOCALAPPDATA%\<identifier>\<build> |
The next launches of the same build start right away. A build of other files, or by another version of Nativella, extracts again, next to the previous one, and removes it once it runs, when no other instance of the application can be running from there. The data directory stays. Extraction is safe when two instances start at once, because each one extracts aside and the first to finish wins.
Then the application runs exactly as with nativella run. Its command line belongs to the application, and a second launch forwards it to the running instance as AppReopened.
The data directory is separate from the extracted project, so the database and the settings survive updates, as long as the identifier does not change.
Reproducible builds¶
The same project, built by the same nativella, makes the same executable, byte for byte. The project archive keeps neither the dates nor the owners of its files, and <build>, at startup, is a digest of it. APKs are reproducible too, because all their entries are dated 1981-01-01, as Android's build tools do. A Debian package dates its files, and an iOS app for devices its signature, with SOURCE_DATE_EPOCH, in seconds since 1970, when it is set, as reproducible builds ask, and with the build time otherwise.
SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) nativella deb notes
What the machine needs¶
The executable carries the project and PHP, so the machine needs neither PHP nor Composer. On Linux, it needs what the nativella command needs to run: the system webview, WebKitGTK 4.1, the tray library if the application shows a tray icon, and a glibc as recent as that of the machine that built the nativella command, 2.35 for the releases, built on Ubuntu 22.04. Your application's users install them too. Installation lists the packages for each distribution, to repeat in your application's instructions. On macOS, the executable needs nothing.
The PHP inside¶
The executable carries the PHP of the nativella command that built it. For the releases, that is PHP 8.5, linked statically, built with static-php-cli, in the patch release the repository pins (The version of PHP), 8.5.12 for 0.5.0. nativella licenses starts with it, and phpinfo(), in the application, also gives the library versions. The Symfony demo makes a 91 MB executable (see Size and memory).
It has the extensions a Symfony, Laravel or Doctrine application needs (bcmath, ctype, curl, dom, fileinfo, filter, iconv, intl, mbstring, opcache, openssl, pdo_sqlite, phar, session, simplexml, sodium, sqlite3, tokenizer, xml, xmlreader, xmlwriter, zip, zlib), and reads only Nativella's php.ini. PHP runs with its defaults, plus the settings Nativella sets (errors in the logs rather than the page, uploads up to 512 MB, sessions in the application's cache directory, the system time zone). intl carries ICU and its data, for every locale, which makes up 36 MB of the executable.
Android and iOS applications carry the same PHP, with the same extensions, built for the phone. See PHP for the phone.
Another PHP version, or other extensions, take a nativella built with them, from the repository. See Your own PHP.
Note
Linux has Debian packages. Other installers (.dmg, .msi) and code signing are not there yet; standalone executables update themselves. See Limitations and roadmap.
Size and memory¶
The executables of the 0.4.0 release, and the memory the demos take on Linux x86_64, with their first page open (Ubuntu 26.04, GNOME on Wayland, WebKitGTK 2.52):
nativella |
Vanilla demo | Symfony demo | Laravel demo | |
|---|---|---|---|---|
| Executable, Linux x86_64 | 86.9 MB | 87.2 MB | 90.8 MB | 91.8 MB |
| The same, compressed (gzip) | 29.5 MB | 29.9 MB | 33.4 MB | 34.4 MB |
| Executable, macOS arm64 | 74.4 MB | 74.7 MB | 78.3 MB | 79.3 MB |
| APK, Android arm64 | 80.2 MB, the template | 80.5 MB | 84.1 MB | 85.0 MB |
| Project, extracted, on disk | 4 MB | 42 MB | 49 MB | |
| Memory: the application process | 130 MB | 135 MB | 130 MB | |
| Memory: the processes WebKit starts | 140 MB | 130 MB | 125 MB | |
| Memory: total | 270 MB | 265 MB | 255 MB |
The sizes are those GitHub shows, in MB of 1,048,576 bytes.
Nearly all of an executable is the nativella command: PHP and its libraries, ICU and its data for intl (36 MB), and the Rust shell around the webview. The project adds its own size, compressed: 0.3 MB for the vanilla demo, 4 to 5 MB for the Symfony and Laravel ones, vendor/ included. The webview is the system's, and is not part of it. A nativella with your own PHP, without intl, makes smaller executables.
The memory is the PSS of each process (Pss in /proc/<pid>/smaps_rollup): its own memory, plus its share of what it shares with other processes, like the system libraries. The application holds the Rust shell, GTK, the side of WebKit that drives the windows, and PHP. WebKit starts a web process, which renders the pages and runs their JavaScript, a network process, and the sandboxes of both. The demo pages are light; heavier ones take more WebKit memory, as in a browser. The RSS of these processes, which counts the shared libraries in full in each one, adds up to about 500 MB. The memory on macOS and on phones is not measured yet.
Debian packages¶
On Linux, nativella deb makes a Debian package of the project, for Debian, Ubuntu and the distributions based on them. Users install it with their package manager, which brings the webview along, and find the application among the others.
nativella deb notes
Packaging Notes 1.2.0 into dist/notes_1.2.0_amd64.deb…
Done: dist/notes_1.2.0_amd64.deb
nativella deb [<project-dir>] [-o <output>]
| The package holds | |
|---|---|
/usr/bin/<package> |
The executable of nativella build. |
/usr/share/applications/<package>.desktop |
The application's entry, with its deep link schemes. |
/usr/share/icons/hicolor/*/apps/<package>.png |
The icon, from 32 to 512 pixels. Give it 512 pixels or more. |
/usr/share/doc/<package>/THIRD_PARTY_NOTICES.txt |
The licenses of the software inside. |
The package is named after the application's name, in lower case with dashes, and so are its executable and its entry, because the desktop matches the windows to the entry, and so to the icon, by that name. Its version is the version, which has to start with a digit, with a pre-release after a ~ rather than a -. 1.2.0-beta.1 becomes 1.2.0~beta.1, which Debian sorts before 1.2.0, as a pre-release. composer.json gives the rest: the description, the homepage, and the first of the authors as the maintainer, or the application's name.
It depends on libwebkit2gtk-4.1-0 and a glibc 2.35 or later (Ubuntu 22.04, Debian 12), and recommends libayatana-appindicator3-1, for a tray icon. It is made for the processor of the machine that builds it, amd64 or arm64. Build it with the nativella of the releases, whose PHP is linked statically. A development build of nativella links its machine's PHP, and warns that its packages will not run elsewhere.
Licenses¶
The executable contains other people's software, under their licenses: PHP, the libraries it is linked with (OpenSSL, curl, ICU, libxml2, GNU libiconv, libsodium, libzip, SQLite, zlib) and the ones php-src bundles (PCRE2, libmagic, lexbor, uriparser…), and Nativella's Rust crates (Tauri, wry, tao, ext-php-rs…). Most of these licenses ask that their copyright notices and texts go with every copy, and the PHP License that it says "This product includes PHP software, freely available from http://www.php.net/software/".
The nativella command of the releases carries these third-party notices, gathered when it is built. nativella licenses prints them, and nativella build writes them next to the executable it builds.
Packaging Notes 1.2.0 into dist/notes…
Wrote the licenses of the software the application contains to dist/THIRD_PARTY_NOTICES.txt: distribute them with it.
Done: dist/notes
To distribute the application:
- ship
THIRD_PARTY_NOTICES.txtwith the executable, in the same archive or installer, or show it in the application, in an "about" page:App::thirdPartyNotices()returns it; - add the licenses of what your project ships: the Composer packages of
vendor/(composer licenseslists them), the front-end libraries.
The system libraries the executable loads are not part of it, and need no notice: glibc, libstdc++ and WebKitGTK on Linux, the macOS frameworks. The releases also attach each platform's notices, THIRD_PARTY_NOTICES-<platform>.txt. A nativella built without them, by cargo build rather than castor nativella:build, says so, and nativella build warns.
Important
Some of the static PHP is under the GNU Lesser General Public License 2.1, linked statically: GNU libiconv, which the iconv extension and libxml2 use, and, inside php-src, libbcmath (bcmath) and libmbfl (mbstring). The notices give the version of each, and the URL of the source archive it was built from. Among other things, the LGPL asks that whoever gets the application can use it with a modified version of these libraries. For that, Nativella is open source and builds from source, PHP included (castor static-php:build, then castor nativella:build). An application's project is an archive appended to that executable, which it extracts in clear and nativella build packs again. Nativella cannot tell you what your distribution needs, so ask someone who knows these licenses.