MimirDocumentationSign in
Documentation

Self-hosting

Mimir is one application container and one PostgreSQL 18 container with pgvector. Nothing else is required: jobs run inside the app process, and there is no Redis, no object store and no external queue.

Quick start

bash
cp .env.example .env
openssl rand -hex 32 # paste into MIMIR_SECRET
docker compose up -d

Two passwords in .env are required before the first start: POSTGRES_OWNER_PASSWORD and MIMIR_APP_PASSWORD. Both must be URL-safe, because they are interpolated into connection strings. The first boot creates the schema, the two database roles and the admin account from MIMIR_ADMIN_EMAIL and MIMIR_ADMIN_PASSWORD.

Behind a proxy

  • Set MIMIR_PUBLIC_URL to the address people actually use. It is never inferred from the request, because a forged Host header would poison emailed links.
  • Set MIMIR_TRUSTED_PROXIES to the proxy addresses. Without it Mimir cannot see a client address, so audit rows carry no IP and a non-empty IP allowlist denies everyone.
  • The container publishes on 127.0.0.1 only. Publish it on a public interface and the allowlist becomes advisory.

Upgrading from 0.2.0

0.3.0 moved to PostgreSQL 18. Dump with a 17 client, then restore into the new volume; there is no automatic migration. 0.4.0 has no schema change.

Browsing memory

Every memory is reachable in the web app at /app/projects/<slug>: a directory tree on the left, a reader on the right, search in the header, and a deep link for every memory (?path=auth/rotation). Saving an edit rewrites the body and clears the summary until the learning job writes a new one; deleting keeps the memory recoverable for 30 days, after which a nightly job removes it for good. Archiving a project hides it from every list and API call without touching its rows.

Every write keeps the body it replaced. Open a memory and follow History to see the last 20 bodies, diff one against what is live now, and restore it. A restore is an ordinary write, so the body it replaced becomes history too and a mistaken restore can be undone the same way. History is dropped when a memory is purged, 30 days after it was deleted.

Import an existing project

npx @mojoaar/opencode-mimir ingest . reads a folder and loads its documentation and configuration — READMEs, docs/, markdown, compose files, package.json — into the project under import/, where memory_search finds them. Source code is skipped by default, because the agent already has it on disk.

In a git repository the import lands in the same project your agents write to, so nothing is duplicated. A folder that is not a repository needs a name: --project notes creates notes on the first run and reuses it afterwards, which also means the same folder on another machine is a different project. --all imports every text file, and --dry-run prints what would be sent without sending it.

Sharing a project

A project can be shared with a person or with a team. Open a project, then Settings → Sharing, and grant either read or read and write. A team is a group of accounts with one owner and any number of members: Teams in the app creates one, adds people by email, and removes them again. Removing a member ends their access on their next request, because access is read from the database every time rather than cached in a session.

A share is never the owner. Whoever owns the project keeps every right — renaming, archiving, deleting, sharing again and changing settings — and a person or team with write access can read, write, delete and restore memories but cannot do any of those. Deleting a team takes its shares with it; the projects it could see are untouched.

An agent sees this too: project_list returns the projects shared with a team the token's user belongs to, under the role the team grants.

Your account

Settings → Profile (/app/settings/profile) is where a user changes their own name, email address and password. Changing the email asks for the current password and refuses an address that is already in use; the address is also how you sign in, so the next sign-in uses the new one. A password change signs out every other device and keeps the one making the change.

The same page lists the sessions signed in to the account, with the address and device each one came from, and lets a user revoke one device or sign out everywhere else. Revocation takes effect on that device's next request. An admin can still reset a password or MFA from Admin → Users; nobody can edit another user's profile from here.

Language

The signed-in app is available in English, Danish and German. The language menu sits in the header, next to the theme switcher, and the choice is saved on the account: the next page is already in that language on its first paint, on any browser you sign in from. A visitor who is not signed in gets the language their browser asks for, falling back to English.

The public pages are separate: / is English, /da Danish and /de German, each with its own address so a search engine can index them. These docs are English only.

Signing in and out

A user who forgets a password can ask for a link at /forgot-password: it is emailed when Resend is configured, and otherwise the admin sends it from Admin → Users → Reset password. The page answers the same way whether or not the address has an account, so it cannot be used to discover who has one. A link lasts an hour, works once, and signing in with it ends every other session for that account.

Health

/health and /api/health answer {"status":"ok"} anonymously and 503 with {"status":"unavailable"} when the database is unreachable. The authenticated form adds the version, uptime and database latency.

Self-hosting Mimir