a love letter to tangled (android, iOS, and a search API)
1# Deployment Walkthrough
2
3Twisted now deploys as one VPS Compose stack defined by
4`docker-compose.prod.yaml`. Coolify can still host that stack, but the Compose
5file is the source of truth.
6
7## Services
8
9- `postgres`: primary database on `pgvector/pgvector:pg17`
10- `migrate`: one-shot schema bootstrap via `twister migrate`
11- `api`: public HTTP service
12- `indexer`: private Tap consumer
13- `tap`: private Indigo Tap service
14- `llama-embeddings`: private llama.cpp server for future embedding work
15
16## Prerequisites
17
18- one VPS or Coolify host with Docker Compose
19- this repo available to the host
20- explicit `INDEXED_COLLECTIONS` and `READ_THROUGH_COLLECTIONS`
21- one shared `TAP_AUTH_PASSWORD`
22- a production `POSTGRES_PASSWORD`
23
24Use explicit search collections. Do not use `sh.tangled.*` in production.
25
26## Environment
27
28Required:
29
30- `POSTGRES_PASSWORD`
31- `TAP_AUTH_PASSWORD`
32- `INDEXED_COLLECTIONS`
33
34Common overrides:
35
36- `POSTGRES_USER` default `twisted`
37- `POSTGRES_DB` default `twisted`
38- `LOG_LEVEL=info`
39- `LOG_FORMAT=json`
40- `READ_THROUGH_MODE=missing`
41- `READ_THROUGH_COLLECTIONS=<explicit CSV>`
42- `READ_THROUGH_MAX_ATTEMPTS=5`
43- `HTTP_BIND_ADDR=:8080`
44- `INDEXER_HEALTH_ADDR=:9090`
45- `LLAMA_MODEL_REPO=nomic-ai/nomic-embed-text-v1.5-GGUF`
46- `LLAMA_MODEL_FILE=nomic-embed-text-v1.5.Q8_0.gguf`
47
48`llama-embeddings` is private and not part of search yet. It only keeps the
49model warm and cached for later adapter work.
50
51## Bootstrap
52
53From the repo root:
54
55```sh
56just vps-up
57```
58
59That sequence:
60
611. starts PostgreSQL and Tap
622. runs `migrate`
633. starts `api`, `indexer`, and `llama-embeddings`
64
65## Clean Reset
66
67For a fresh VM or full reset:
68
69```sh
70just vps-reset
71```
72
73This removes named volumes, recreates PostgreSQL, reapplies migrations, and
74starts the full stack from zero.
75
76## Coolify Notes
77
78If you run this through Coolify:
79
801. create one Docker Compose application
812. point it at `/docker-compose.prod.yaml`
823. set the env vars above in Coolify
834. expose only the `api` service through Traefik
84
85Do not add a separate PostgreSQL resource for this stack.
86
87## Checks
88
89- `api`: `GET /readyz` returns `200`
90- `indexer`: `GET /health` returns `200`
91- `indexer` can reach `ws://tap:2480/channel`
92- `llama-embeddings` serves embeddings on its internal port
93
94Then rebuild the serving dataset:
95
96```sh
97docker compose -f docker-compose.prod.yaml exec indexer twister backfill
98docker compose -f docker-compose.prod.yaml exec indexer twister enrich
99docker compose -f docker-compose.prod.yaml exec indexer twister reindex
100```
101
102Do not import old Turso data as the default migration path.
103
104## App Target
105
106For local app builds:
107
108```sh
109VITE_TWISTER_API_BASE_URL=https://<your-api-domain>
110```
111
112## Rollback
113
114- PostgreSQL restore is the rollback primitive
115- keep `--local` only as a temporary development fallback
116
117## Post-Deploy Checklist
118
119After a fresh deploy or indexer recovery:
120
1211. confirm `migrate` exited successfully
1222. confirm `api` returns `200` from `/readyz`
1233. confirm `indexer` returns `200` from `/health`
1244. in the `indexer` container run the following
125 1. run `twister backfill`
126 2. run `twister enrich`
127 3. run `twister reindex`
1285. watch `indexer` logs and confirm the Tap cursor keeps moving forward