Skip to content

Repository files navigation

keepIT logo

keepIT

A modern, real-time notes app you can run yourself.

Docker Android License: MIT Status

keepIT web app: masonry grid of notes, checklists and photos in the dark UI Β  keepIT Android app: a note with photos on a phone

Notes, checklists, lists, reminders and sharing in a fast web app and a native Android client, syncing live across your devices. Everything runs on your own server: no cloud account, no subscription, and nobody else holding your notes. Just want notes on your phone? The Android app also works entirely on its own, with no server at all.

Get it running with one Docker command, or use the Android app standalone.

Buy Me A Coffee

What you can do with it

  • πŸ“ Notes your way: text notes with rich formatting (bold, headings, lists, links, code), or checklists you tick off as you go.
  • πŸ–ΌοΈ Photos in your notes: attach images to any note, up to ten each. Location data is stripped from every upload, so sharing a photo doesn't share where you took it.
  • πŸŽ™οΈ Voice notes: record a thought on your phone instead of typing it, and play it back anywhere β€” on the phone or in the web app, straight from your list of notes without opening one. Recording works offline too.
  • πŸ—‚οΈ Stay organized: group notes into lists, pin the important ones, archive what's done, and find anything instantly with search. Deleted notes wait in the trash until you're sure.
  • ⏰ Reminders: once, or on a schedule (daily, weekly, monthly, yearly). On your phone they arrive as real notifications, even with the app closed, the screen locked, or no internet.
  • πŸ‘₯ Share notes: invite someone by email to view or edit a note with you. You each keep your own pins, lists and reminders; edits show up for everyone, live.
  • πŸ”„ Always in sync: change a note on one device and watch it update on the others. No refresh, no sync button.
  • πŸ“± Android app included: the same notes on your phone, with a home-screen widget for recent notes and one-tap capture. It works fully offline: read and edit anywhere, and your changes sync as soon as you're back online.
  • πŸ“΄ No server? No problem: the Android app also runs standalone. Notes, lists, photos, reminders and the widget all live on your phone, with nothing to set up. Connect a server later and everything moves into your account.
  • 🎨 Make it yours: a background color per note, and an accent color for the whole app.
  • πŸ’Ύ Take your notes with you: save everything you own β€” notes, checklists, lists, reminders and photos β€” as a single zip, and load it back into any keepIT account. On the web and on Android, standalone phones included, so a phone with no server still has a real backup. Your data, readable without keepIT, and no lock-in.
  • πŸ”’ Your notes stay yours: everything lives on your server, or only on your phone. No third-party cloud, no account with anyone but yourself.

Quick start

keepIT runs on your own machine or home server with Docker. One command, no database to set up:

docker run -d \
  --name keepit \
  -p 8080:80 \
  -v keepit-data:/data \
  -e Jwt__Key="your-random-secret-at-least-32-chars" \
  richy1989/keepit:latest

Then open http://localhost:8080 (or your server's address on port 8080) and create your account. That's it.

A few things worth knowing:

  • Jwt__Key is a secret that keeps your sign-ins secure. Replace it with any random string of at least 32 characters, and keep it the same across restarts.
  • Your data lives in the keepit-data volume. Back that up and you've backed up your notes.
  • Once your accounts are created, you can close public sign-up by adding -e App__AllowRegistration=false (recommended if your server is reachable from the internet).
  • Forgot password works without any mail server: the reset link is written to the server log (docker logs keepit), where you, the operator, can grab it. To have it emailed to users instead, configure SMTP with the Email__* settings below and set App__PublicBaseUrl to your instance's address. Reset emails are not sent without it; if it's missing, the server log says so at startup and the web app's Settings page shows a warning.
  • keepIT doesn't run as root. The app runs as the unprivileged user app (uid 1654). On start, the container makes the data folder that user's, so after updating from an older version your data (on Unraid, the appdata/keepit folder) shows 1654 as its owner. Start the container as usual, without --user. If that folder is on storage that can't change owners (a network share, for instance), make it writable for uid 1654.
  • Behind your own reverse proxy (Traefik, Nginx Proxy Manager, SWAG…)? If it keeps access logs, have it leave out query strings, or at least token and access_token: the web app's live-sync connection carries a sign-in token in its URL, and so does a password-reset link. keepIT's own logs blank both.
  • Running Unraid? A Community Apps template is included at deploy/keepit.unraid.xml.

Updating from an older version, or something not working? The FAQ covers what changes when you update, reverse proxies and HTTPS, email, and images. What's new in each version is in the CHANGELOG.

Prefer Docker Compose, Postgres, or building the image yourself?

Docker Compose (three containers: app, web server, and a PostgreSQL database), from a clone of this repo:

cp .env.example .env          # set JWT_KEY (32+ chars), optionally POSTGRES_PASSWORD
docker compose up -d --build  # builds everything locally, then starts the stack

Open http://localhost:8080. Data persists in named Docker volumes.

Use PostgreSQL with the single container instead of the built-in database, either with the discrete variables (POSTGRES_HOST is the switch; port/db/user default to 5432/keepit/keepit):

docker run -d \
  --name keepit \
  -p 8080:80 \
  -v keepit-data:/data \
  -e Jwt__Key="your-secret" \
  -e POSTGRES_HOST=<host> \
  -e POSTGRES_PASSWORD=<pass> \
  richy1989/keepit:latest

…or a full connection string (takes precedence when both are set):

docker run -d \
  --name keepit \
  -p 8080:80 \
  -v keepit-data:/data \
  -e Jwt__Key="your-secret" \
  -e "ConnectionStrings__Postgres=Host=<host>;Port=5432;Database=keepit;Username=keepit;Password=<pass>" \
  richy1989/keepit:latest

Build the image yourself (no Docker Hub needed):

docker build -f deploy/Dockerfile -t keepit:local .
docker run -d --name keepit -p 8080:80 -v keepit-data:/data \
  -e Jwt__Key="your-random-secret-at-least-32-chars" keepit:local
All settings (environment variables)
Variable Required Default Description
Jwt__Key yes (none) Random secret, min 32 chars, that keeps sign-ins secure. The variable the app actually reads; use it for docker run / Unraid / the single-container image.
JWT_KEY compose only (none) Convenience .env value that docker-compose.yml passes through as Jwt__Key. Not read directly by the app.
ConnectionStrings__Postgres no (built-in SQLite) Full Postgres connection string. If neither this nor POSTGRES_HOST is set, a zero-setup SQLite database is used. Takes precedence over the discrete POSTGRES_* variables below.
POSTGRES_HOST no (none) Postgres host, the friendlier alternative to a full connection string. Setting it switches the API to Postgres, built from the POSTGRES_* variables.
POSTGRES_PORT no 5432 Postgres port (discrete setup only).
POSTGRES_DB no keepit Postgres database name (discrete setup only).
POSTGRES_USER no keepit Postgres username (discrete setup only).
POSTGRES_PASSWORD no keepit Postgres password. Read by the API for the discrete setup, and by the Compose stack for both the db service and the connection string it hands the API.
App__AllowRegistration no true Whether new accounts may be created. On an internet-exposed instance: register your own accounts first, then set false to close public sign-up.
App__DataRoot no ./App_Data Directory for the database, security keys, and media.
App__ForwardedProxyHops no 1 Trusted reverse-proxy hops in front of the app: 1 for the plain setups above, 2 if you put another proxy (e.g. Traefik) in front.
App__Media__MaxImagePixels no 100000000 Largest image a note can take, in pixels (100 megapixels, above any phone photo that fits in 10 MB). Checked before the image is decoded, since a small file can claim a huge size and decoding costs memory by pixel. Lower it on a server with little memory.
App__PublicBaseUrl with SMTP (none) The address users open keepIT at (e.g. https://notes.example.com). Emailed password-reset links point here, and while it's unset no reset email is sent, since a link built from the incoming request could point anywhere. Optional without SMTP: the link written to the server log then uses the address you opened keepIT at.
Email__SmtpHost no (none) SMTP server for outgoing email (password-reset links). Needs App__PublicBaseUrl too. Leave empty to run without email; reset links then land in the server log.
Email__From with SMTP (none) From address, e.g. keepIT <no-reply@example.com>. Required once Email__SmtpHost is set.
Email__SmtpUsername no (none) SMTP login username. Leave empty (along with the password) for an unauthenticated relay.
Email__SmtpPassword no (none) SMTP login password, paired with Email__SmtpUsername.
Email__SmtpPort no 587 SMTP port: 587 for STARTTLS submission, 465 for implicit TLS (set Email__UseStartTls=false too).
Email__UseStartTls no true true = STARTTLS (port 587); false = implicit TLS (port 465). With STARTTLS the server must offer it, or nothing is sent: the test email in Settings then says so.
Email__AllowUnencrypted no false Lets STARTTLS mail go out in plain text when the server doesn't offer encryption. Only for a relay on a network you trust (e.g. a local Postfix on port 25); anywhere else it exposes reset links and the SMTP password. Settings shows a warning while it's on.
Auth__RefreshCookie__Secure no true (Compose) / false (single container) Whether the sign-in cookie is HTTPS-only even on plain HTTP. Over HTTPS (directly or through a TLS proxy) it always is. false lets a plain-HTTP address such as a LAN IP stay signed in; true refuses that, for an instance only ever reached over HTTPS.
Jwt__Issuer / Jwt__Audience no keepITCore / keepIT.api Advanced: token claims.
Jwt__AccessTokenMinutes / Jwt__RefreshTokenDays no 15 / 14 Advanced: how long sign-in tokens last.
ASPNETCORE_ENVIRONMENT no Production Set to Development for verbose logging and the API explorer at /scalar/v1.

The Compose stack sets most of these itself and reads only five values from .env: JWT_KEY, POSTGRES_PASSWORD, REFRESH_COOKIE_SECURE, FORWARDED_PROXY_HOPS, and ALLOW_REGISTRATION.

The Android app

The app in app/ brings your notes to your phone: offline-first, live sync, native reminder notifications, note sharing, and a home-screen widget. It works with your own server, or standalone without one.

No server? Use it standalone

Tap Use without a server on the sign-in screen and the app works entirely on your phone: notes, checklists, lists, images, reminders and the widget, with nothing to install anywhere else. Sharing and syncing with other devices need a server, so they're switched off.

When you do set up a server, open Settings β†’ Connect to a server and sign in: everything on the phone is uploaded into that account (alongside anything already in it) and syncs from then on. A photo the server won't take (over 10 MB or 100 megapixels, or HEIC) is saved to your gallery instead of being lost. Until then, standalone notes live only on the phone. There's no backup, and Settings β†’ Erase notes deletes them for good.

Get it

Get it on Obtainium Β Β  Get it on F-Droid

Obtainium is the easiest way: tap the badge, or add https://github.com/Richy1989/keepIT as a GitHub app source. It installs the APK and keeps it up to date as new releases land, straight from this repo and without a store account.

F-Droid carries keepIT in its main repository, so it updates alongside everything else you installed from there.

Prefer to do it by hand? Grab keepit-vX.Y.Z-universal.apk from the latest release and sideload it.

Every source ships the same signed build: F-Droid checks that the release APK reproduces from this repository and then publishes that developer-signed APK rather than one of its own. So you can move between F-Droid, Obtainium and a hand-downloaded APK by updating, with nothing to uninstall.

Build it yourself

Open app/ in Android Studio, or from the command line (requires the Android SDK):

cd app
./gradlew :app:assembleDebug     # build the APK
./gradlew :app:installDebug      # or install straight onto a connected phone

On first launch, enter your server address on the sign-in screen, or tap Use without a server to try it standalone. The address is the same URL you open in the browser; from the Android emulator, your own machine is http://10.0.2.2:5025. For reminders that fire on the minute even while your phone sleeps, grant Alarms & reminders in the app's Settings screen.

What's next

keepIT is a work in progress and actively developed. Up next:

  • πŸ–ΌοΈ Background images: use a photo as a note's background (attaching images already works).
  • βœ‰οΈ Invite anyone: share a note with someone who hasn't signed up yet.
  • πŸ“₯ Move in from Google Keep: import a Google Takeout export straight into keepIT.

For developers

Curious how it works, or want to hack on it? ARCHITECTURE.md holds the full design and the reasoning behind it. The short version: an ASP.NET Core (.NET 10) REST API plus SignalR for realtime, a React 19/TypeScript web app, and a Kotlin/Jetpack Compose Android app, all speaking the same API, with the C# DTOs as the single source of truth for the contract (the typed TypeScript client is generated from OpenAPI: cd web && npm run generate:api).

You'll need the .NET 10 SDK and Node.js 22+. No database setup needed: a SQLite dev database is created for you. One command builds and runs both halves, seeding test data on first run:

bash deploy/run-dev.sh        # Windows: ./deploy/run-dev.ps1

That serves the web app on http://localhost:5173 and the API on http://localhost:5025 (API explorer at /scalar/v1), signed in as test@test.com / Test1234#1234. Ctrl+C stops both.

Prefer to start the two halves yourself?
# 1) Backend on http://localhost:5025 (Scalar API UI at /scalar/v1)
dotnet run --project keepIT/keepITCore

# 2) Frontend on http://localhost:5173 (proxies /api to the backend)
cd web && npm install && npm run dev

Open http://localhost:5173 and register an account, or seed test data (test@test.com / Test1234#1234, plus lists and a variety of notes):

bash scripts/seed-dev-data.sh        # PowerShell twin: ./scripts/seed-dev-data.ps1

Why I built this

Honestly? I just wanted a simple notes app, and couldn't find one with the three things I actually cared about, so I built it myself. With a little AI help πŸ˜‰, modern problems require modern solutions.

The features I really wanted:

  • a simple notes app with a modern web UI
  • note sharing between different users
  • a native Android app, including a home-screen widget

It has since grown into a blazing-fast app with optimistic editing, lists, search, sharing, and real-time sync, so a note edited on one device shows up on your others without a refresh. I'm really happy with how this turned out.

Support

Running into a problem? Check the FAQ first, or open an issue.

keepIT is free and self-hosted: no accounts, no subscriptions. If it's useful to you and you'd like to say thanks, you can buy me a coffee β˜•. Much appreciated, but never expected.

License

Released under the MIT License. Β© 2026 Richard Leopold. Free to use, modify, and distribute; just keep the copyright and license notice.

About

A self-hosted notes app with sharing between users, real-time sync, search, and reminders - plus a native Android app with offline support and a home-screen widget

Topics

Resources

Stars

24 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages