The server is a single process, so a compose file that only wrapped one
container added a second way to do the same thing. Docker is now the one
documented path, pointing at a Postgres that lives outside the container.
💘 Generated with Crush
Assisted-by: Crush:kimi-k3
Prompt sync server
Small Go service that stores one JSON blob per sync group, keyed by an 8-digit
code (1234-5678). There is no authentication: the code is the shared secret,
so anyone who knows it can read and overwrite that group. It is meant to be
self-hosted for personal use, not exposed as a public service.
Requirements
- Postgres (any recent version; developed against 18)
- A host both Macs can reach — a VPS, a home server, or a Tailscale node
Run it
createdb prompt_sync
cd Server
PROMPT_SYNC_DATABASE_URL="postgres://localhost:5432/prompt_sync?sslmode=disable" \
go run . # or: go build -o prompt-sync . && ./prompt-sync
| Env var | Default |
|---|---|
PROMPT_SYNC_ADDR |
:8080 |
PROMPT_SYNC_DATABASE_URL |
postgres://localhost:5432/prompt_sync?sslmode=disable |
The schema is created on startup. Every request is logged as
METHOD PATH STATUS DURATION; SIGTERM/SIGINT shut down gracefully so an
in-flight push is not cut off mid-write.
Run with Docker
The image is a static binary on distroless (non-root, no shell), so its
HEALTHCHECK calls prompt-sync -healthcheck, which pings the local
/healthz — no curl needed in the image. That is what docker ps reports as
the container health.
docker build -t prompt-sync Server/
docker run -d --name prompt-sync -p 8080:8080 \
-e PROMPT_SYNC_DATABASE_URL="postgres://user:pass@host:5432/prompt_sync?sslmode=disable" \
prompt-sync
docker logs -f prompt-sync # request log
Postgres stays outside the container; the server only needs a URL. If Postgres
runs on the Docker host itself, host is host.docker.internal on macOS and
Windows, or the host's LAN address on Linux (--network host also works).
Deploy to a host
Build for the host's architecture and ship the image, or build it on the host
itself (a git clone plus docker build is usually simplest):
# build on the Mac for a linux/arm64 host, then copy it over
docker build --platform linux/arm64 -t prompt-sync Server/
docker save prompt-sync | gzip > prompt-sync.tar.gz
scp prompt-sync.tar.gz <host>:
ssh <host> 'gunzip -c prompt-sync.tar.gz | docker load'
ssh <host> 'docker run -d --name prompt-sync --restart unless-stopped \
-p 8080:8080 \
-e PROMPT_SYNC_DATABASE_URL="postgres://user:pass@db-host:5432/prompt_sync?sslmode=disable" \
prompt-sync'
Put it behind TLS if it is reachable from the internet — the code is the only secret, and it travels in the URL path. Both plain HTTP and HTTPS work from the app; an HTTP URL is often fine over a LAN or Tailscale.
Endpoints
| Method | Path | Behaviour |
|---|---|---|
POST |
/api/codes |
Generates a unique code, inserts an empty group |
GET |
/api/sync/{code} |
{"version":N,"updatedAt":…,"data":{…}|null}, 404 if unknown |
GET |
/api/sync/{code}/version |
{"version":N} — cheap poll used by the app |
PUT |
/api/sync/{code} |
Upserts the payload, returns the incremented version |
GET |
/healthz |
Liveness (200) |
Payloads are opaque JSONB; the server never parses note contents. Bodies are
capped at 50 MB, which is generous enough for base64 photos.
Backups
The whole state is one table, so pg_dump prompt_sync is a complete backup:
pg_dump prompt_sync | gzip > prompt_sync-$(date +%F).sql.gz