The existing server and cloudflared services run the Vaultwarden deployment.
Client builds are opt-in, one-shot Compose services; a regular docker compose up
does not build clients.
./build-clients --target androidThe native Compose equivalent is:
OUTPUT_UID=$(id -u) OUTPUT_GID=$(id -g) docker compose run --rm --build --no-deps androidCompose selects services and profiles rather than accepting a --target flag.
The wrapper supplies that interface and sets artifact ownership to the invoking user.
--build builds the toolchain image; the one-shot container then compiles and exports
the client. docker compose build android alone only builds the toolchain image.
The builder uses Linux amd64 Android tools; ARM hosts need amd64 emulation.
The first build downloads the Android SDK/NDK, Rust toolchain, and dependencies,
and compiles the SDK for four ABIs. Allow several GB of RAM, ample disk space,
internet access, and a substantial first-build time. BUILD_JOBS=1 reduces concurrency.
The app's Gradle heap defaults to 4 GB; set GRADLE_HEAP (for example, 6g)
if the release shrinker needs more memory. Other compiler processes use additional RAM.
On success, artifacts/android/ contains:
bitwarden-foss.apk: a locally signed, non-debuggablefdroidReleaseAPK containing arm64-v8a, armeabi-v7a, x86_64, and x86 native SDK libraries.SHA256SUMS: checksum of the signed APK.sources.txt: exact Android and SDK revisions and the selected flavor.LICENSE-android.txtandLICENSE-sdk.txt: upstream GPL license texts.
Check and install it with:
(cd artifacts/android && sha256sum -c SHA256SUMS)
adb install -r artifacts/android/bitwarden-foss.apkThe app uses the upstream package ID com.x8bit.bitwarden. Its local signing key
differs from Bitwarden's store/release keys, so it cannot update an official installation.
Subsequent local builds use the same key and can update each other. A debug APK is
not used for this target.
Every default build checks GitHub for the latest published stable Android
Password Manager release whose tag ends in -bwpm, resolves the tag to an exact
commit, and fetches that commit. Drafts, prereleases, and Authenticator releases
are excluded. Releases are compared by publication time across all API pages.
The builder reads the SDK version from the selected checkout's version catalog
and resolves its matching commit automatically. sources.txt records the release
tag and both exact commits. Cached build outputs do not skip the release check.
The APK retains the checked-in local-build version metadata. Upstream's release CI overrides that metadata when publishing store builds.
The Android fdroid flavor
excludes the standard flavor's Firebase, Google Play billing/review, and ML Kit
dependencies. Those integrations, including Firebase push sync and ML Kit card
scanning, are therefore absent. This is Bitwarden's flavor in its own F-Droid
repository; this build is not an official F-Droid build or a reproducibility certification.
The SDK is compiled locally from sdk-internal,
whose mobile binding crate offers GPL-3.0 licensing. The build enables upstream's
bitwarden_ensure_non_commercial compile-time guard, builds no commercial feature,
and substitutes the locally published SDK using upstream's supported
localSdk=true setting in the untracked user.properties configuration file.
An external Gradle init script routes
that SDK's LOCAL version exclusively to Maven Local, so a GitHub Packages token
is unnecessary. It does not edit upstream repositories or build files. Every build
checks that neither upstream checkout has changes to tracked files before exporting
the APK. No source patches are carried or reapplied when updating.
The Android SDK/NDK are build tools downloaded
from Google; their SDK licenses are accepted during image creation.
To check, pull, and build the latest release, run:
./build-clients --target androidANDROID_REF defaults to latest. An existing full-SHA ANDROID_REF in the
shell environment or .env pins the build instead; remove that setting or set it
to latest to enable automatic release selection. Clear an old SDK_REF override
as well so the SDK follows the selected Android release. Release lookup failures
stop the build; the builder does not silently use an older cached release.
For a reproducible build of a specific release:
- Choose a stable Password Manager release from
upstream Android releases.
Password Manager tags end in
-bwpm; Authenticator tags are a different app. - Resolve the selected tag to its full commit SHA (requires Git on the host):
ANDROID_TAG=v2026.9.1-bwpm # Replace with the release you want.
ANDROID_REF=$(git ls-remote https://github.com/bitwarden/android.git \
"refs/tags/$ANDROID_TAG" "refs/tags/$ANDROID_TAG^{}" \
| awk 'NR == 1 { sha = $1 } /\^\{\}$/ { sha = $1 } END { print sha }')
test "${#ANDROID_REF}" -eq 40 && \
ANDROID_REF="$ANDROID_REF" ./build-clients --target android- To pin that revision for future builds, add or update the
ANDROID_REF=<resolved full SHA>entry in this directory's.env. Keep any other existing settings in that file. Remove an oldSDK_REFoverride if present; the builder selects the SDK matching the pinned Android revision automatically. - Check
artifacts/android/sources.txt, run the checksum command above, and install the APK. Keep the signing volume so the installed local app accepts the update.
The SDK commit lookup uses GitHub's public commits API once per new SDK revision,
then caches the result. An optional full-SHA SDK_REF override avoids that lookup;
the builder checks that it matches the SDK selected by Android.
The update process requires no upstream source edits, patch rebasing, or separate SDK version selection. Upstream can still change its required Android/JDK/NDK tools or build interfaces; no external builder can guarantee compatibility with every future release. If an update changes those requirements, update this builder's toolchain configuration using upstream's build documentation. Builds fail rather than silently altering upstream code.
This Android revision has no upstream build-time server URL option. The APK keeps upstream's default server selection. Set the server through the client's server selector, or use upstream's supported managed-device configuration:
| Application configuration key | Type | Example value |
|---|---|---|
baseEnvironmentUrl |
string | https://vault.example.com |
Configure this for package com.x8bit.bitwarden through an Android device/profile
owner's MDM/EMM application restrictions. This is deployment configuration outside
the APK and survives client upgrades without source customization. It requires a
managed device or profile; an ordinary APK installation does not supply application
restrictions. See Bitwarden's managed-client instructions
and individual-client setup.
Compose keeps downloads/build outputs in android-build-cache and
android-maven-cache, and the generated signing key/password in android-signing.
The signing volume must be preserved and backed up if you want to update installed
local builds. docker compose down -v removes it, along with the caches.
The key is never copied to the exported artifacts or built image. The source cache
is managed by the builder: tracked changes there are reset before each build.
Changes belong in the build recipe, rather than in the cache.
./build-clients --target browserThis builds upstream's production OSS extension for Chromium (Manifest V3)
and Firefox (Manifest V2), and exports into artifacts/browser/:
bitwarden-foss-chrome.zipandbitwarden-foss-firefox.zip: unsigned extensions.SHA256SUMS: checksums of both ZIPs.sources.txt: client commit, release tag, OSS variant, locked SDK package version and source commit, and Node version.LICENSE-clients.txtandLICENSE-sdk.txt: upstream GPL licenses.
The builder follows upstream's OSS browser workflow:
it exports the original root files, browser sources and libraries to a disposable
build tree, excluding bitwarden_license/, installs the locked npm dependencies,
and removes @bitwarden/commercial-sdk-internal. It runs the regular browser
webpack configuration, rather than build:bit:*. The published OSS SDK package
selected by the lockfile supplies the WASM SDK; this target does not rebuild it
from Rust. Export requires a successful build, a module graph with no commercial
source/package references, a valid manifest, and the SDK WASM artifact. The upstream
checkout is checked for tracked changes. No source patches or lockfile edits occur.
Node 24 matches this release's .nvmrc; a change in upstream's required Node major
stops the build with a toolchain update message. npm downloads are cached in the
browser-build-cache volume, while the build tree is recreated on every run.
Upstream's build scripts reserve an 8 GB Node heap; allow additional memory for
parallel minification workers and several GB of disk space.
Verified with browser-v2026.9.3: both production builds, ZIP integrity and
checksums, OSS module checks, and unchanged bytes for all 5,887 original files in
the exported build tree. A Chromium runtime smoke test reached the login form
through normal worker startup and first-run onboarding. Firefox was compiled and
its artifact checked, but was not tested in a running Firefox instance.
(cd artifacts/browser && sha256sum -c SHA256SUMS)
mkdir -p artifacts/browser/chrome-unpacked
unzip -o artifacts/browser/bitwarden-foss-chrome.zip -d artifacts/browser/chrome-unpackedIn Chromium, open chrome://extensions, enable Developer mode, choose
Load unpacked, and select that directory. Keep the same directory for updates;
empty it, extract the new ZIP into that same path, and click Reload. See Chrome's local
installation instructions.
These artifacts do not carry store signatures or automatic store updates.
In Firefox, open about:debugging, choose This Firefox, then Load Temporary
Add-on, and select the Firefox ZIP. This installation lasts until Firefox
restarts. Regular Firefox requires Mozilla signing for persistent installation;
the builder does not submit or sign extensions. The upstream Firefox extension ID
is retained, so use a separate profile or remove the store extension before testing.
See temporary installation
and Firefox signing requirements.
Safari packaging requires macOS and is deferred with iOS.
Every default browser build checks all GitHub release pages for the latest
published stable browser-v… release, resolves its full commit SHA, and fetches it.
This repository uses client-specific tag prefixes; -bwpm applies to the mobile
repositories. Drafts, prereleases, and other clients' releases are excluded, and
lookup failures stop the build rather than reuse a stale release.
To update, run ./build-clients --target browser again. BROWSER_REF defaults to
latest; remove any old pin from the shell or .env to restore automatic updates.
To pin a release, choose its tag from upstream browser releases,
resolve it with git ls-remote as in the Android example (using the clients
repository and browser-v… tag), and set BROWSER_REF=<full SHA> in .env or
on the build command. Check the selected release and commit in sources.txt.
The production browser build exposes no default server URL override in its webpack configuration. Select the self-hosted server in the extension's login screen. Upstream also supports browser managed-storage policies; its managed-settings documentation describes the environment setting. Development-only flags do not configure a production extension. This target adds no source customization for the server URL.
./build-clients --target clientsCurrently this builds Android, then the browser extensions. Each service has its
own client profile and the shared clients profile. Use the wrapper or explicit one-shot services to build;
docker compose --profile clients up also selects the existing server services.
Remaining upstream repositories:
- Web, desktop, and CLI
- iOS is deferred: it requires macOS/Xcode, and no Mac builder is available. A hosted macOS workflow could build an unsigned archive; installing on an iPhone also requires Apple signing and provisioning.