Local-first long-form fiction assistant: .NET 8 Web API (OData, EF Core, SignalR), PostgreSQL, Ollama, and an Angular client.
GETTING_STARTED.md — step-by-step setup for non-developers (Docker Desktop, copy-paste commands, Windows / Mac / Linux). No coding required.
Pre-built production API and web images are published to GitHub Container Registry (GHCR). They are intended to be public so anyone can docker pull without logging in. Maintainers: for each package under GitHub → Packages, open Package settings and set Change package visibility to Public (the default for new packages may be private).
| Image | Package |
|---|---|
| API | ghcr.io/jholovacs/creativelongform/creativelongform-api |
| Web (nginx + Angular) | ghcr.io/jholovacs/creativelongform/creativelongform-web |
Tags follow Git releases (e.g. v1.0.0) and latest. Pin a version for reproducible deploys: set IMAGE_TAG=v1.0.0 when using the compose file below.
- Docker (Docker Desktop, Engine, or Podman with compose compatibility)
- This repository cloned or at least
docker-compose.prod.ymldownloaded
-
Pull and start the stack (Postgres, Ollama, API, nginx) using the production compose file:
docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d
-
Pull an Ollama model (first run only; models persist in the
ollamavolume):docker compose -f docker-compose.prod.yml exec ollama ollama pull llama3.2Or set
OLLAMA_MODELin a.envfile next to the compose file (see docker-compose.prod.yml) and recreate the API container after changing it. -
Open the app at http://localhost:8080 (default
WEB_PORT). The API is on http://localhost:5094 by default (API_PORT).
In Production, the API does not expose Swagger; use GET /health on the API for a quick check. Database migrations run automatically on startup; development-only seed data is not applied.
- Postgres: Override defaults with environment variables or a
.envfile:POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB,POSTGRES_PORT. If you change the user or database name, update thepostgresservice healthcheck in docker-compose.prod.yml to match. - Ports:
WEB_PORT,API_PORT,OLLAMA_PORTadjust published ports. - Ollama:
OLLAMA_MODELshould match a model you have pulled (ollama listinside theollamacontainer).
CI publishes images when you push a v* tag or run the Publish Docker images workflow manually (see .github/workflows/docker-publish.yml). To build locally instead, use the main docker-compose.yml with docker compose up -d --build or make deploy.
Everything can run in Docker: Postgres, Ollama, the API, and nginx serving the built Angular app and proxying /api, /odata, /hubs, and /swagger to the API.
- Docker Desktop (WSL2 backend on Windows is fine)
- Make (optional): Git for Windows includes
makein Git Bash, or use WSL
First-time or fresh deploy (recommended): interactive Ollama CPU vs GPU (when an NVIDIA GPU is detected, GPU is the default; press Enter to accept), model choice, then build and start everything:
make setupThis writes a local .env with OLLAMA_MODEL (used for Ollama__WriterModel / Ollama__CriticModel in Compose) and, if you choose GPU, a gitignored docker-compose.override.yml that adds gpus: all to the Ollama service. It runs docker compose up -d --build, then either ollama pull for a library tag or, under advanced options: download a GGUF from an HTTPS URL (requires curl or wget on the host) or copy a local .gguf file from disk, then ollama create in the container under a name you provide. On Docker Desktop for Windows with Git Bash, large GGUF files are copied by streaming into the container via stdin (avoids Docker’s cp/compose cp tar path, which often fails with “closed pipe” on multi‑GB files). Other methods are only fallbacks.
In GPU mode, setup reads total VRAM for the first GPU (nvidia-smi) and only lists curated models that fit: Qwen2.5 14B instruct (Q4_K_M) (qwen2.5:14b-instruct-q4_K_M), Mistral Nemo 12B instruct (Q4_K_M) (mistral-nemo:12b-instruct-2407-q4_K_M), and Gemma3 27B instruct (Q3_K_M) (gemma3:27b-instruct-q3_K_M).
Manual / repeat deploy (same Compose files and .env as above)—rebuilds the API and web Docker images and restarts containers:
make deploy
# same as: make docker-up (uses plain BuildKit progress so long image builds are visible)
# or: DOCKER_BUILDKIT=1 BUILDKIT_PROGRESS=plain docker compose up -d --buildmake build runs dotnet build and npm run build in web/ (compile only on your machine). It does not rebuild Docker images or redeploy the stack; use make deploy after changing code you want running in Docker.
If you did not use make setup, pull a model after the stack is up:
-
Pull a model into the Ollama container (first time only; models persist in the
ollamavolume):make docker-pull-models MODELS=llama3.2
Or:
docker compose exec ollama ollama pull llama3.2 -
Open the app
URL Purpose http://localhost:8080 Angular UI (via nginx) http://localhost:8080/swagger Swagger (Development) http://localhost:5094/swagger API directly (bypass nginx) http://localhost:11434 Ollama (host port; optional) -
Align
Ollama:WriterModelandOllama:CriticModelwith the model you pulled. With Docker Compose, setOLLAMA_MODELin.env(seemake setup) or override in Compose; appsettings.json defaults apply when not using Compose.
Changing the model after setup: OLLAMA_MODEL in .env is the single value Compose substitutes into the API container. After you edit .env (or change the model name in Ollama), run docker compose up -d --force-recreate api or make deploy so the running API process gets the new variables. If you run the API on the host with dotnet run, .env is not loaded — set Ollama__WriterModel / Ollama__CriticModel in the environment or in appsettings to match the model Ollama actually serves (ollama list).
The API container uses:
ConnectionStrings__Default→ Postgres servicepostgresOllama__BaseUrl→http://ollama:11434/apiOllama__WriterModel/Ollama__CriticModel→${OLLAMA_MODEL:-llama3.2}from.envwhen presentDisableHttpsRedirection→true(HTTP behind nginx)ASPNETCORE_ENVIRONMENT=Developmentso migrations and dev seed run on startup
make docker-down
# or: docker compose downIf you run the API and Angular on the host but want only DB in Docker:
make db-up
# or: docker compose up -d postgresDefault connection string matches docker-compose.yml and appsettings.json.
Requires .NET 8 SDK, Node.js (LTS), and either Docker for Postgres only or a local Postgres instance.
-
Postgres —
make db-upor your own Postgres with the sameConnectionStrings:Defaultshape. -
Ollama — install from ollama.com and run it on the host, or use the Ollama container alone (
docker compose up -d ollama) and setOllama:BaseUrltohttp://localhost:11434/api. -
Migrations —
make migrate(the API also applies migrations on startup). -
API —
make dev-api(default http://localhost:5094, see launchSettings). -
Angular —
make dev-web→ http://localhost:4200. The dev app usesenvironment.apiBaseUrlpointing athttp://localhost:5094(see web/src/environments).
Production builds used in Docker use an empty API base URL so the browser calls the same origin and nginx proxies to the API.
- api/CreativeLongform.Domain — entities and enums
- api/CreativeLongform.Application — orchestrator, narrative state DTOs, abstractions
- api/CreativeLongform.Infrastructure — EF Core, Ollama HTTP client
- api/CreativeLongform.Api — OData controllers, SignalR hub, generation API
- web — Angular client (OData + SignalR)
- web/nginx — nginx config for the
webimage - api/Dockerfile, web/Dockerfile — container builds
- docker-compose.prod.yml — production stack using published GHCR images (no local build)
- OData (JSON):
GET /odata/Books,Chapters,Scenes,GenerationRuns(with$filter,$expand, etc.) - Generation:
POST /api/scenes/{sceneId}/generationwith optional body{ "idempotencyKey": "..." }→{ "id": "<runGuid>" } - SignalR:
/hubs/generation— callJoinRun(runId)after starting a run; listen forStepStarted,AgentEditTurn,RepairAttempt,RunFinished,RunStarted
make test
# or: dotnet test CreativeLongform.sln and cd web && npm run test:ciGitHub Actions runs .NET and Angular tests on push to main and on pull requests that target main (see .github/workflows/ci.yml). Docker images are verified on each PR and published to GHCR on v* tags or manual workflow runs (see .github/workflows/docker-verify.yml and .github/workflows/docker-publish.yml).
- Application (
api/CreativeLongform.Application.Tests):LlmJson,AgenticEditLoop,WorldContextBuilder,MeasurementPromptFormatter. - API (
api/CreativeLongform.Api.Tests): OData EDM smoke test; integration tests for/healthand/odata/Booksusing Testcontainers (PostgreSQL) — Docker must be running. - Angular (
web):AppComponent, feature components, and HTTP services (Karma + Chrome headless).
Override connection string and Ollama settings via appsettings.Development.json, environment variables, or user secrets, for example:
cd api/CreativeLongform.Api
dotnet user-secrets set "ConnectionStrings:Default" "Host=localhost;..."
dotnet user-secrets set "Ollama:WriterModel" "llama3.2"In Docker Compose, override with environment: on the api service (see docker-compose.yml).
See LICENSE.