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. 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.

    Terminal
    git clone https://github.com/mahmoud-ath/Kairos.git
    cd Kairos
  2. 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.

    Terminal
    cp .env.example .env
    # then edit .env: KAIROS_AUTH_DISABLED=true

    If you want real accounts instead, fill in NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. Those two are inlined at build time, so after changing them the image has to be rebuilt — a plain docker compose up -d would keep the old values without complaining.

  3. 3

    Build and start

    Compose builds the app image, starts PostgreSQL beside it, waits for the database to accept connections, runs prisma migrate deploy and then serves the app.

    Terminal
    docker compose up -d --build
  4. 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.

    Browser
    http://127.0.0.1:3000

What the stack actually does

  • Two containers. kairos serves the app and kairos-db runs PostgreSQL 16. The app container runs as the unprivileged node user.
  • Your data lives on a volume. The database is stored on the kairos-db volume, 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.

Terminal
docker compose ps                        # both services healthy?
curl -s localhost:3000/api/health        # {"ok":true,"database":"ok"}
docker compose logs -f kairos            # migrations and warnings

Everyday commands

Common Docker Compose commands
TaskCommand
Follow the logsdocker compose logs -f kairos
Stop, keeping your datadocker compose down
Update to the latest codegit pull && docker compose up -d --build
Restart after editing .envdocker 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:

Environment variables
VariableDefaultWhat it does
KAIROS_PORT3000Host port the app is published on.
KAIROS_BIND_HOST127.0.0.1Host interface to bind. Loopback = not reachable from your network.
KAIROS_DB_PASSWORDkairosPostgreSQL password. Only read when the volume is first created.
NEXT_PUBLIC_APP_URLhttp://localhost:3000Public origin, used by canonical links, the sitemap and social cards.
KAIROS_NO_INDEXfalse"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=true on 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_URL to 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-db volume 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.