Installera Nessie
Kör Nessie på den infrastruktur du väljer, från en enda maskin till en hanterad molntjänst.
Det här behöver du innan du börjar
Nessie är en pnpm-monorepo med Node-tjänster och två statiska bundles. Oavsett hur du kör det måste samma fyra saker finnas på plats först.
- PostgreSQL med
pgvector. Det är inte valfritt, och det går inte att byta ut – migreringarna körCREATE EXTENSION vectoroch skapar kolumner av typenvector(1024). Produktionen körpgvector/pgvector:pg17. - Node 22 och pnpm. Varje Dockerfile i
infrastructure/docker/bygger pånode:22-slim, och repositoryt låser sin pakethanterare i rotenspackage.json(pnpm@10.22.0). - En OpenAI-kompatibel modellendpoint och en nyckel till den. Agenter och embeddings går båda genom den. Se ”Konfigurera inferens” nedan.
- S3-kompatibel objektlagring, för varje driftsättning som inte är den lokala utvecklingsmiljön. Uppladdningar på lokal disk nekas utanför läget
local, och nekandet är ett startfel, inte en uppstart med begränsad funktion – se ”En maskin eller en hanterad molntjänst”.
Docker och en reverse proxy behövs för containervägen som beskrivs längre ned, och en domän och TLS för allt som nås från internet.
Hårdvarudimensionering – lägsta CPU, minne och disk för ett visst antal personer – anges inte någonstans i repositoryt, så den här sidan anger den inte heller.
Komponenterna och hur de hänger ihop
| Komponent | Vad den är | Ingress |
|---|---|---|
api/ | Fastify-API:t för HTTP och WebSocket. Alla klienter pratar med det och inget annat. | Ja |
worker/ | Agentorkestrering och bakgrundskörningar. Hämtar jobb från kön och binder ingen port. | Nej |
admin/ | Produktgränssnittet – ett statiskt paket byggt med Vite och serverat av nginx. | Ja |
web/ | Den publika webbplatsen. Statisk, valfri och inte kopplad till att köra produkten. | Ja |
| PostgreSQL | Databasen, jobbkön och realtidsbussen – alla tre. | Nej |
| Objektlagring | Bilagor, avatarer och filer i kunskapsbasen. S3-kompatibel; MinIO i produktion. | Nej |
gateway/ | En valfri push-relä för APNs och FCM. Av om du inte aktiverar den medvetet. | Ja |
API:t och worker:n är en och samma image. De två paketen är tätt sammanlänkade, så Dockerfile.app bygger hela teamet en gång, och worker-containern åsidosätter kommandot med node worker/dist/index.js. I läget local finns ingen worker-container alls: API:t kör worker:n inbäddad i sin egen process.
Det finns ingen Redis. Jobbkön och realtidstransporten är båda Postgres-baserade, av ett beslut snarare än av förbiseende – Google Cloud-konfigurationen tar bort sina Pub/Sub- och Redis-moduler av samma skäl.
Portar
I lokal utveckling ligger API:t som standard på 5454 och admin på 5455. En utcheckning kan ta ett eget par genom att sätta NESSIE_API_PORT och NESSIE_ADMIN_PORT, i miljön eller i repositoryts .env; varje verktyg i repositoryt, webbläsartesterna inräknade, läser samma två värden. Välj paret innan du startar och behåll det: flyttar du en tjänst som körs till en annan port pratar admin med fel API.
Produktionen är något annat och påverkas inte av den regeln. API-containern lyssnar internt på 5554 (fastställd med NESSIE_API_PORT) och push-reläet på 5556, båda bakom reverse proxyn.
Köra på en maskin, för utveckling
Repositoryt levereras med en Compose-fil som bara innehåller en databas, och det är allt den lokala loopen behöver.
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgresContainern publicerar Postgres på 55432, med användare, lösenord och databas alla namngivna nessie. Lägg anslutningssträngen i en .env i repositoryts rot – API:ts dev-skript läser den – och kör sedan migreringarna och bygg worker:n en gång, eftersom API:t i läget local laddar worker:n från sin byggda dist:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev kör API:t och admin parallellt med hot reload, på 5454 och 5455. Kontrollera att API:t svarar med GET /api/health.
Postgres och kravet på pgvector
Tre tillägg skapas av migreringarna själva – vector, pg_trgm och pgcrypto – så rollen som kör dem måste ha rätt att skapa tillägg. På en hanterad Postgres är det första du ska kontrollera.
Vektorbredden är en del av schemat, inte en inställning. thoughts.embedding, thought_recalls.query_embedding och knowledge_page_chunks.embedding är vector(N)-kolumner, och N anges en gång som EMBEDDING_DIMENSIONS i packages/schemas/src/embedding.ts – för närvarande 1024, den inbyggda bredden för embedding-modellen som produktionen använder. Varje embedding-anrop skickar den bredden, så en leverantör som svarar med en annan bredd misslyckas tydligt i stället för att skriva vektorer som databasen förkastar. Byter du till en embedding-modell med en annan bredd måste du ändra den konstanten, skriva en migrering som ändrar typen på de tre kolumnerna och göra om alla embeddings. Vektorer med olika bredd kan inte konverteras, så migreringen nollställer dem i stället för att trunkera dem.
Om du kör Nessie bredvid andra applikationer på ett delat Docker-nätverk, ge dess databascontainer ett eget namn. Projektets egen Compose-tjänst heter nessie-postgres och aldrig postgres, eftersom tjänstnamnet blir ett DNS-alias på det delade nätverket, och en krock gör att postgres:5432 fördelas i tur och ordning mellan två databaser.
Konfiguration
Konfigurationen under körning är lagrad i lager: en nessie.config.json-fil monterad skrivskyddad i API:t och worker:n, med miljövariabler ovanpå. Kopplingen mellan de två är ConfigEnvMap i packages/config, och den är den auktoritativa listan.
NESSIE_MODE väljer driftsättningens form: local (standard, och utvecklarvägen), hosted eller selfHosted – vilket är det en egenhostad installation sätter. Den stänger av dev-inloggning och kräver en CORS-tillåtelselista.
Det här är inställningarna som en selfHosted-driftsättning inte startar, eller inte fungerar utan:
| Variabel | Varför |
|---|---|
DATABASE_URL / NESSIE_DB_URL | Postgres-anslutningssträngen. |
NESSIE_AUTH_SECRET | 32 byte hex (openssl rand -hex 32). Signerar sessioner och bootstrap-token. Uppstarten kastar fel utan den utanför läget local, och den måste vara samma värde på varje replik och över alla driftsättningar – annars dör alla sessioner vid varje release. |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | En JSON-nyckelring och etiketten som väljer den aktiva roten, krävs utanför läget local. Det här är en separat rot från signeringshemligheten – nyckelringen krypterar varje varaktig hemlighet som Nessie lagrar. Använd inte den ena som den andra. |
NESSIE_STORAGE_PROVIDER | Måste vara s3. filesystem är standard och nekas i lägena hosted och selfHosted, så en driftsättning som inte sätter något här misslyckas vid start i stället för att skriva uppladdningar till en disk som ingen annan container kan läsa. |
NESSIE_CORS_ORIGINS | Admin-ursprunget. Admin och API är separata ursprung, och API:t måste tillåta det ursprung det betjänar. |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | Används för att skapa OAuth-redirect-URI:er utanför en HTTP-begäran. API:t kastar fel vid en begäran utan den första. |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | Dit inferensen går. Se nedan. |
infrastructure/compose/.env.prod.example har exakt den form alla har, med de valfria blocken – objektlagring, hemlighetsvalv, SSO och kopplad e-post – utkommenterade på sina platser. Hela katalogen, med varje valfri inställning, finns i docs/deployment/configuration.md.
Driftsättning med Docker Compose
Kopiera .env.prod.example till .env bredvid docker-compose.prod.yml och fyll i den, starta sedan databasen, bygg, migrera och starta. Ordningen spelar roll: migreringarna och seed-stegen körs mot en databas som redan är igång, innan någon tjänst svarar.
COMPOSE="docker compose -f infrastructure/compose/docker-compose.prod.yml"
bash infrastructure/compose/ensure-encryption-key-ring.sh infrastructure/compose/.env
$COMPOSE up -d nessie-postgres
$COMPOSE build nessie-api nessie-admin nessie-web
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api prisma:migrate:deploy
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:connectors
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:apps
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api reconcile
$COMPOSE up -dProduktions-Dockerfilerna kör varje paketets lint innan det byggs, så ett lint-fel är ett byggfel.
Reconcile-steget är inte valfritt
reconcile seedar varje organisations standardpolicyregler och fyller i efterhand de skyddade MCP- och Personal Assistant-verktygstilldelningarna. API:t gjorde det förr vid uppstart på varje replik; nu ansluter det, lyssnar och gör ingenting annat, så utan det här steget har den första organisationen inga policyregler och varje agentbindning nekas. Steget är idempotent, så att köra det vid varje driftsättning kostar ingenting – och en uppgradering som görs för hand måste också köra det.
Det första ägarkontot
En ny selfHosted-installation har inga användare, så API:t skapar en engångs-bootstrap-token och loggar en uppstarts-URL. Token lagras i Postgres i stället för i en process, så alla repliker är överens om den.
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrapÖppna https://<your-admin-host>/bootstrap?token=<token> och skapa ägarkontot. Token går ut efter 15 minuter; starta om API-tjänsten för att skapa en ny.
Om du konfigurerar SSO finns det inget bootstrap-steg alls. Bootstrap-läget stängs av i samma ögonblick som en extern autentiseringsleverantör aktiveras – annars skulle det kapa inloggningsskärmen – och den första personen som loggar in via SSO skapar standardteamet och blir dess ägare.
Konfigurera inferens
Chatten är OpenAI-kompatibel. NESSIE_MODEL_BASE_URL är endpointen, NESSIE_MODEL_API_KEY bearer-nyckeln och NESSIE_MODEL_PROVIDER anger adaptern. Nessie kompilerar adaptrar för openai, kimi och deepseek, och når allt annat genom den generiska OpenAI-kompatibla kopplingen. NESSIE_MODEL_NAME anger driftsättningens standardmodell, som en enskild agents egna modellval fortfarande väger tyngre än.
Embeddings dirigeras separat, eftersom chattleverantören kanske inte erbjuder dem alls – DeepSeek har till exempel ingen embeddings-endpoint. Varje ej satt NESSIE_EMBEDDING_*-fält ärver chattleverantören, så en driftsättning som inte sätter något av dem embeddar via chatt-endpointen. Sätt NESSIE_EMBEDDING_PROVIDER, NESSIE_EMBEDDING_MODEL och, på en proxy som dirigerar efter tjänst, tjänstsegmentet, när chattleverantören inte kan embedda. Att göra fel här misslyckas tyst i stället för högljutt: minnesåterkallning och sökning i kunskapsbasen faller tillbaka till en lexikal kanal och fortsätter, och loggar kb_search: query embedding failed på vägen.
Nessies egen förbrukningsredovisning – tokenledgern per organisation och budgetspärren – påverkas inte av vilken endpoint du pekar den mot, och fungerar likadant oavsett om signerad attribuering per anrop är konfigurerad eller inte.
Inloggning
Nessie autentiserar personer genom UnlikeOtherAI, som också hanterar organisations- och teamstrukturen. Inloggningssidan i admin visar en enda knapp: “Login / Register”.
Integrationen är ett config-JWT-flöde snarare än standard-OIDC: API:t serverar ett signerat RS256-konfigurationsdokument på GET /api/auth/sso/config och motsvarande JWKS på GET /.well-known/jwks.json, båda på API-värden. Att sätta upp den är en engångssekvens. Generera ett RSA-2048-nyckelpar och sätt UOA_CONFIG_JWT_PRIVATE_KEY_B64, UOA_CONFIG_JWT_KID, UOA_DOMAIN, UOA_CONFIG_URL, UOA_JWKS_URL, UOA_REDIRECT_URL och UOA_CONTACT_EMAIL. Klicka på knappen en gång, vilket skapar en integrationsbegäran. Få den godkänd, vilket skickar en engångslänk med klienthemligheten till kontaktadressen. Sätt UOA_CLIENT_SECRET och starta om API:t. kid måste vara unik per domän.
Bakom en domän, med TLS
En driftsättning serverar tre namn: den publika webbplatsen, admin och API:t. Admin- och API-ursprungen är inte utbytbara. Dockerfile.admin bakar in VITE_API_BASE_URL i bundlen vid byggtiden, så varje byggd admin-artefakt – även en skrivbordsbuild som bäddar in den – måste byggas mot API-ursprunget. Bygger du den mot adminens egna ursprung löser /api/auth/providers till adminens HTML-skal, och inloggningen fastnar på ”Loading providers...”.
Produktionen avslutar TLS i Caddy, som hämtar certifikat per värdnamn via HTTP-01 och inte behöver något plugin för DNS-leverantören för det. Peka proxyn mot admin- och webcontainrarna på port 80 och mot API:t på dess interna port.
Sätt NESSIE_API_TRUSTED_PROXY_HOPS till antalet proxyer framför API:t – 1 bakom en enda reverse proxy. Standardvärdet är 0, vilket ignorerar X-Forwarded-For helt. Det är ett beslut med två konsekvenser: det är också det som nycklar autentiseringens hastighetsbegränsningar, så för lågt värde gör att varje klient delar proxyns hink, och för högt värde gör att en klient kan förfalska sin egen adress. Välj hellre för lågt och mät.
Att servera varje organisation och team på sin egen subdomän är möjligt och av som standard. Det styrs av NESSIE_TEAM_HOST_BASE_DOMAIN. Ej satt betyder att organisationer inte dirigeras efter värdnamn alls, och certifikatutfärdningens spärr NESSIE_TLS_CHECK_KEY nekar allt så länge den inte är satt. Det är rätt standard för en driftsättning som inte använder funktionen.
Hälsokontroller
| Endpoint | Fråga den besvarar |
|---|---|
GET /api/health | Lever den här processen? Misslyckas bara när den har börjat tömmas. |
GET /api/health/ready | Får den här repliken ta emot nya förfrågningar? Det här är den du ska peka en lastbalanserare mot. |
GET /api/ops/health | Hur mår driftsättningen? En rapport för en människa – worker-heartbeats, djupet på kön, döda jobb – inte en probe. |
Vid SIGTERM tömmer API:et sig i stället för att dö där det står. NESSIE_SHUTDOWN_TIMEOUT_MS begränsar tömningen och är som standard 25 sekunder. Håll den under den nådeperiod din orkestrerare ger containern, annars dödar körmiljön processen först och tömningen ger ingenting.
Uppgradering
En uppgradering är prisma migrate deploy mot den befintliga databasen, sedan seed-stegen, sedan reconcile. Samma fyra kommandon som vid den första driftsättningen.
Vägen är testad för den rutt en egenhostare faktiskt tar, i stället för mot en ny databas: CI återställer en ögonblicksbild tagen tjugo migreringar bakåt, migrerar den till det aktuella huvudet, validerar schemat och kör ett röktest över kärntabellerna. Varje release inom det bakre fönstret är en beprövad uppgraderingskälla. Migreringsmappar är oföränderliga när de väl är committade, och ett lint-steg underkänner bygget om en av dem byts namn på, numreras om, tas bort eller ändras, eftersom alla fyra bryter migrate deploy för en databas som redan registrerat den gamla raden.
En misslyckad migrering pausar varje driftsättning efter den. migrate deploy stannar vid första felet och vägrar varje senare migrering med P3009, så installationen stannar kvar på den gamla releasen tills någon rensar det för hand. Projektets egen produktion tillbringade ett dygn med att förkasta varje driftsättning på det sättet. Att rensa en innebär att bekräfta att den misslyckade migreringen verkligen rullades tillbaka – inget av objekten den skapar ska finnas, och det den ändrar ska fortfarande ha sin ursprungliga form – och först då markera den som återrullad. En migrering som halvt tillämpats är inte en kandidat.
Backup och återställning för den egenhostade Compose-driftsättningen är inte dokumenterad i repositoryt, så den här sidan beskriver ingen procedur för det.
En maskin eller en hanterad molntjänst
Den enmaskins Docker Compose-vägen ovan är den som är beprövad: det är vad projektets egen produktion kör, på en enda server, med databas, objektlagring, API, worker, admin och webbplats som containrar bakom en delad reverse proxy.
Att skala bortom en maskin ändrar tre saker, och alla tre är påtvingade snarare än rekommenderade. Utanför läget local kör Nessie API:t och worker:n som separata processer, och som flera kopior av var och en. Därför nekas varje förmåga som förutsätter att en process äger maskinens disk: filsystemslagring för objekt (ett startfel), mallar för körmiljöer med leverantören docker (inaktiva, och provisioneringen misslyckas tydligt) och de inbyggda verktygen file_read, file_write och file_glob (ett misslyckat verktygsresultat som agenten kan läsa). Varje nekande nämner inställningen, läget och vad du ska använda i stället.
För en hanterad molntjänst innehåller repositoryt en Terraform-konfiguration i infrastructure/terraform/ som riktar sig mot Google Cloud: API:t som en Cloud Run-tjänst med minst en instans – den håller en beständig Postgres-LISTEN-klient och kör underhållssvep, så att skalning till noll stoppar realtidsleveransen – worker:n som en Cloud Run-worker-pool, eftersom den inte binder någon port och en Service aldrig skulle bli redo, ett migreringsjobb som grindar varje utrullning, Cloud SQL för PostgreSQL 17 och en GCS-bucket nådd via den S3-kompatibla backenden.
Den vägen är skriven, inte genomgången. Repositoryt säger det rakt ut, och det gör den här sidan också. Inget Google Cloud-projekt finns för den, ingenting i konfigurationen har planerats eller tillämpats mot ett riktigt projekt, multipart-uppladdningen till GCS är otestad mot en riktig bucket, worker-pool-resurstypen är ovaliderad, och antalet betrodda proxyhopp i den är en platshållare som ska mätas snarare än en mätning. Admin och den publika webbplatsen är inte modellerade där alls. Behandla den som en utgångspunkt du verifierar, inte som en stödd driftsättning.
Var en driftsättnings data finns, vad programvaran håller och vad den medvetet inte håller är en egen sida: Gjord i EU och var dina data finns.