services: app: # 發版時 release.yml 推的映像(linux/amd64、linux/arm64),標籤取自 .env 的 KNOTEBOOK_VERSION。 # Release 附件版的這個檔由 release.yml 產生:預設標籤換成該版版號、拿掉 build:(只能 pull)。 # 刻意不設 pull_policy——compose 的預設行為正是要的: # - `docker compose up -d`:本機沒有這個映像才 pull;有 build: 時 pull 失敗會改從原始碼 build。 # - `docker compose pull`:一律重拉。明設 pull_policy: missing 的話,本機已有的標籤(例如 latest) # 會被跳過,升級拉不到新版(Compose v2.27 實測)。 # - `docker compose up -d --build`(repo 裡):一律從原始碼 build(不 pull),產物標成這個映像名。 image: ghcr.io/sw-willie-wu/knotebook:${KNOTEBOOK_VERSION:-latest} build: context: . dockerfile: docker/Dockerfile env_file: .env # 日誌輪替上限(issue #50 的治標半):docker 的 json-file driver 預設**沒有**大小 # 上限,任何能讓 app 持續寫日誌的路徑(不只 #50 那條,未來的都算)最終症狀都是 # 自架者的磁碟被灌爆。50MB × 5 檔對排錯綽綽有餘;治本(對灌日誌的連線斷線)在 # collab/server.ts 的訊息預檢。 logging: driver: json-file options: max-size: 50m max-file: "5" # 選配:限制 app 能用的 CPU 與記憶體(#398)。設了之後,沒設 PARSE_WORKERS 時 server 會照這裡的上限 # (容器的 cgroup)推算解析 worker 數:min(CPU − 1,最多 4;floor(記憶體 × 0.5 ÷ PARSE_WORKER_MEMORY_MB)),至少 1。 # 不設就照主機的 CPU 與記憶體算。怎麼挑數字見 docs/self-hosting.md「Parse workers」。 # deploy: # resources: # limits: # cpus: "2" # memory: 4g # 預設只綁 loopback:public 拓撲(docs/self-hosting.md「Deployment prerequisites」)的假設是 # app 只透過反代對外曝露;若直接 "3000:3000"(等同 0.0.0.0:3000),docker 會 # 直接在 iptables 開洞,繞過 host 防火牆規則,讓區網/公網能不經反代直接打到 # app。Quickstart 的 `curl http://localhost:3000/...` 不受影響(127.0.0.1 對本機 # 而言就是 localhost)。要用 trusted-LAN plain-http 拓撲,改用下面註解掉的替代寫法。 ports: - "127.0.0.1:3000:3000" # LAN mode(trusted-LAN plain-http,見 docs/self-hosting.md「Deployment prerequisites」): # 換成 "3000:3000" 會把 app 直接曝露到你的網段(等同 0.0.0.0:3000),並透過 # docker 自己在 iptables 開的洞繞過 host 防火牆規則——啟用前務必先讀過 # docs/self-hosting.md 的風險清單(cleartext 密碼/session cookie、無 Secure flag)。 # - "3000:3000" # 上傳圖片持久化——不掛 volume 的話,圖片只存在 container 的可寫層, # `docker compose down`/重建 container 會整批遺失。掛載點與 Dockerfile 建的 # `/app/uploads`(已 chown 給 node)一致,`src/index.ts` 用同一個相對路徑 # (`process.cwd()/uploads`,WORKDIR 為 /app)解析。 volumes: - uploads:/app/uploads # 讓 DATABASE_URL 能用 host.docker.internal 指到「跑 docker 的這台主機」上的 PostgreSQL # (Linux 的 docker 預設不提供這個名字)。用內建 db 時用不到,留著無害。見 # docs/self-hosting.md「Using your own PostgreSQL」。 extra_hosts: - "host.docker.internal:host-gateway" # BUNDLED_DB=0(不起內建 db,見下方 db 的 scale)時 compose 不等這個依賴:scale 為 0 的依賴 # 不等是 Docker Compose v2.3.0 起的有意行為(`shouldWaitForDependency` 的 `GetScale() == 0` # 分支,commit 09e0fa94;v2.27 實測同),所以 BUNDLED_DB 需要 Compose v2.3.0 以上。這裡不必寫 # required: false——寫了的話,內建 db 起不來時 app 也會照樣被啟動,預設起法的失敗行為就變了。 depends_on: db: condition: service_healthy healthcheck: # 用 127.0.0.1 而非 localhost:避免容器內 /etc/hosts 或 Node DNS 解析(部分 # image 的 getaddrinfo 在無 IPv6 時可能先嘗試 ::1)導致的額外延遲/失敗, # 明確指定 loopback IPv4 位址。 test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/healthz').then(r=>process.exit(r.ok?0:1),()=>process.exit(1))"] interval: 5s timeout: 3s retries: 5 start_period: 10s restart: unless-stopped db: image: pgvector/pgvector:pg17 # 內建 PostgreSQL:預設會起(BUNDLED_DB 未設=1)。改用自己的 PostgreSQL 時在 .env 設 # BUNDLED_DB=0、並把 DATABASE_URL 指過去——這個容器就不會被建立,已在跑的會被移除 # (db_data volume 保留)。只接受 0 或 1:非數字插值失敗、負數讓 compose panic、大於 1 會讓 # 多個 postgres 共用同一個 db_data。 scale: ${BUNDLED_DB:-1} environment: POSTGRES_USER: knotebook # 可用 shell/.env 的 POSTGRES_PASSWORD 覆寫預設值;注意 Postgres 只在「第一次 # 初始化空 data 目錄」時套用這個變數(見 docs/known-limitations.md),事後改這裡 # 不會自動生效。 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-knotebook} POSTGRES_DB: knotebook volumes: - db_data:/var/lib/postgresql/data healthcheck: # CMD 陣列形(不經 shell)、走 TCP: # - CMD-SHELL 形會讓 sh 留著當 pg_isready 的父程序;逾時時 docker 只殺 sh,孤兒 pg_isready # 被 PID 1 的 postmaster 收養,之後以 exit 2 結束會被當成 backend 崩潰、整座 cluster 重啟。 # - 走 unix socket 的話,第一次初始化(initdb)期間那個只聽 socket 的暫時 server 會讓它提早 # 回報就緒;走 TCP 要等正式 server 起來才通。 # timeout 5s 大於 pg_isready 自己的 3 秒連線逾時,讓它自己結束。start_period 60s:首次初始化 # 較慢的機器在這段期間的失敗不計入 retries,一成功就 healthy,所以快的機器不會因此等更久。 test: ["CMD", "pg_isready", "--host", "127.0.0.1", "--username", "knotebook", "--dbname", "knotebook"] interval: 5s timeout: 5s retries: 5 start_period: 60s restart: unless-stopped volumes: db_data: uploads: