Files
PalladiumWallet/docker
davide 6a5daa0c18 feat(build): add Docker-based reproducible build system
Adds docker/ with two Dockerfiles and a build.sh interactive script that
builds all three release targets (Windows exe, Linux binary, Android APK)
inside pinned Docker containers — no SDK required on the host.

Key design decisions:
- Source mounted read-only; build runs on an in-container copy so bin/obj
  never pollute the working tree.
- NuGet packages cached in a named Docker volume (plm-nuget-cache) across
  runs to avoid re-downloading on each build.
- Single-file desktop publishes use IncludeNativeLibrariesForSelfExtract so
  Avalonia's native libs (Skia, HarfBuzz, ANGLE) are embedded — without this
  flag the exe/binary silently fails to start.
- Android Dockerfile pins platform API 36 (dictated by the .NET 10 android
  workload, error XA5207 if mismatched) and bakes the full Android SDK +
  workload into the image layer.
- Artifacts are chown'd back to the host user so dist/ files are never
  owned by root.

All three targets verified end-to-end from a clean docker system prune -a:
Windows PE32+ exe (103 MB), Linux single-file binary launches on this host,
Android APK installs and renders UI on API-34 x86_64 emulator.
2026-07-02 10:45:43 +02:00
..

PalladiumWallet — Reproducible Builds via Docker

This folder builds all three distribution targets — Windows, Linux, Android — inside Docker containers. The entire toolchain (.NET 10 SDK, JDK, Android SDK, android workload) lives in the container images, pinned in the Dockerfiles, so:

  • anyone can produce the official binaries with a single command, on any Linux machine, without installing any SDK on the host;
  • the toolchain never drifts: every build uses exactly the same SDK versions, regardless of what is installed (or updated) on the host;
  • the build environment is auditable: the Dockerfiles in this folder are the complete, reviewable definition of how release binaries are made — which matters for a wallet, where users must be able to trust that the shipped binary comes from the published source.

Scope note: this pins the build environment. Bit-for-bit identical output across machines is a stronger property that .NET does not guarantee by default (embedded timestamps, signing); if two builds of the same commit differ, they differ only in those metadata, not in code.


Prerequisites

A Linux host (native, WSL2, or a VM) with Docker Engine installed and running. Nothing else — no .NET SDK, no JDK, no Android SDK.

# Check that Docker works:
docker info

If Docker is missing, install it from https://docs.docker.com/engine/install/ (or sudo apt install docker.io on Debian/Ubuntu). If docker info fails with a permission error, add yourself to the docker group and start a new shell:

sudo usermod -aG docker $USER

Disk space: ~2 GB for the desktop image, ~7 GB for the Android image (SDK + emulator-less toolchain). First-run time: the images are built automatically on first use — a few minutes for desktop, 1020 minutes for Android (large downloads). Subsequent builds reuse the cached images and take well under a minute (desktop) / a few minutes (Android).


Quick start

# From the repository root (or from the docker/ folder — both work):
./docker/build.sh

Running without arguments shows an interactive menu — pick a single target or all. Non-interactive usage:

./docker/build.sh [TARGET] [--rebuild]

Targets:
  windows   Win x64 single-file executable (native libs embedded)
  linux     Linux x64 single-file binary (runs as-is, nothing to install)
  android   Android APK (debug-signed)
  all       All three targets

Options:
  --rebuild   Force rebuild of the Docker images (needed after editing a Dockerfile)

Examples:

./docker/build.sh all               # build everything
./docker/build.sh windows           # Windows only
./docker/build.sh android --rebuild # Android, rebuilding the image first

Output — and how to use each artifact

All artifacts land in dist/ at the repository root. The version number is read automatically from <Version> in src/App/PalladiumWallet.App.csproj.

Target Path
Windows dist/windows/PalladiumWallet-{ver}-win-x64.exe
Linux dist/linux/PalladiumWallet-{ver}-linux-x64
Android dist/android/PalladiumWallet-{ver}.apk

Windows — a single self-contained .exe (runtime and native libraries embedded). Copy it to any 64-bit Windows 10/11 machine and double-click. The first launch takes a few extra seconds (it unpacks native libraries to a per-user cache); later launches are normal. SmartScreen may warn because the binary is not code-signed — choose "Run anyway".

Linux — a single self-contained binary, already executable. Copy and run:

./PalladiumWallet-{ver}-linux-x64

Works on any desktop distro with glibc, X11/Wayland and fontconfig (i.e. effectively all of them); no .NET or other packages to install. If you transfer it through a channel that strips permissions (e.g. a web download), restore the execute bit with chmod +x.

Android — a debug-signed APK for sideloading: transfer it to the phone and open it (enable "install from unknown sources" if prompted), or install via adb install dist/android/PalladiumWallet-*.apk. Supports Android 6.0+ (API 23), arm64 phones and x86_64 emulators.

Signature caveat: debug-signed APKs built on different machines carry different keys. If a previous build is already installed, Android refuses the update (INSTALL_FAILED_UPDATE_INCOMPATIBLE) — uninstall the old app first. Uninstalling deletes the app's data: back up the wallet seed before doing this. Release signing with a stable key is not set up yet.


How it works

Docker images

Image Dockerfile Used for Size
plm-build-desktop Dockerfile.desktop windows + linux ~1.5 GB
plm-build-android Dockerfile.android android ~5 GB

Images are built automatically the first time a target needs them and reused afterwards. Use --rebuild only after modifying a Dockerfile.

Source isolation

The repository is mounted read-only inside the container; the build works on a copy at /tmp/build. Your working tree is never touched — no stray bin//obj/ directories, and a dirty working tree doesn't leak into the build beyond the files it contains. Artifacts are written back through a bind mount to dist/ and chown'd to your user.

NuGet cache

A Docker named volume plm-nuget-cache holds downloaded NuGet packages across builds. To reclaim the space or force a clean re-download:

docker volume rm plm-nuget-cache

Troubleshooting

  • Docker daemon is not running — start it (sudo systemctl start docker; on WSL2, start Docker Desktop or the docker service).
  • Android image build fails downloading commandlinetools — Google rotates the build number in the URL. Update the URL in Dockerfile.android to the current one from https://developer.android.com/studio#command-tools and rerun with --rebuild.
  • error XA5207: Could not find android.jar for API level N — the .NET android workload moved to a newer API level. Bump ANDROID_SDK_PLATFORM (and ANDROID_SDK_BUILD_TOOLS) in Dockerfile.android to the level the error names, then --rebuild.
  • APK won't install over an existing app — signature mismatch between debug keys; see the signature caveat above.
  • Everything is broken / start from scratchdocker system prune -a && docker volume rm plm-nuget-cache, then rerun the script (images and packages are re-downloaded).

Linux AppImage (future)

The Linux target currently produces a single-file self-contained binary. Once a pupnet.conf is added to the repository, the build_linux function in build.sh can be extended to call PupNet Deploy inside the same plm-build-desktop image to also produce an AppImage with desktop integration (icon, menu entry).