A self-hosted, local-first notes app focused on offline support, safer note management, mobile usability, simpler self-hosting, and a native Android companion app.
Originally based on Glass Keep.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
GlassKeep focuses more strongly on:
- 🔄 local-first usage and offline support
- 🗑️ safer note deletion with Trash / restore
- 📱 better mobile usability
- ✏️ a real WYSIWYG / live-formatting editor for text notes
- 🪟 side-by-side note reading and comparison
- 🤖 a native Android companion app
- 📺 an Android TV layout designed for couch use and remote control
- 🌍 a cleaner and more extensible i18n foundation
- 🛠️ simpler self-hosting
- ✨ a broad polish / stability pass
- 🎨 a deeper overhaul of the drawing mode
- 💬 configurable AI assistant with local or remote endpoints
- 🔐 server-side encryption & passkeys
- 🔔 in-app update notifications
- 🚀 one-click in-app updates from the admin panel — live progress, cancel/rollback, AI-translatable changelog after install
- 🎙️ audio notes
- 🖼️ refreshed branding and icons across the web app, PWA, favicon, Android launcher, and TV banner
For a more complete and structured overview of the changes made over time, see:
GlassKeep also includes:
- 🔐 authentication and multi-user support
- 👑 admin panel
- 🗝️ secret recovery key login
- 📝 Markdown notes, checklists, drawings, and images
- 👥 real-time collaboration on notes
- 📦 import / export with cross-device duplicate detection
- 📥 Google Keep import (Takeout
.zip— full colour, images, line breaks) - 🤖 optional AI assistant via any OpenAI-compatible endpoint (Ollama, Open WebUI, LiteLLM, OpenAI, …)
- 📲 PWA support
- ⏰ note reminders with notifications everywhere — in-app, browser/PWA push, and on the Android app even when fully closed (no Google, no third-party service)
A native Android companion app is available for GlassKeep, making self-hosted mobile usage more convenient.
The Android app is a WebView wrapper for GlassKeep and does not necessarily change with every project release.
The same APK also runs on Android TV — the app detects leanback hardware (or the ?tv=1 URL override) and switches the React frontend to a dedicated TV layout designed for the couch and the D-pad. No separate build, no separate install: phone, tablet and TV all share one codebase. See section 4 of IMPROVEMENTS.md for the full TV layout details.
The launcher icon, the Android TV banner, the PWA install icon, and the favicon have all been redrawn from a single master so the app looks coherent across every surface.
Current APK version: 1.4.6
The Android source code is available in the
android/directory.
From APK 1.3.0 onward, the Android app supports passkeys natively (fingerprint, face unlock, hardware security keys, password managers — the same authenticators you'd use from your browser). It works out-of-the-box with the official APK on a regular HTTPS install.
⚠️ Both the APK and the server must be up to date. Android passkeys and cross-device QR sign-in rely on endpoints introduced in server v2.3.7. Pair APK1.3.0+with serverv2.3.7+; mixing an old server with a new APK (or vice-versa) silently falls back to password-only login.
📖 Full setup guide, prerequisites, custom-build instructions and troubleshooting: see
PASSKEYS.md. Worth a read even for the smooth path — the non-standard-port and reverse-proxy edge cases trip a lot of self-hosted setups.
Run as root on a clean Debian-based system:
curl -fsSL https://raw.githubusercontent.com/Victor-root/glasskeep-enhanced/main/install.sh | sudo bashThe script is designed to make installation as simple as possible:
-
it directly offers install / update / uninstall
-
it asks for the important information up front
-
it creates the admin account
-
it generates the configuration automatically
-
it sets up the systemd service
-
it can handle HTTPS depending on your setup:
- reverse proxy
- self-signed certificate
- custom SSL certificate
-
it optionally sets up at-rest encryption to protect notes in the database (you can enable it later from the admin panel if you prefer)
This is the main installation method recommended for this project.
Docker is also available, especially for NAS and similar environments. The same docker-compose.yml works whether you deploy from a terminal or from a graphical interface — pick the one that matches your setup.
services:
glasskeep:
image: ghcr.io/victor-root/glasskeep-enhanced:latest
container_name: glasskeep
restart: unless-stopped
ports:
- "8080:8080"
environment:
ADMIN_EMAIL: "your-admin-username"
ADMIN_PASSWORD: "choose-a-strong-password"
volumes:
- ./data:/data
# Lets the admin panel update the container in one click.
- /var/run/docker.sock:/var/run/docker.sockOnce the container is up, open http://<your-host>:8080 and sign in with the admin username and password you chose.
🖥️ Command line (Linux / macOS / WSL)
Edit the ADMIN_EMAIL and ADMIN_PASSWORD values below, then paste the whole block into your terminal — it creates the folder, writes the compose file and starts the container in one go.
mkdir -p ~/glasskeep && cd ~/glasskeep && cat > docker-compose.yml <<'EOF'
services:
glasskeep:
image: ghcr.io/victor-root/glasskeep-enhanced:latest
container_name: glasskeep
restart: unless-stopped
ports:
- "8080:8080"
environment:
ADMIN_EMAIL: "your-admin-username"
ADMIN_PASSWORD: "choose-a-strong-password"
volumes:
- ./data:/data
# Lets the admin panel update the container in one click.
- /var/run/docker.sock:/var/run/docker.sock
EOF
docker compose up -d🐳 Don't have Docker yet?
Linux — one-liner that works on Debian / Ubuntu / Fedora / Arch / …:
curl -fsSL https://get.docker.com | sudo shmacOS / Windows — install Docker Desktop and make sure it's running before continuing.
🐙 Portainer
- Open Portainer → select your environment → Stacks → Add stack
- Give it a name (e.g.
glasskeep) - Choose Web editor and paste the compose file above
- Edit the
ADMIN_EMAIL/ADMIN_PASSWORDvalues - Click Deploy the stack
The container appears in the Containers tab once it's pulled and started.
📦 Synology (Container Manager / DSM 7.2+)
- Open Container Manager → Project → Create
- Set Project name to
glasskeepand Path to a folder of your choice (e.g./docker/glasskeep) - Source → Create docker-compose.yml and paste the compose file above
- Edit the
ADMIN_EMAIL/ADMIN_PASSWORDvalues - Click Next → Done
On DSM the Docker socket is owned by root:root (there is no docker group). GlassKeep's entrypoint detects this and grants the in-container node user access to it automatically, so the in-app "Update now" button works after the container's first start — no extra setup. If you added the socket mount to an existing project, recreate the container once (stop then start the project in Container Manager, or docker compose up -d --force-recreate) so the entrypoint can apply the permission.
🟧 Unraid
The simplest route is the Compose Manager plugin (Community Apps):
- Install Compose Manager from Community Apps if you don't have it
- Go to the Docker tab → Add New Stack → name it
glasskeep - Click the gear → Edit Stack → Compose and paste the compose file above
- Adjust the
ADMIN_*values, click Save - Click Compose Up
You can also use the native Add Container form, but the compose route preserves the Docker-socket mount for one-click updates.
🏠 CasaOS
- Click the ➕ icon on the dashboard → Install a customized app → Import
- Paste the compose file above
- Edit the
ADMIN_EMAIL/ADMIN_PASSWORDvalues - Click Install
The app shows up on the dashboard with a launcher tile pointing to port 8080.
🛟 TrueNAS SCALE
TrueNAS SCALE doesn't ship a generic compose UI, so the simplest route is the built-in shell:
- Open System Settings → Shell
- Create a folder for the stack:
mkdir -p /mnt/<your-pool>/glasskeep && cd $_ - Paste the compose file above into
docker-compose.yml - Run
docker compose up -d
The easiest way is to open the admin panel and click "Update now" — the new image is pulled and the container is replaced automatically. Your data is preserved.
If you prefer the command line:
cd ~/glasskeep && docker compose pull && docker compose up -d💡 Existing install without one-click updates? You have two options:
- Patch the existing compose — add
- /var/run/docker.sock:/var/run/docker.sockunder thevolumes:block of your currentdocker-compose.yml, then re-deploy the stack once.- Redeploy from the current example — replace your
docker-compose.ymlwith the one at the top of this section (keep yourADMIN_*values and your./datavolume), then re-deploy. The current example already ships the right configuration, so you'll never have to touch the file again.Either way, the in-app Update now button takes over from there.
GlassKeep no longer ships an embedded local model — it was too small to be genuinely useful. Instead, it connects to any OpenAI-compatible chat endpoint, so each instance picks what fits its hardware, privacy needs and budget — fully local with Ollama / Open WebUI, or remote via OpenAI, OpenRouter, …
Two AI features are available once configured:
- 🔎 Global AI search — ask questions across your notes from the search bar. The backend pre-selects relevant notes before calling the model, and only cites notes it actually received (no fabricated sources).
- 🗒️ Per-note assistant — discuss the currently opened note with the AI. Conversations are temporary by default; a save button can keep them per note.
Admins control AI at the instance level: disable it entirely, configure a server-side provider (optionally shared with users so the API key stays hidden), or let each user bring their own endpoint in their settings.
⚠️ Notes sent to a remote provider leave your GlassKeep instance. For sensitive data, prefer a local setup such as Ollama + Open WebUI on your LAN/LXC.
Recommended starter model (light enough to run even on CPU-only setups):
ollama pull qwen3:4b-instruct-2507-q4_K_M📘 Full setup guide — base-URL gotchas, model recommendations, privacy notes, admin/user config flows →
AI_CHANGES.md
-
Copy
src/i18n/locales/en.jsto a new file, e.g.src/i18n/locales/it.js, and translate all values -
In
src/i18n/index.js:- import the new locale:
import { it } from "./locales/it"; - add the language code to
SUPPORTED_LANGUAGES:export const SUPPORTED_LANGUAGES = ["fr", "en", "it"]; - add the native display name to
LANGUAGE_NATIVE_LABELS:it: "Italiano" - extend the dict selector:
const dict = locale === "fr" ? fr : locale === "it" ? it : en;
- import the new locale:
-
In
server/index.js, add the new code to the validation allowlist inPATCH /api/user/profile(the line that checkslang !== "fr" && lang !== "en") -
Rebuild the app:
npm run build
The language selector in the settings panel will automatically show the new option. Missing keys fall back to English automatically.
- More translations with better RTL language support
- Make the Android app available on F-Droid
- (open — suggestions welcome)
JWT_SECRETis automatically generated by the native install script- if you run the server outside the script, you must provide your own valid secret
- serving the app behind HTTPS is still recommended
- the recovery secret key should be treated like a password
GlassKeep originally started from the foundation of Glass Keep, a project I genuinely liked from the start.
What began very modestly as a French translation gradually evolved into a much broader project focused on local-first usage, offline support, mobile usability, simpler self-hosting, and a more complete day-to-day experience.
GlassKeep now follows its own direction, but it still keeps clear attribution to the original project and its author.
Thanks to nikunjsingh93 for the original foundation that made this possible.
Thanks to @Rikhtar for active testing, bug reports, UX feedback.
MIT
Originally based on Glass Keep by nikunjsingh93. Original attribution remains preserved in the project.















