Self-hosting · Docker Compose
Run Kairos on your own machine
Kairos is a Next.js server plus a PostgreSQL database, and both are wired together in the repository with Docker Compose. One command builds the image, starts the database, applies the migrations and serves the app on127.0.0.1:3000— no accounts to create, no outside services, nothing to configure first.
Quick start
Four commands, about a minute. Everything below assumes a terminal in the folder you cloned into.
- 1
Get the code
Clone the repository and step into it. Nothing needs to be installed on the host — the toolchain lives inside the image.
Terminalgit clone https://github.com/mahmoud-ath/Kairos.git cd Kairos - 2
Decide how sign-in works
Copy the sample environment file. For a private, single-workspace installation the only line you need to change is
KAIROS_AUTH_DISABLED=true— there is then no sign-up, and everyone who can reach the port shares one workspace.Terminalcp .env.example .env # then edit .env: KAIROS_AUTH_DISABLED=trueIf you want real accounts instead, fill in
NEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_ANON_KEY. Those two are inlined at build time, so after changing them the image has to be rebuilt — a plaindocker compose up -dwould keep the old values without complaining. - 3
Build and start
Compose builds the app image, starts PostgreSQL beside it, waits for the database to accept connections, runs
prisma migrate deployand then serves the app.Terminaldocker compose up -d --build - 4
Open it
Kairos is published on the loopback interface, so it is not reachable from your network unless you change that. Your workspace starts empty: add a task with the quick-add field and it is saved straight away.
Browserhttp://127.0.0.1:3000
What the stack actually does
- Two containers.
kairosserves the app andkairos-dbruns PostgreSQL 16. The app container runs as the unprivilegednodeuser. - Your data lives on a volume. The database is stored on the
kairos-dbvolume, never inside the image — so rebuilding the app keeps your tasks. - Migrations run on start. Upgrading is a rebuild and a restart; there is no separate migration step to remember, and no restart loop if the database is still booting.
The app is designed as a single instance — it is not meant to be scaled horizontally. A health check polls /api/health, which also verifies the database, so docker compose ps tells you whether the whole stack is actually working.
docker compose ps # both services healthy?
curl -s localhost:3000/api/health # {"ok":true,"database":"ok"}
docker compose logs -f kairos # migrations and warningsEveryday commands
| Task | Command |
|---|---|
| Follow the logs | docker compose logs -f kairos |
| Stop, keeping your data | docker compose down |
| Update to the latest code | git pull && docker compose up -d --build |
| Restart after editing .env | docker compose up -d --build |
docker compose down -v deletes the volume — and with it every task, habit and category. Export a JSON backup from Settings first if you are not sure.
Configuration
Everything is read from .env at the repository root. The values most people touch:
| Variable | Default | What it does |
|---|---|---|
KAIROS_PORT | 3000 | Host port the app is published on. |
KAIROS_BIND_HOST | 127.0.0.1 | Host interface to bind. Loopback = not reachable from your network. |
KAIROS_DB_PASSWORD | kairos | PostgreSQL password. Only read when the volume is first created. |
NEXT_PUBLIC_APP_URL | http://localhost:3000 | Public origin, used by canonical links, the sitemap and social cards. |
KAIROS_NO_INDEX | false | "true" asks every crawler to stay away. |
The daily reminder is optional and off until you configure it: generate a Web Push key pair, put the public key next to the private one in .env, and rebuild. Without keys, Settings simply says notifications are not configured for this deployment — nothing else changes.
Before you put it on the internet
The defaults are deliberately conservative: the app listens on 127.0.0.1, so it is reachable only from the machine running it. If you want it on your LAN or a domain, change that knowingly:
- Never leave
KAIROS_AUTH_DISABLED=trueon a port that other people can reach — every visitor would share the one workspace. - For a public domain, put a reverse proxy (Caddy, nginx, Traefik) in front for TLS, and set
NEXT_PUBLIC_APP_URLto the real origin before building. - Use Supabase sign-in (or an authenticating proxy) rather than opening a single shared workspace to the world.
Your data, and getting it out
Tasks live in PostgreSQL. Two independent ways exist to keep or move them:
- JSON backup — Settings → Backup & data → Export JSON writes one versioned file with tasks, subtasks, habits, categories, history and settings. Import it back into any Kairos instance, including this one after a reset.
- The volume — the
kairos-dbvolume survives rebuilds and restarts. Back that volume up the same way you back up any PostgreSQL database if you want the byte-level copy.
Task transfer is also available without a full backup: switch on Settings → Header tools and the top bar gains a JSON export/import menu for the task list alone.
Prefer not to run a server?
The hosted version needs nothing installed, and the data model is the same — the JSON backup moves your workspace between the two in either direction.