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
- Install Docker Desktop and open it (on Windows, use it inside WSL2 with Docker: tested). Korely needs 4 GB of free memory.
- 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 | shcurldownloads the installer and| shruns 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. - 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.
| Name | What it is |
|---|---|
korely-api-1 | Korely: the dashboard, the API and the MCP server, on port 8000. |
korely-db-1 | The database: Postgres with pgvector. |
korely_pgdata | The disk the database keeps its data on. It stays when Korely is stopped, and goes with ./korely uninstall --yes. |
korely_default | The private network the two programs talk over. |
korely-facts-dir-1 | A 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:
curl -fsSL https://raw.githubusercontent.com/korely-ai/korely/main/install.sh | KORELY_PROVIDER=gemini shGemini, 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
--versionstops before anything is downloaded, saying so; it also refuses--buildof an older release and a--from-localcheckout 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
--versionstops at once, saying to use that release's owninstall.shor 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 57a77fbaad61with 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_URLmust 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:
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 from127.0.0.1:docker network inspect <directory>_defaultnames 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
/healthkeeps 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 fromPUBLIC_BASE_URL, or fromDOMAIN, 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-Foritself, 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 totrusted_proxiesin the Caddyfile'sserversblock.
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:
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
./korelyin 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_idor anagent_id(why). - Without installing anything, the same code runs as Korely Cloud, with a free plan: the Quickstart.