todo-postgres
July 16, 2026 · View on GitHub
This project was generated by meshc init --template todo-api --db postgres.
This is the PostgreSQL-backed autonomous-cluster starter and the application used by meshc proof docker-autoscaling. PostgreSQL provides shared transactional application state; Mesh owns adaptive execution routing, request continuity, controller consensus, and capacity changes.
Project layout
todo-postgres/
mesh.toml
main.mpl
work.mpl
config.mpl
README.md
Dockerfile
.dockerignore
.env.example
api/
health.mpl
router.mpl
todos.mpl
deploy/
todo-postgres.up.sql
migrations/
20260402120000_create_todos.mpl
runtime/
registry.mpl
scripts/
apply-deploy-migrations.sh
deploy-smoke.sh
stage-deploy.sh
services/
rate_limiter.mpl
storage/
todos.mpl
tests/
config.test.mpl
types/
todo.mpl
Clustered contract
mesh.tomlcarries autonomous scaling, adaptive routing, strict continuity, and Docker capacity-driver policymain.mplvalidates config, opens the local PostgreSQL pool, then boots throughNode.start_from_env()before starting the local HTTP serverwork.mpldeclares@cluster pub fn sync_todos()- the runtime-owned handler name is derived from the ordinary source function name as
Work.sync_todos GET /todosandGET /todos/:iduse clustered reads;POST /todosusesHTTP.clustered(2, ...)with a callerIdempotency-Key, so either worker can perform the transaction against the same PostgreSQL database and Mesh can safely replay a retained result
HTTP routes
GET /health— local runtime + PostgreSQL/starter configuration snapshot without leakingDATABASE_URLGET /todos— list todos throughHTTP.clustered(1, ...)GET /todos/:id— fetch one todo throughHTTP.clustered(1, ...)POST /todos— clustered idempotent create from{"title":"Buy milk"}with anIdempotency-KeyheaderPUT /todos/:id— toggle completionDELETE /todos/:id— delete a todo
POST /todos is the starter's clustered mutation proof. PUT and DELETE stay local until their callers provide stable operation keys. All mutations go through the actor-backed limiter in services/rate_limiter.mpl.
One public URL, runtime-owned placement
BASE_URLindeploy-smoke.shis the single public app URL for this starter. Locally it can point straight athttp://127.0.0.1:8080; behind a proxy/platform ingress it can front multiple starter nodes.- Clustered reads and
POST /todosresponses include a runtime-ownedX-Mesh-Continuity-Request-Keyresponse header. - Treat that header as an operator/debug seam: take the returned request key and run
meshc cluster continuity <node-name@host:port> <request-key> --jsonagainst a node when you want the same request's continuity record directly. - Use
meshc cluster status,meshc cluster continuity, andmeshc cluster diagnosticsagainst node addresses when you need the same request's ingress, owner, replica, or execution truth. - The response header is a runtime-owned operator/debug seam, not a frontend-aware routing signal.
- The starter does not promise frontend-aware node selection, sticky-session semantics, or a Fly-specific product contract.
- If you only need a local-only single-node path, stay on
meshc init --template todo-api --db sqlite.
Schema and migrations
migrations/ owns the source-tree schema changes. Apply those migrations before booting the API directly from source:
meshc migrate . status
meshc migrate . up
deploy/todo-postgres.up.sql is the staged bundle artifact for production-like replays. The runtime opens the pool and serves HTTP only after configuration succeeds. It does not create tables at startup.
Environment
DATABASE_URL— required PostgreSQL connection stringPORT— HTTP port (8080by default)TODO_RATE_LIMIT_WINDOW_SECONDS— limiter window length (60by default)TODO_RATE_LIMIT_MAX_REQUESTS— allowed mutating requests per window (5by default).env.example— starter copy of the same local-development keysMESH_CLUSTER_COOKIE,MESH_NODE_NAME,MESH_DISCOVERY_SEED,MESH_CLUSTER_PORT,MESH_CONTINUITY_ROLE,MESH_CONTINUITY_PROMOTION_EPOCH— optional clustered-runtime env when you want the runtime-owned operator flow
Local run
meshc migrate . up
meshc build .
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/todo-postgres \
PORT=8080 \
TODO_RATE_LIMIT_WINDOW_SECONDS=60 \
TODO_RATE_LIMIT_MAX_REQUESTS=5 \
./todo-postgres
Staged deploy bundle
Stage a production-like bundle outside the source tree:
bash scripts/stage-deploy.sh ./.bundle/todo-postgres
The staged directory contains:
./.bundle/todo-postgres/todo-postgres— built starter binary./.bundle/todo-postgres/todo-postgres.up.sql— deploy SQL artifact copied fromdeploy/./.bundle/todo-postgres/apply-deploy-migrations.sh—psql-based schema apply helper./.bundle/todo-postgres/deploy-smoke.sh— health + CRUD smoke helper
Each script emits phase-tagged logs: [stage-deploy], [deploy-apply], and [deploy-smoke].
Production-like replay
Apply the staged schema, boot the staged binary, then smoke the running starter through one public BASE_URL:
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/todo-postgres \
bash ./.bundle/todo-postgres/apply-deploy-migrations.sh ./.bundle/todo-postgres/todo-postgres.up.sql
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/todo-postgres \
PORT=8080 \
TODO_RATE_LIMIT_WINDOW_SECONDS=60 \
TODO_RATE_LIMIT_MAX_REQUESTS=5 \
MESH_CLUSTER_COOKIE=dev-cookie \
MESH_NODE_NAME=todo-postgres@127.0.0.1:4370 \
MESH_DISCOVERY_SEED=localhost \
MESH_CLUSTER_PORT=4370 \
MESH_CONTINUITY_ROLE=primary \
MESH_CONTINUITY_PROMOTION_EPOCH=0 \
./.bundle/todo-postgres/todo-postgres
The smoke helper treats BASE_URL as the public app URL. In local replay it can point directly at the starter port; behind a proxy/platform ingress it can front multiple nodes without changing the public contract.
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/todo-postgres \
PORT=8080 \
BASE_URL=http://127.0.0.1:8080 \
bash ./.bundle/todo-postgres/deploy-smoke.sh
The staged bundle is the public deploy contract. Hosted runtimes or managed PostgreSQL providers can sit behind the same bundle later, and a proxy/platform ingress may expose one public app URL in front of multiple nodes, but Mesh runtime ownership still stays on the meshc cluster inspection path and the starter does not promise frontend-aware node selection or a Fly-specific product contract.
Runtime inspection
When you boot the app in clustered mode, inspect the runtime-owned state through Mesh CLI surfaces instead of package-owned control routes:
meshc cluster status <node-name@host:port> --json
meshc cluster continuity <node-name@host:port> --json
meshc cluster continuity <node-name@host:port> <request-key> --json
meshc cluster diagnostics <node-name@host:port> --json
Use the continuity list form first to discover runtime-owned startup records or for general manual investigation.
Docker
Build the Linux binary you want to ship, then package that ./output artifact into the image:
meshc build .
docker build -t todo-postgres .
The container expects a migrated database and a runtime DATABASE_URL; it does not run migrations or create schema at startup. The same staged SQL artifact is the contract for applying schema before the container boots. Re-run meshc build . before rebuilding the image, and build on the Linux target you plan to run because the Dockerfile copies the already-compiled ./output binary into the container.
docker run --rm \
-p 8080:8080 \
-e PORT=8080 \
-e DATABASE_URL=postgres://postgres:postgres@host.docker.internal:5432/todo-postgres \
todo-postgres
Docker Compose (clustered)
The docker-compose.yml runs a two-node cluster with a shared PostgreSQL instance. Apply migrations, build the binary, then start the cluster:
meshc build .
docker compose up -d
Note for macOS/Windows users: If you see "exec format error", you need to build for Linux:
# Install Linux target (one-time setup)
rustup target add x86_64-unknown-linux-gnu
# Build for Linux
meshc build --target x86_64-unknown-linux-gnu .
# Then start the cluster
docker compose up -d
The compose file uses a shared DNS alias (mesh-cluster) so every node can discover its peers through MESH_DISCOVERY_SEED=mesh-cluster. Each container's hostname becomes its node identity automatically — no manual MESH_NODE_NAME construction is required. Set MESH_NODE_HOST to the container's reachable hostname or IP when the system hostname is not routable.
Bootstrap environment contract
| Variable | Required | Default | Purpose |
|---|---|---|---|
MESH_CLUSTER_COOKIE | Yes (cluster) | — | Shared HMAC-SHA256 secret for node authentication |
MESH_DISCOVERY_SEED | Yes (cluster) | — | DNS name that resolves to all node IPs |
MESH_CLUSTER_PORT | No | 4370 | TCP port for inter-node communication |
MESH_NODE_NAME | No | auto | Explicit name@host:port identity (overrides auto) |
MESH_NODE_HOST | No | hostname | Advertised address peers use to reach this node |
MESH_CONTINUITY_ROLE | No | primary | primary or standby continuity role |
MESH_CONTINUITY_PROMOTION_EPOCH | No | 0 | Monotonic counter for split-brain prevention |
MESH_DISCOVERY_INTERVAL_MS | No | 5000 | DNS re-resolution interval in milliseconds |
Identity resolution order: MESH_NODE_NAME > FLY_* env > hostname + MESH_NODE_HOST.
Operator inspection
When the cluster is running, inspect it from a node container. Compose already injects the cluster cookie into that container, so the secret is not placed in the command line. The node1 hostname in node1@node1:4370 typically does not resolve from the host machine:
docker compose exec node1 meshc cluster status node1@node1:4370 --json
docker compose exec node1 meshc cluster continuity node1@node1:4370 --json
docker compose exec node1 meshc cluster diagnostics node1@node1:4370 --json