Deployment
Installation, configuration, secrets, TLS, rate limits, workers and webhook destinations.
Full guide in the repository: docs/deployment.mdInstall
GitHub Releases has archives for Linux, macOS and Windows on amd64 and arm64. Each contains ledgerforge, ledgerforge-mcp, the licence and the notice file. Checksums cover the archives, the source and container SBOMs and the recorded image digest. Release binaries are built without CGO, so the optional SQLite heartbeat falls back when SQLite is unavailable.
$ curl -LO https://github.com/devaccuracy/ledgerforge/releases/download/v1.0.0/ledgerforge_v1.0.0_linux_amd64.tar.gz$ curl -LO https://github.com/devaccuracy/ledgerforge/releases/download/v1.0.0/checksums.txt$ sha256sum -c checksums.txt --ignore-missingledgerforge_v1.0.0_linux_amd64.tar.gz: OK$ tar -xzf ledgerforge_v1.0.0_linux_amd64.tar.gz$ ./ledgerforge_v1.0.0_linux_amd64/ledgerforge versionledgerforge 1.0.0 (commit 64c7aec, built 2026-10-10T05:53:57+00:00)The container image ghcr.io/devaccuracy/ledgerforge:1.0.0 is published for linux/amd64 and linux/arm64 and contains both binaries. To build from source, use Go 1.26.9 or later:
$ go install github.com/devaccuracy/ledgerforge/cmd/ledgerforge@v1.0.0$ go install github.com/devaccuracy/ledgerforge/cmd/ledgerforge-mcp@v1.0.0Configuration and secrets
Copy ledgerforge.example.json to ledgerforge.json, replace the secret placeholder with a random value and set your database and Redis addresses. server.secure: trueenables authentication; don't disable it on a reachable deployment.
{ "project_name": "LedgerForge", "data_source": { "dns": "postgres://postgres:password@postgres:5432/ledgerforge?sslmode=disable" }, "redis": { "dns": "redis:6379" }, "server": { "port": "5001", "secure": true, "secret_key": "REPLACE_WITH_A_RANDOM_SECRET" }}Environment variables such as LEDGERFORGE_SERVER_SECRET_KEY, LEDGERFORGE_DATA_SOURCE_DNS and LEDGERFORGE_REDIS_DNS override loaded fields; pass them to both server and workers. Keep the master secret in a secret manager and out of images, repositories, logs and support requests.
$ cp ledgerforge.example.json ledgerforge.json# Replace server.secret_key first, for example with: openssl rand -hex 32$ LEDGERFORGE_IMAGE=ghcr.io/devaccuracy/ledgerforge:1.0.0 docker compose up -dProcesses
$ ledgerforge migrate up$ ledgerforge start$ ledgerforge workersRun migrations before serving a new version, and keep API servers and workers on compatible versions. Pin a release tag or image digest in production rather than :main. Back up PostgreSQL with a tested restore procedure, and monitor worker lag, queue saturation, lock contention, rejected transactions and webhook failures.
Network, TLS and limits
- Don't expose PostgreSQL, Redis, Typesense, tracing UIs or worker monitoring to the internet. Use PostgreSQL TLS and restricted roles, and Redis TLS across trust boundaries.
- Terminate TLS at a reverse proxy with deadlines and body and connection limits, or use the built-in ACME support (
server.ssl), which needs public ports 80 and 443. - Set explicit rate limits. The code's fallback is not a useful production limit, and limits are per process, so apply aggregate limits at the proxy too.
- Request bodies default to 5 MB and uploads to 256 MB (
server.max_request_body_size_mb,server.max_upload_size_mb). Setserver.metrics_bearer_tokenbefore exposing metrics.
{ "rate_limit": { "requests_per_second": 100, "burst": 200, "cleanup_interval_sec": 60 }}Webhook destinations
{ "notification": { "webhook": { "url": "https://receiver.example/events", "signing_secret": "REPLACE_WITH_A_SEPARATE_RANDOM_SECRET", "block_private_destinations": true, "timeout_sec": 30 } }}Registration and updates accept only absolute HTTP(S) URLs without credentials or fragments. block_private_destinations, off by default for compatibility with internal receivers, rejects private and special-purpose ranges at registration and again when connecting. Redirects are not followed, timeouts default to 30 seconds (maximum 120) and hook response bodies are limited to 1 MiB. Restrict egress at the network boundary as well. Non-2xx responses are retried.