← Back to Wiki
Packaging / Linux

Packaging an Electron App for Flathub: Six Things That Break, Including the BaseApp Not Shipping Electron

A Flatpak manifest sat in the repo looking entirely reasonable. It had never been built once. Every problem below appeared within minutes of actually running flatpak-builder, and not one of them was visible by re-reading the file. A manifest that looks right and a manifest that builds are unrelated states. Here is what broke, in the order it broke.

Share on X

1. Flathub builds offline, so the dependency tree has to be a file

Flathub build workers have no network. That is the single fact driving most of what follows. Your package-lock.json is not enough, because nothing is allowed to go and fetch what it describes. You pre-generate every source as JSON:

python3 -m venv /tmp/fng && /tmp/fng/bin/pip install flatpak-node-generator
/tmp/fng/bin/flatpak-node-generator npm -o generated-sources.json package-lock.json

Generate it from the lockfiles as they stand at the tag you are building, not from your working tree. Building v1.2.3 from sources generated against uncommitted changes gives you a build that cannot be reproduced by anyone, including you.

On Ubuntu that tool needs a venv, because PEP 668 marks the system Python as externally managed.

2. Offline npm still needs to be told where the cache is

npm ci failed ENOTCACHED on the very first package, while the populated cache sat in the build directory a few paths away.

NPM_CONFIG_OFFLINE=true only says "do not use the network". It does not say where to look instead, so npm consults its default cache location, which in the sandbox is empty. Set both:

build-options:
  env:
    NPM_CONFIG_OFFLINE: "true"
    NPM_CONFIG_CACHE: /run/build/<module-name>/flatpak-node/npm-cache

The module name in that path is the name of your manifest module. Get it wrong and you get the identical error, which makes this feel like an offline-mode bug rather than a path typo.

3. The Electron BaseApp does not ship Electron

BE WARNED: this is the one that costs you the afternoon. org.electronjs.Electron2.BaseApp supplies zypak, the wrapper that makes Chromium's sandbox work inside Flatpak, plus supporting libraries. It does not supply an Electron runtime. Assume it does, set ELECTRON_SKIP_BINARY_DOWNLOAD, and the build succeeds while producing a 2.7MB app with no runtime in it. The launcher then hands a .js file to zypak with nothing capable of executing it. A correct build here is 327MB.

Take Electron from the zip that flatpak-node-generator has already cached for you, and unzip it directly:

unzip -q flatpak-node/cache/electron/electron-v*-linux-x64.zip -d /app/lib/myapp/electron

Do not rely on the electron npm package's postinstall to place a binary. It does not run in this sandbox at all. After npm ci there is no dist/ and no path.txt, so anything expecting those finds nothing.

Then the launcher has to pass two different things: the binary to zypak, and the app directory to Electron, so package.json resolves main, the name and the version:

#!/bin/sh
exec zypak-wrapper /app/lib/myapp/electron/electron /app/lib/myapp "$@"

4. Half the app never gets installed

Anything main.js reaches for via __dirname has to be installed explicitly. The bundler output is not the whole app:

5. chrome-sandbox must be deleted, not chmod'd

Flatpak refuses to export a setuid binary, so the obvious move is to drop the setuid bit and move on. That is worse than deleting it.

Electron finds the helper, decides it is misconfigured, and aborts at startup with "The SUID sandbox helper binary was found, but is not configured correctly". zypak is providing the sandbox, so the helper has no job:

rm -f /app/lib/myapp/electron/chrome-sandbox

6. The small ones that still stop a build

The bug the packaging found in the app

Worth the price of admission on its own. The desktop entry had no MimeType=x-scheme-handler/myapp; line, which means the custom URL scheme was never registered with the desktop. Single sign-on returns to the app through that scheme, so desktop SSO could not have completed under any Linux packaging, Flatpak or otherwise. Packaging exercises paths your dev machine never touches, because your dev machine has the scheme registered by some other means or never used it.

Test it in a way that means something

Do not smoke-test by invoking the Electron binary directly. flatpak run --command=/app/lib/myapp/electron/electron bypasses zypak entirely, which means it skips the exact wrapper the whole packaging exists to set up. It can pass while the real launch path is broken. Run the app the way a user does, and if you are headless, run it under a virtual display rather than not at all.

When this isn't your problem