Disclaimer: This project is a Work in Progress (WIP). It runs fine, but hasn't been thoroughly tested yet.
A self-hosted, private Android App Store built with Kotlin Multiplatform.
ApkDepot serves two main purposes:
- For Enthusiasts: A simplified, self-hosted Play Store alternative. Upload APKs once, then install/update them from an Android client or web dashboard.
- For Developers: A full-stack Compose Multiplatform reference project featuring shared UI, state, and networking across Android, Wasm (Web), and Ktor (Backend).
Web Client: App Details |
Android Client: Update Flow |
- Private APK Library: Centralize your family/personal APK collection.
- Frictionless Updates: Android client detects installed versions and offers one-tap updates.
- Single Deployable: Backend + API + Bundled Wasm Web UI in one executable JAR.
- Modern Tech Stack: 100% Kotlin (Ktor, SQLDelight, Koin, Compose Multiplatform).
Clone the repo and run the full stack:
./gradlew :server:runFullStackDefault Behavior:
- HTTP only: Starts on
http://0.0.0.0:8083(no SSL/HTTPS enabled by default). - Local Storage: Creates
apkdepot.dband anapk_storage/folder in the project root.
Then open:
- Public dashboard:
http://localhost:8083 - Admin dashboard:
http://localhost:8083/#admin(admin credentials are set on first run)
This builds one runnable JAR containing:
- Ktor backend/API
- the compiled Wasm web UI bundled as static assets
./gradlew :server:buildDeployableRun it (Defaults):
java -jar server/build/libs/server-all.jarRun with Environment Variables (Example):
To change the port or storage location:
export PORT=8090
export APK_STORAGE_ROOT=/var/data/apkdepot
java -jar server/build/libs/server-all.jarThe Ktor plugin includes a Docker task:
./gradlew :server:runDockerThe server is configured via application.conf:
server/src/main/resources/application.conf
You can override settings via environment variables:
| Env Variable | Description |
|---|---|
PORT |
Server port |
SSL_PORT |
HTTPS port |
SSL_KEYSTORE_PATH |
Path to keystore |
SSL_KEYSTORE_ALIAS |
Keystore alias |
SSL_KEYSTORE_PASSWORD |
Keystore password |
SSL_PRIVATE_KEY_PASSWORD |
Private key password |
JWT_SECRET |
JWT secret |
JWT_ISSUER |
JWT issuer |
JWT_AUDIENCE |
JWT audience |
CORS_ALLOWED_HOSTS |
Comma-separated allowed CORS hosts |
CORS_ALLOW_ANY_HOST |
Allow any CORS host (true/false) |
APK_STORAGE_ROOT |
Root dir for APK + screenshot files |
- Android Emulator: Works out-of-the-box. The app defaults to
http://10.0.2.2:8083(the special alias for your host machine's localhost). - Physical Device:
- Tap the Settings (gear icon) in the Android app.
- Update Host URL to match your server's LAN IP (e.g.,
http://192.168.1.50:8083). - Note: If using HTTP, ensure you are on a trusted network.
- Web Client (Wasm): Automatically infers the API URL from the browser's address bar. No configuration needed.
ApkDepot follows a clean separation between the Ktor backend and the client applications (Android & Wasm), sharing significant business logic and UI code.
🛠️ Detailed Architecture Diagrams (Containers, Components, Sequences)
This project showcases a production-style Kotlin Multiplatform setup:
| Component | Technology | Highlights |
|---|---|---|
| Common logic | Compose / Kotlin Multiplatform | Shared ViewModels, repositories, DTOs, networking, navigation. |
| Android client | Jetpack Compose | Shares most UI with Web. Uses PackageManager for install/update state. |
| Web client | Compose Multiplatform for Web (Wasm) | Same UI as Android; provides download flows. |
| Backend | Ktor | API + SQLite + serves bundled Wasm SPA. |
| Database | SQLite + SQLDelight | Type-safe DB access. |
| DI & state | Koin + Coroutines | DI with StateFlow-driven MVVM. |
- Unified deployment: The Wasm frontend is bundled into the Ktor fat JAR (custom Gradle tasks).
- “Smart” configuration:
- Web: infers API base URL from
window.origin. - Android: persists server configuration.
- Web: infers API base URL from
- Storage split: binaries + screenshots on filesystem (easy backup), metadata in SQLite.
- Platform-specific actions: Shared
PublicActionButtonsUI renders Download on Web and * Install/Update* on Android.
- HTTP & Clipboard: browsers block the Clipboard API on insecure contexts (HTTP). This affects token copy on LAN.
- Workaround: use
localhostor enable HTTPS.
- Workaround: use
- Polling vs push: to avoid external dependencies, the Android client polls for updates when opened (no background push notifications).
- Wasm maturity: Kotlin/Wasm is still evolving; it requires a modern browser.
- Single admin: intended for a single-admin setup (no multi-user permission system).
To enable the Clipboard API on other devices in your network, serve over HTTPS.
This generates:
- a local Certificate Authority (CA) you install on your phone once
- a server certificate signed by that CA
- a
keystore.jksyou point Ktor to
From the repo root:
./scripts/generate-dev-https.sh \
--cn apkdepot.local \
--san "DNS:apkdepot.local,DNS:localhost,IP:127.0.0.1,IP:192.168.1.10"Then configure the server (env vars are easiest):
export SSL_KEYSTORE_PATH=.tls/keystore.jks
export SSL_KEYSTORE_ALIAS=apkdepot
export SSL_KEYSTORE_PASSWORD=changeit
export SSL_PRIVATE_KEY_PASSWORD=changeitInstall the CA certificate on Android:
- Copy
.tls/ca/ca.cer(recommended) or.tls/ca/ca.pemto your phone. - Install it as a CA certificate (Settings | Security | Encryption & credentials | Install a certificate | CA certificate).
- If your device asks for confirmation/warnings: that's expected for user-added CAs.
Important:
- Your browser URL must match the certificate SAN (hostname/IP). If you browse via
https://192.168.1.10:8443, make sure that IP is in--san. - Keep
tls/ca/ca.keysecret.
The Android client is configured for local/LAN use by default, which means it currently allows cleartext HTTP traffic to make home-server setup frictionless.
If you’re using ApkDepot outside a trusted network (or you simply prefer stricter defaults), it’s a good idea to:
- enable HTTPS on the server and access it via
https://... - tighten Android’s network rules by updating
composeApp/src/androidMain/res/xml/network_security_config.xml
That file controls whether the app allows cleartext traffic and which certificate authorities it trusts.
This project is in Beta/MVP status. PRs and issue reports are welcome.
Development commands:
- Run server + web:
./gradlew :server:runFullStack - Run Android app:
./gradlew :composeApp:installDebug - Lint:
./gradlew ktlintCheck
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.












