Korely

Get Started

Install Self-hosted

The same code that runs as Korely Cloud, installed on your own machine with Docker.

The code is on GitHub under Apache 2.0: github.com/korely-ai/korely. Its README is the source of these steps; this page repeats them so you can read them next to the rest of the documentation, and if the two ever differ, follow the README.

Install

  1. Install Docker Desktop and open it (on Windows, use it inside WSL2 with Docker: tested). Korely needs 4 GB of free memory.
  2. Download and run the installer. Open a terminal (on a Mac: the Terminal app), paste this line and press Enter:
    Terminal window
    curl -fsSL https://raw.githubusercontent.com/korely-ai/korely/main/install.sh | sh
    curl downloads the installer and | sh runs it. The installer works in a new folder, korely, and nowhere else: it downloads Korely (about 0.7 GB to download and about 2.4 GB on disk, the first time), starts it and creates your account.
  3. Open the address it prints, http://127.0.0.1:8000, and sign in with the login and password it prints. It also prints an API key: that is what your agents use. The password and the key are shown once. The key expires after 90 days (the installer prints the date); make a new one on the dashboard's API keys page.

On a Mac, without a terminal: download Korely Installer, open it and double-click Korely Installer. It checks Docker Desktop, asks where to install and, if you want, your model key, then installs in a Terminal window you can watch, and opens your dashboard at the end. It runs the same installer as the line above.

Running the line again starts what is there and creates nothing new. To read the installer before running it: curl -fsSLO …/install.sh, then sh install.sh --help.

What the installer prints

Two programs run: Korely and its database. The other lines are Docker setting them up.

NameWhat it is
korely-api-1Korely: the dashboard, the API and the MCP server, on port 8000.
korely-db-1The database: Postgres with pgvector.
korely_pgdataThe disk the database keeps its data on. It stays when Korely is stopped, and goes with ./korely uninstall --yes.
korely_defaultThe private network the two programs talk over.
korely-facts-dir-1A one-off helper: it lets Korely write to the facts folder, then stops. "Exited" is normal.

Your model key

The one model call is the writer that turns memories into facts, and it is billed to you. Name your provider and the installer asks for the key without showing it:

Terminal window
curl -fsSL https://raw.githubusercontent.com/korely-ai/korely/main/install.sh | KORELY_PROVIDER=gemini sh

Gemini, Scaleway, Mistral, OpenAI and OpenRouter work; Scaleway writes in Europe, in Paris. Without a key the install completes and the dashboard's first screen asks for one (Skip for now works too: memories are stored and searched, and facts arrive when your agents write them).

Installing a particular release

The installer installs the release its own copy carries. To name one, add --version X.Y.Z. The installer checks the files it downloads against checksums it carries only for its own release, so any other release also needs --unverified. sh install.sh --help lists every option.

  • The oldest release it installs is 1.0.9 (since 1.0.22). An older --version stops before anything is downloaded, saying so; it also refuses --build of an older release and a --from-local checkout of one. 1.0.9 is the first release published under the repository's present name: the images before it are under the old name, which the installer does not pull, and 1.0.8 was never published. An older release is installed by hand, as its own README says.
  • Only published releases install. Up to 1.0.21 the published releases are 1.0.9, 1.0.10, 1.0.11, 1.0.17, 1.0.18 and 1.0.21; the Releases page on GitHub is the list to trust. A release's copy of the installer checks only its own release's files, so another --version stops at once, saying to use that release's own install.sh or to add --unverified; with --unverified, a version that was never published stops at the download of its files, asking whether it is a release of the repository.

Stopping, restarting and the database

  • Going back to 1.0.22. Korely 1.0.23 adds one migration (three columns on the API keys). To go back, run alembic downgrade 57a77fbaad61 with the 1.0.23 image first, then start 1.0.22: the 1.0.22 image stops at its start on a database at a revision it does not know. An old key secret still in its overlap stops working at once; the new one goes on.
  • Stopping. When Docker stops Korely, the API gives the requests still running 5 seconds, cancels what is left, and hands back the memories it was writing so the next start picks them up at once (the image's command carries --timeout-graceful-shutdown 5, inside Docker's 10-second stop timeout). If you start the API with a command of your own, add that flag with a value below your stop timeout. See what a restart does to a memory being written.
  • Connect straight to Postgres. DATABASE_URL must not go through PgBouncer, or any pooler, in transaction mode: each process holds a lock on its own database session, and that lock is how another process knows whether the memories it claimed are still being worked. A pooler in transaction mode runs each transaction on whichever session is free, so the lock would not be the process's. The compose file Korely ships connects directly. No pooler has been tested, in either mode.

Your own proxy in front

The API takes the client's address from X-Forwarded-For and the scheme from X-Forwarded-Proto, and believes them only from the peers named in FORWARDED_ALLOW_IPS. From Korely 1.0.23 the image sets it to caddy, the name of the proxy service in the shipped docker-compose.yml: the name is looked up again when that container comes back at another address, and every other peer that sends the headers is ignored, so it keeps its own address and scheme. The address decides the login lock, the address the audit trail keeps for a sign-in, and the caps per network. Up to 1.0.22 the image said * and believed every peer, so a container on the compose network, or a port 8000 published by mistake, could choose its own address. With the shipped compose, and with the installer's setup, with or without Caddy, there is nothing to do.

Before upgrading, if a proxy of yours stands in Caddy's place, name it with one line in .env, then run docker compose up -d:

Terminal window
FORWARDED_ALLOW_IPS=<your proxy's name, address or network>
  • An entry is a name (a service on the same compose network), an address or a network (10.0.0.0/24); several go comma-separated. A proxy on the same machine reaches the API from the compose network's gateway, not from 127.0.0.1: docker network inspect <directory>_default names it.
  • Without that line, every client appears with your proxy's own address (one login lock for everybody, the caps per network shared), the API takes every request for plain HTTP, and /health keeps its details from the callers your proxy relays. The API's log says so once: [proxy] a request carried X-Forwarded-For .... The OAuth and MCP addresses it advertises come from PUBLIC_BASE_URL, or from DOMAIN, and do not change.
  • A FORWARDED_ALLOW_IPS=* you set yourself is kept, and keeps the old behaviour.
  • If a command: of yours starts the API with --proxy-headers, drop that flag: the image turns uvicorn's own reading off (--no-proxy-headers), and with both the application takes every request for one relayed by a stranger.
  • Every request that comes while the API makes its first lookup of those names waits for it, at most 2 seconds, so the proxy's own requests are not taken for a stranger's in that moment (since 1.0.26). A lookup that fails is no error for the requests waiting for it. The one-time [proxy] warning is said only of a peer that sent the headers and that the names, once answered, leave out.
  • The proxy should write X-Forwarded-For itself, with the address of the connection, or append to it; the API reads it from the right, skipping only the peers it believes. With Caddy behind another proxy (a CDN), add that proxy to trusted_proxies in the Caddyfile's servers block.

The address you serve it at. A proxy that serves the install at an address other than https://DOMAIN (another name, a path, another port) names it in .env, then docker compose up -d:

Terminal window
PUBLIC_BASE_URL=https://memory.example.com

It is the address the OAuth and MCP metadata advertise, and the one the MCP door's 401 points to: a client that connected to one address and is told another refuses to go on (RFC 9728 section 3.3, RFC 8414 section 3.3). Left unset, it is https://DOMAIN, with HTTPS_PORT when that is not 443.

Its log is yours to keep clean too. The shipped Caddyfile logs no request address at all, neither the request's path and query nor the Location of a redirect: a path such as /v1/users/<end user>/memories carries an end user, and an erasure never reaches a log. The API's own access line writes each path by its route (/v1/users/{end_user}/memories). A proxy of yours that logs full paths or query strings keeps them, for as long as it keeps its log.

After the install

  • To stop it, start it again or remove it, use ./korely in that folder. The commands are in the Install guide on GitHub, which also covers a server with your own domain and a certificate.
  • Connect an agent over MCP or the SDK with the key the installer printed: Korely MCP and the SDK. Every write names a user_id or an agent_id (why).
  • Without installing anything, the same code runs as Korely Cloud, with a free plan: the Quickstart.