Summary
Two related gaps in the core-lightning migration from core-lightning-rest (V2 API) to the native CLNRest (V3) API make modern CLN consumer apps like LNbits fragile under DR recovery and break Umbrel's provider contract for CLNRest-native consumers:
-
Bind gap — CLNRest binds to a single static container IP instead of 0.0.0.0.
In steady-state this is functionally equivalent (port 2107 is published via ports:
and reachable from other containers and the LAN). The difference surfaces on failure
paths: a DR restore or fresh install can start lightningd before the static IP is
attached to its interface, causing a hard EADDRNOTAVAIL bind failure. Binding to
0.0.0.0 removes that race entirely.
-
Provider contract gap — exports.sh exports zero CLNRest-native keys
(CLNREST_HOST, CLNREST_URL, CLNREST_RUNE_PATH). CLNRest-native apps (LNbits,
Boltz) cannot autodiscover the endpoint, and umbrelOS cannot reconstruct the V3
environment during DR recovery — consumers fall back to empty strings or hardcoded
V2 patterns that are wrong by construction on a fresh reinstall.
CLNRest (V3) has been built into lightningd since v23.08, but the Umbrel packaging
still reflects V2 (core-lightning-rest) naming conventions throughout.
Gap 1 — docker-compose.yml: CLNRest bind host
Before (master):
- --clnrest-host=${APP_CORE_LIGHTNING_DAEMON_IP} # binds to 10.21.21.96 only
After (PR #5261):
- --clnrest-host=${CLNREST_HOST} # exported as 0.0.0.0 → all interfaces
With container_name pins on lightningd, app, and tor:
lightningd:
container_name: core-lightning_lightningd_1
app:
container_name: core-lightning_app_1
tor:
container_name: core-lightning_tor_1
Impact — DR-safe bind address
lightningd honors --clnrest-host literally. Binding to the daemon's static
container IP is safe in steady state but fails on two recovery paths:
- DR / fresh-install ordering —
lightningd can start before the static IP is
attached to its interface; EADDRNOTAVAIL causes a hard bind failure. 0.0.0.0
removes that race entirely.
- Subnet drift — if
10.21.21.0/24 is renumbered in a future umbrelOS release,
every CLN compose pinned to a literal IP breaks silently. 0.0.0.0 survives.
- No exposure delta — the container netns is the security boundary;
0.0.0.0
inside the container exposes nothing that the published ports: mapping doesn't
already expose.
Impact — container_name pins
Before #3931, the separate c-lightning-rest service was the API surface; consumers
never resolved lightningd directly. After #3931 removed that service, lightningd
itself (with CLNRest built in since v23.08) became the consumer-facing endpoint.
Without container_name, downstream consumers (RTL, LNbits, Boltz) can race Docker
DNS registration on parallel container recreation after an OS update or DR restore.
Pinning makes the name a compose-time guarantee. Scope is bounded: only lightningd
(V3 API surface), app (Commando rune owner, already hardcoded as APP_HOST), and
tor (lightningd's hard startup dependency, see #5529).
Gap 2 — exports.sh: missing V3 native provider contract
Before (master) — zero CLNRest-native exports:
export APP_CORE_LIGHTNING_DAEMON_IP="10.21.21.96"
export CORE_LIGHTNING_REST_PORT="2107"
# nothing else — no assembled endpoint, no CLNRest-native keys
After (PR #5261):
# DNS-stable container hostnames (resilient to IP drift across restarts / DR recovery)
export APP_CORE_LIGHTNING_APP_HOST="core-lightning_app_1"
export APP_CORE_LIGHTNING_DAEMON_HOST="core-lightning_lightningd_1"
# Canonical CLNREST exports for consumer apps (RTL, LNbits)
export APP_CORE_LIGHTNING_CLNREST_PORT="${CORE_LIGHTNING_REST_PORT}"
export APP_CORE_LIGHTNING_CLNREST_HOST="${APP_CORE_LIGHTNING_DAEMON_HOST}"
# Backward-compat aliases — V2 names preserved verbatim for RTL and existing consumers
export APP_CORE_LIGHTNING_REST_PORT="${APP_CORE_LIGHTNING_CLNREST_PORT}"
export APP_CORE_LIGHTNING_REST_HOST="${APP_CORE_LIGHTNING_DAEMON_IP}"
# Native CLNRest (V3) keys — what CLNRest-native apps (LNbits, Boltz) expect
export CLNREST_HOST="0.0.0.0"
export CLNREST_URL="https://${APP_CORE_LIGHTNING_DAEMON_HOST}:${CORE_LIGHTNING_REST_PORT}"
export CLNREST_RUNE_PATH="${COMMANDO_CONFIG}"
Impact — native consumer contract (LNbits, Boltz, Zap Bridge)
CLNRest-native apps are built against CLNRest's own config key names:
CLNREST_HOST, CLNREST_URL, CLNREST_RUNE_PATH. The current exports.sh exports
only V2-era names. Every consumer app must therefore hardcode V2 fallbacks and document
manual configuration steps — defeating the Umbrel provider contract pattern where
"install app → it just works" is the standard experience.
The same reason bitcoin exports BITCOIN_RPC_* and electrs exports ELECTRUM_*:
the Provider publishes the native contract its Consumers expect. CLN is the outlier
still shaped around the deprecated V2 naming, six months after #3931 retired the
binary.
The Umbrel-native names (APP_CORE_LIGHTNING_*) stay verbatim as backward-compatible
aliases. RTL and every existing consumer continue working unchanged. The new exports
are purely additive.
Impact — DR recovery
During recovery, umbrelOS sources exports.sh to reconstruct the environment for all
dependent apps. With no CLNREST_* exports, V3-native consumers reading ${CLNREST_URL}
get an empty string and silently fall back to hardcoded V2 patterns. On a fresh
reinstall the daemon IP may also drift, making any hardcoded IP fallback wrong. Full
manual remapping of V3 API keys was required to bring consumer apps back online after
a real DR restore.
What's verifiable in master today
# docker-compose.yml — binds to static internal IP, not 0.0.0.0
- --clnrest-host=${APP_CORE_LIGHTNING_DAEMON_IP}
# exports.sh — no CLNREST_* exports exist
grep CLNREST core-lightning/exports.sh # returns nothing
# exports.sh — no DNS-stable container hostname exports
grep _HOST core-lightning/exports.sh # returns nothing
# core-lightning-rtl/docker-compose.yml — consumer hardcodes raw IP+port
LN_SERVER_URL: "https://$APP_CORE_LIGHTNING_DAEMON_IP:$CORE_LIGHTNING_REST_PORT"
Dependency chain
[Issue #4785] Missing native V3 CLNRest provider contract — DR recovery + consumer apps
│
▼
[PR #5261] gap(core-lightning): native V3 CLNRest provider contract + DR-safe binding
• core-lightning/docker-compose.yml — clnrest-host=0.0.0.0 + container_name pins
• core-lightning/exports.sh — CLNREST_* native keys + V2 compat aliases verbatim
• core-lightning/data/app/.gitkeep — missing mount source placeholder
• Linter clean. Tested on umbrelOS / Pi5 with CLN v25.09.3-hotfix.1
│
├──────────────────────────────────────────┐
▼ ▼
[PR #5465] [PR #5457]
feat(lnbits-cln): refactor(core-lightning-rtl):
add LNbits for Core Lightning adopt CLNRest V3 provider contract
• First CLNRest-native consumer • Replace hardcoded IP+port with
app in Umbrel app store ${CLNREST_URL} and ${CLNREST_RUNE_PATH}
• Validates full V3 provider • Decouples RTL from CLN internals
contract end-to-end • container_name pins on web + boltz
• Closes #4753 • Draft. Depends on #5261
• Draft. Depends on #5261
Related
Summary
Two related gaps in the
core-lightningmigration fromcore-lightning-rest(V2 API) to the native CLNRest (V3) API make modern CLN consumer apps like LNbits fragile under DR recovery and break Umbrel's provider contract for CLNRest-native consumers:Bind gap — CLNRest binds to a single static container IP instead of
0.0.0.0.In steady-state this is functionally equivalent (port 2107 is published via
ports:and reachable from other containers and the LAN). The difference surfaces on failure
paths: a DR restore or fresh install can start
lightningdbefore the static IP isattached to its interface, causing a hard
EADDRNOTAVAILbind failure. Binding to0.0.0.0removes that race entirely.Provider contract gap —
exports.shexports zero CLNRest-native keys(
CLNREST_HOST,CLNREST_URL,CLNREST_RUNE_PATH). CLNRest-native apps (LNbits,Boltz) cannot autodiscover the endpoint, and umbrelOS cannot reconstruct the V3
environment during DR recovery — consumers fall back to empty strings or hardcoded
V2 patterns that are wrong by construction on a fresh reinstall.
CLNRest (V3) has been built into
lightningdsince v23.08, but the Umbrel packagingstill reflects V2 (
core-lightning-rest) naming conventions throughout.Gap 1 —
docker-compose.yml: CLNRest bind hostBefore (master):
After (PR #5261):
With
container_namepins onlightningd,app, andtor:Impact — DR-safe bind address
lightningdhonors--clnrest-hostliterally. Binding to the daemon's staticcontainer IP is safe in steady state but fails on two recovery paths:
lightningdcan start before the static IP isattached to its interface;
EADDRNOTAVAILcauses a hard bind failure.0.0.0.0removes that race entirely.
10.21.21.0/24is renumbered in a future umbrelOS release,every CLN compose pinned to a literal IP breaks silently.
0.0.0.0survives.0.0.0.0inside the container exposes nothing that the published
ports:mapping doesn'talready expose.
Impact —
container_namepinsBefore #3931, the separate
c-lightning-restservice was the API surface; consumersnever resolved
lightningddirectly. After #3931 removed that service,lightningditself (with CLNRest built in since v23.08) became the consumer-facing endpoint.
Without
container_name, downstream consumers (RTL, LNbits, Boltz) can race DockerDNS registration on parallel container recreation after an OS update or DR restore.
Pinning makes the name a compose-time guarantee. Scope is bounded: only
lightningd(V3 API surface),
app(Commando rune owner, already hardcoded asAPP_HOST), andtor(lightningd's hard startup dependency, see #5529).Gap 2 —
exports.sh: missing V3 native provider contractBefore (master) — zero CLNRest-native exports:
After (PR #5261):
Impact — native consumer contract (LNbits, Boltz, Zap Bridge)
CLNRest-native apps are built against CLNRest's own config key names:
CLNREST_HOST,CLNREST_URL,CLNREST_RUNE_PATH. The currentexports.shexportsonly V2-era names. Every consumer app must therefore hardcode V2 fallbacks and document
manual configuration steps — defeating the Umbrel provider contract pattern where
"install app → it just works" is the standard experience.
The same reason
bitcoinexportsBITCOIN_RPC_*andelectrsexportsELECTRUM_*:the Provider publishes the native contract its Consumers expect. CLN is the outlier
still shaped around the deprecated V2 naming, six months after #3931 retired the
binary.
The Umbrel-native names (
APP_CORE_LIGHTNING_*) stay verbatim as backward-compatiblealiases. RTL and every existing consumer continue working unchanged. The new exports
are purely additive.
Impact — DR recovery
During recovery, umbrelOS sources
exports.shto reconstruct the environment for alldependent apps. With no
CLNREST_*exports, V3-native consumers reading${CLNREST_URL}get an empty string and silently fall back to hardcoded V2 patterns. On a fresh
reinstall the daemon IP may also drift, making any hardcoded IP fallback wrong. Full
manual remapping of V3 API keys was required to bring consumer apps back online after
a real DR restore.
What's verifiable in master today
Dependency chain
Related