Compose files run on the orchestrator's defaults (#6220)
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 2s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Android / Build the server image (push) Skipped
CI & Build / Python lint (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 10s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Python tests (push) Successful in 13s
CI & Build / integration (push) Successful in 1m34s
CI & Build / Build & push image (push) Successful in 29s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 2s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Android / Build the server image (push) Skipped
CI & Build / Python lint (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 10s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Python tests (push) Successful in 13s
CI & Build / integration (push) Successful in 1m34s
CI & Build / Build & push image (push) Successful in 29s
Shipped stacks drop every healthcheck and the depends_on service_healthy wait, per rule 198 "Shipped compose files run on the orchestrator's defaults: no healthcheck, no restart policy". A pg_isready healthcheck turns slow storage into a killed database. The app needs neither: inkwell.dbwait waits up to 60s for Postgres at start, and the engine's pool_pre_ping retries per request. - docker-compose.yml: db and app healthchecks removed; depends_on is a plain start order. Comments cut to one or two lines per setting under rule 32 "Comments describe the product to a stranger": the family/rule references are gone, and the three-way port explanation points at docs/public-hosting.md, which already holds it. - docker-compose.dev.yml: db healthcheck and service_healthy removed; header shortened. - README: the copy-paste stack loses the same two blocks. restart: unless-stopped stays: plain Compose defaults to no restart, and Swarm ignores the key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,18 +69,12 @@ services:
|
|||||||
POSTGRES_DB: inkwell
|
POSTGRES_DB: inkwell
|
||||||
volumes:
|
volumes:
|
||||||
- inkwell-db:/var/lib/postgresql/data
|
- inkwell-db:/var/lib/postgresql/data
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U inkwell"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
|
|
||||||
app:
|
app:
|
||||||
image: git.fabledsword.com/bvandeusen/inkwell:latest # :dev for the current dev build
|
image: git.fabledsword.com/bvandeusen/inkwell:latest # :dev for the current dev build
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
- db
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
environment:
|
||||||
INKWELL_DATABASE_URL: postgresql+asyncpg://inkwell:CHANGE_ME@db:5432/inkwell
|
INKWELL_DATABASE_URL: postgresql+asyncpg://inkwell:CHANGE_ME@db:5432/inkwell
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
+4
-16
@@ -1,15 +1,9 @@
|
|||||||
# Hot-reload DEV stack: Postgres + backend (hypercorn --reload) + Vite dev server.
|
# Development stack: Postgres, the backend with live reload, and the Vite dev server.
|
||||||
# Edit code on the host and changes reload live. Per family rule 12 the agent does
|
|
||||||
# NOT start this — run it yourself:
|
|
||||||
#
|
#
|
||||||
# docker compose -f docker-compose.dev.yml up
|
# docker compose -f docker-compose.dev.yml up
|
||||||
#
|
#
|
||||||
# Then open http://localhost:5173 (the Vite dev server proxies /api to the backend).
|
# Then open http://localhost:5173. For laptops only: the password is fixed and
|
||||||
#
|
# Postgres is published on 5432. docker-compose.yml is the deployment stack.
|
||||||
# docker-compose.yml — the DEFAULT file — is the production stack instead: it pulls
|
|
||||||
# the published image, keeps Postgres off the host network, and expects a .env. This
|
|
||||||
# one builds nothing and is deliberately insecure-by-convenience (weak password,
|
|
||||||
# Postgres published on 5432) because it is meant for a laptop, not a deployment.
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
@@ -21,18 +15,12 @@ services:
|
|||||||
- inkwell-dev-db:/var/lib/postgresql/data
|
- inkwell-dev-db:/var/lib/postgresql/data
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U inkwell"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: python:3.12-slim
|
image: python:3.12-slim
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
- db
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
environment:
|
||||||
INKWELL_DATABASE_URL: postgresql+asyncpg://inkwell:inkwell@db:5432/inkwell
|
INKWELL_DATABASE_URL: postgresql+asyncpg://inkwell:inkwell@db:5432/inkwell
|
||||||
PYTHONPATH: /app/src
|
PYTHONPATH: /app/src
|
||||||
|
|||||||
+12
-84
@@ -1,23 +1,11 @@
|
|||||||
# Inkwell — PRODUCTION stack (app + Postgres).
|
# Inkwell: the app and its Postgres database, from the published image.
|
||||||
#
|
|
||||||
# This is the default compose file: `docker compose up -d` runs a real deployment
|
|
||||||
# from the published image. Development lives in docker-compose.dev.yml (hot-reload,
|
|
||||||
# builds from source).
|
|
||||||
#
|
#
|
||||||
# cp .env.example .env # then set POSTGRES_PASSWORD
|
# cp .env.example .env # then set POSTGRES_PASSWORD
|
||||||
# docker compose up -d
|
# docker compose up -d
|
||||||
#
|
#
|
||||||
# Per family rule 12 the agent does NOT start this — run it yourself.
|
# Upgrade: `docker compose pull && docker compose up -d`. Migrations run at start.
|
||||||
#
|
# Back up first from Settings → Backups; a bare pg_dump misses the attachments,
|
||||||
# Upgrades: docker compose pull && docker compose up -d
|
# which live in the inkwell-data volume.
|
||||||
# Rollback: set INKWELL_TAG to a commit sha in .env, then the same two commands.
|
|
||||||
# Every push publishes an immutable :<sha> image for exactly this.
|
|
||||||
#
|
|
||||||
# Schema migrations run automatically at container start (see the Dockerfile CMD),
|
|
||||||
# so an upgrade is just a pull and a restart. Take a backup first anyway: Settings →
|
|
||||||
# Backups → Back up now writes one file holding the notes AND their attachments
|
|
||||||
# (docs/backups.md). A bare pg_dump does not: attachments are files in the
|
|
||||||
# `inkwell-data` volume, not rows.
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
@@ -25,98 +13,38 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-inkwell}
|
POSTGRES_USER: ${POSTGRES_USER:-inkwell}
|
||||||
# No default on purpose. A production compose that ships a known password is
|
|
||||||
# how self-hosted databases end up in search engines; compose fails fast here
|
|
||||||
# instead, with the message below.
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env — see .env.example}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env — see .env.example}
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-inkwell}
|
POSTGRES_DB: ${POSTGRES_DB:-inkwell}
|
||||||
volumes:
|
volumes:
|
||||||
# Which volume this resolves to is set at the bottom of this file, and an
|
|
||||||
# existing deployment overrides it from .env rather than renaming anything.
|
|
||||||
- inkwell-db:/var/lib/postgresql/data
|
- inkwell-db:/var/lib/postgresql/data
|
||||||
# Deliberately NOT published to the host. The app reaches Postgres over the
|
# Not published to the host; `docker compose exec db psql -U inkwell` reaches it.
|
||||||
# compose network; exposing 5432 only widens the attack surface. If you need
|
|
||||||
# psql, `docker compose exec db psql -U inkwell` gets you there without it.
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-inkwell} -d ${POSTGRES_DB:-inkwell}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
start_period: 30s
|
|
||||||
logging: &logging
|
logging: &logging
|
||||||
driver: json-file
|
driver: json-file
|
||||||
options:
|
options:
|
||||||
# Unbounded container logs are a slow-motion disk-full outage on a
|
|
||||||
# long-running self-hosted box.
|
|
||||||
max-size: "10m"
|
max-size: "10m"
|
||||||
max-file: "3"
|
max-file: "3"
|
||||||
|
|
||||||
app:
|
app:
|
||||||
# :latest tracks `main`. Set INKWELL_TAG=dev in .env to follow the
|
# latest follows main, dev follows dev, a commit sha pins one build.
|
||||||
# development line instead, or a commit sha to pin exactly.
|
|
||||||
image: git.fabledsword.com/bvandeusen/inkwell:${INKWELL_TAG:-latest}
|
image: git.fabledsword.com/bvandeusen/inkwell:${INKWELL_TAG:-latest}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
- db
|
||||||
condition: service_healthy
|
|
||||||
environment:
|
environment:
|
||||||
# The only required application setting. Everything else a person might want
|
|
||||||
# to tune lives in the admin Settings UI, backed by the database (rule 25).
|
|
||||||
INKWELL_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-inkwell}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-inkwell}
|
INKWELL_DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-inkwell}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-inkwell}
|
||||||
volumes:
|
volumes:
|
||||||
# Uploaded attachments. /var/inkwell is fixed in the app (Config.DATA_DIR),
|
# Uploaded attachments; without this mount they are lost when the container is recreated.
|
||||||
# not configurable — mount it or lose every image on container recreation.
|
|
||||||
- inkwell-data:/var/inkwell
|
- inkwell-data:/var/inkwell
|
||||||
# Daily backups to a folder (Settings → Backups, docs/backups.md): mount the
|
# Optional: the folder scheduled backups are written to (Settings → Backups).
|
||||||
# share they should land in. Not needed when they go to an S3 bucket.
|
|
||||||
# - /mnt/backups/inkwell:/backups
|
# - /mnt/backups/inkwell:/backups
|
||||||
# WHERE THE APP IS REACHABLE FROM. Three shapes, and the right answer is
|
# Behind a reverse proxy in Docker, delete `ports:`; behind one on the host, set
|
||||||
# different for each — the default serves the first.
|
# INKWELL_BIND=127.0.0.1. See docs/public-hosting.md.
|
||||||
#
|
|
||||||
# 1. LAN, no proxy (the default). Binds every interface so your phone and your
|
|
||||||
# desktop can reach the server. This is what makes a self-hosted install work
|
|
||||||
# out of the box, and it is why the default is NOT the locked-down value: a
|
|
||||||
# server only reachable from the machine it runs on is not hardened, it is
|
|
||||||
# broken.
|
|
||||||
#
|
|
||||||
# 2. Reverse proxy in Docker, on this network (Traefik discovering the container,
|
|
||||||
# an nginx container, etc). DELETE the `ports:` block below entirely. The proxy
|
|
||||||
# reaches the app over the compose network without any port being published,
|
|
||||||
# and publishing one is a second, unauthenticated way in that bypasses the
|
|
||||||
# proxy — including whatever the proxy is doing about TLS and auth.
|
|
||||||
#
|
|
||||||
# 3. Reverse proxy on the HOST (not in Docker). Set INKWELL_BIND=127.0.0.1 in
|
|
||||||
# .env, so the port exists but only the host itself can reach it.
|
|
||||||
#
|
|
||||||
# If you are exposing this to the internet, you want 2 or 3. Leaving it at 1
|
|
||||||
# means the app is reachable directly on port 5000, past everything your proxy
|
|
||||||
# does.
|
|
||||||
ports:
|
ports:
|
||||||
- "${INKWELL_BIND:-0.0.0.0}:${INKWELL_PORT:-5000}:5000"
|
- "${INKWELL_BIND:-0.0.0.0}:${INKWELL_PORT:-5000}:5000"
|
||||||
healthcheck:
|
|
||||||
# python rather than curl: the runtime image is python:3.12-slim and carries no
|
|
||||||
# HTTP client binary. Hits the app's own /api/health.
|
|
||||||
test:
|
|
||||||
- CMD
|
|
||||||
- python
|
|
||||||
- -c
|
|
||||||
- |
|
|
||||||
import sys, urllib.request
|
|
||||||
sys.exit(0 if urllib.request.urlopen("http://127.0.0.1:5000/api/health", timeout=5).status == 200 else 1)
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
# Generous: the container waits for Postgres and runs migrations before it
|
|
||||||
# serves anything, and a first boot builds the whole schema.
|
|
||||||
start_period: 60s
|
|
||||||
logging: *logging
|
logging: *logging
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
# Named explicitly so the name is one a person can type, and overridable so a
|
# Overridable so an install from before the rename keeps its volumes (.env.example).
|
||||||
# deployment that predates the rename to Inkwell keeps the volumes it already has
|
|
||||||
# (see "Upgrading from ThoughtSync" in .env.example). Pointing these at the wrong
|
|
||||||
# name does not fail: it starts against an EMPTY database and the old one sits
|
|
||||||
# there untouched, which looks exactly like data loss.
|
|
||||||
inkwell-db:
|
inkwell-db:
|
||||||
name: ${INKWELL_DB_VOLUME:-inkwell-db}
|
name: ${INKWELL_DB_VOLUME:-inkwell-db}
|
||||||
inkwell-data:
|
inkwell-data:
|
||||||
|
|||||||
Reference in New Issue
Block a user