2026-08-21 13:58:06 +00:00
app :
id : cuprate
name : Cuprate
# Matches the crate's own Cargo.toml version (binaries/cuprated/Cargo.toml).
# Cuprate has no stable release yet — this is explicitly work-in-progress
# software (see upstream README). The image tag below pins the exact
# commit built, since "0.1.0-preview" alone is not reproducible.
version : 0.1.0 -preview
# Where this app comes from, so scripts/check-upstream-releases.py can
# tell us when the pin below has fallen behind. Without it nothing can:
# container.image names our mirror, not the project it was mirrored from.
upstream :
kind : github
repo : Cuprate/cuprate
description : Alternative Monero node implementation in Rust. Independently validates Monero consensus rules, providing a layer of security and redundancy for the network.
2026-08-30 16:22:26 -04:00
category : money
2026-08-21 13:58:06 +00:00
container :
# Built from the upstream Dockerfile at the tip of main, 18 commits past
# the cuprated-0.1.0-preview tag (commit 618ff14, 2026-08-19) — there is
# no newer tagged release as of this writing. Re-pin to a tagged release
# once upstream cuts one.
image : source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14
pull_policy : if-not-present
network : archy-net
# The image's own ENTRYPOINT is ["/usr/local/bin/cuprated"]; these are
# appended as its argv, matching the project's own systemd unit
# (cuprated.service) invocation exactly.
custom_args : [ "--config-file" , "/home/cuprate/Cuprated.toml" ]
# The image (FROM scratch) creates uid:gid 1000:1000 for the `cuprate`
# user at build time and runs as it unconditionally (USER 1000:1000,
# no shell to switch users at runtime) — same pattern as
# apps/phoenixd, apps/electrumx, apps/nostr-rs-relay, apps/portainer,
# apps/barkd. The bind-mounted data dir must be owned by that literal
# uid or cuprated dies on a permission error the first time it writes.
data_uid : "1000:1000"
dependencies :
2026-09-09 07:49:04 +00:00
# Monero mainnet is ~250GiB unpruned as of 2026 and growing ~60GiB/year.
# Verified against upstream main (binaries/cuprated/src/config.rs, 2026-09):
# cuprated has NO on-disk pruning setting of any kind — the `pruning`
# crate in its workspace is Monero's p2p *protocol* pruning, not a
# smaller chain — so unlike bitcoin-knots this app CANNOT self-prune
# when disk is scarce (see the DISK_GB branch in
# apps/bitcoin-knots/manifest.yml). Left running on a too-small disk it
# syncs until the filesystem fills and takes Archipelago down. The
2026-09-09 13:58:19 +00:00
# disk-scarce equivalent is enforced in Rust instead: install, start,
# restart and update refuse, and boot reconcile skips, on any node under
2026-09-09 07:49:04 +00:00
# CUPRATE_MIN_DISK_GB (450GB — chain + headroom; refuses the 250GB VPS
# class, allows 500GB-class disks). If upstream ever ships a prune flag,
# replace that gate with the bitcoin-style entrypoint branch.
2026-09-09 13:58:19 +00:00
#
# 450Gi, not the chain size (~250GiB): every manifest-driven surface
# (store size display, install pre-checks, docs) must show the number the
# Rust gate actually enforces, or a user provisioned to the displayed
# value gets refused at a different, unexplained one. Single source of
# truth is crate::constants::CUPRATE_MIN_DISK_GB — keep in lockstep.
- storage : 450Gi
2026-08-21 13:58:06 +00:00
resources :
cpu_limit : 0
2026-09-03 08:20:52 +00:00
# Raised from 4Gi alongside target_max_memory below (see files[] comment)
# — 2026-09-03 incident: a 4Gi/3GB-cache config starved
# cuprated's DB cache into constant eviction/flush, driving 45% sustained
# CPU and ~595GB/24h of block I/O on a fully-synced node. 10Gi leaves
# headroom above the 8GiB cache for the process itself.
memory_limit : 10Gi
2026-09-09 13:58:19 +00:00
# Matches the storage dependency above (= the enforced disk floor),
# not the raw chain size — see the CUPRATE_MIN_DISK_GB note.
disk_limit : 450Gi
2026-08-21 13:58:06 +00:00
security :
# FROM scratch, no package manager/shell, ownership fixed at build time
# — unlike bitcoin-knots this needs no runtime chown/setuid dance, so it
# can run fully read-only with an empty capability set.
capabilities : []
readonly_root : true
no_new_privileges : true
network_policy : isolated
ports :
# P2P. Cuprate's own default listen address is already 0.0.0.0
# (p2p.clear_net.listen_on), so no config override is needed — only the
# host-side port differs from Monero's canonical 18080 because that
# number is already taken on this fleet by lnd's REST port.
- host : 18183
container : 18080
protocol : tcp
auth : none
auth_rationale : >-
Monero p2p gossip. Peers are anonymous by design and speak the Monero wire protocol, not HTTP.
2026-08-22 03:10:53 -04:00
# Unrestricted RPC (full node control) is deliberately NOT published.
# cuprated has no RPC authentication, and for a published port to reach
# it the service would have to bind 0.0.0.0 inside the container — at
# which point every other app can reach it directly on 18081, since
# ports[].bind only restricts the HOST side and podman bridges route to
# each other (verified live 2026-08-22: a peer container on archy-net
# got an unauthenticated get_info, from a *different* network). That is
# unlike bitcoin-knots, whose 0.0.0.0 RPC still demands the rpcuser /
# rpcpassword it writes from generated secrets. So unrestricted RPC is
# left at cuprated's own default — container loopback only, reachable by
# nothing — which is also what upstream intends by refusing a non-local
# bind without an explicit i_know_what_im_doing override.
2026-08-21 13:58:06 +00:00
# Restricted RPC: Monero's own purpose-built safe-for-public subset —
# what wallets use when connecting to a "remote node". Disabled by
2026-09-03 14:51:24 +00:00
# cuprated's own default; enabled via files[] below. `open`, not `gated`:
# the gate still takes the port over (loopback pin, external binds,
# fronts the Tor onion) but skips the dashboard login challenge, same
# reasoning as electrumx's port — wallet clients (Feather,
# monero-wallet-rpc, GUI) speak plain HTTP JSON-RPC programmatically and
# cannot complete a browser login or hold a session cookie. The daemon
# still uses its canonical container port 18089, but Penpot already owns
# host port 18089, so this maps the public host port to the free 18090
# instead.
2026-08-23 01:43:09 -04:00
- host : 18090
2026-08-21 13:58:06 +00:00
container : 18089
protocol : tcp
2026-09-03 14:51:24 +00:00
auth : open
2026-08-21 13:58:06 +00:00
auth_rationale : >-
2026-09-03 14:51:24 +00:00
Monero restricted RPC — the subset upstream considers safe for public/remote-node use. Wallets (Feather, monero-wallet-rpc, GUI) connect directly over plain HTTP JSON-RPC and cannot complete a browser login or hold a dashboard session cookie.
2026-08-21 13:58:06 +00:00
volumes :
- type : bind
source : /var/lib/archipelago/cuprate
target : /home/cuprate
options : [ rw]
# Settings that need to differ from cuprated's own documented defaults
# (verified against `cuprated --generate-config` and `--dry-run` locally,
# 2026-08-21):
2026-09-03 08:20:52 +00:00
# - fast_sync: cuprated's own default is false, which performs full
# cryptographic verification (ring signatures + RandomX PoW) on every
# incoming block instead of trusting checkpointed history. Root-caused
# 2026-09-03 as the dominant cause of a sustained 45% CPU node,
# vs. 2.8% on a reference node with fast_sync = true — same chain height, same
# block rate. Set explicitly rather than relying on the binary
# default so fresh deploys don't silently regress into full-verify.
2026-08-21 13:58:06 +00:00
# - target_max_memory: cuprated's own default auto-detects total *host*
# RAM via sysinfo, which inside a memory-limited container would let
# it size caches far past what resources.memory_limit above actually
# grants — same class of problem bitcoin-knots' -dbcache sizing
2026-09-03 08:20:52 +00:00
# comment addresses. Set explicitly, comfortably under the 10Gi limit.
# Previously 3000000000 (~2.8GiB); that starved the DB cache and
# forced constant eviction/flush (595GB/24h block I/O on a node just
# appending ~2MB blocks every 2 minutes) — raised to 8GiB, matching
# the healthy reference node, and
# resources.memory_limit above raised in step to keep headroom above it.
2026-08-21 13:58:06 +00:00
# - rpc.restricted.enable: cuprated ships this off by default; flip on
# so the auth:none host port above actually serves something instead
# of refusing every connection. port stays at its documented default
# (canonical 18089), and advertise stays false — this node is not
# opting in to being listed as a public remote node over the p2p
# network, just reachable if someone points a wallet at it directly.
# - rpc.unrestricted.address + the allow-public flag: cuprated's own
# default (127.0.0.1) looks like the obviously-correct choice for a
# port meant to stay loopback-only, but verified live (2026-08-21)
# that a service bound literally to 127.0.0.1 *inside* the container
# is unreachable through the host's published port — connections
# reset regardless of how long the daemon has been up. Binding
# 0.0.0.0 inside and letting ports[].bind: 127.0.0.1 below be the
# actual restriction is the same pattern apps/bitcoin-knots already
# uses for its own RPC port (-rpcbind=0.0.0.0:8332 internally, gate
# restricts it externally) — not a new risk, the same one already
# reviewed and accepted for Bitcoin's RPC.
2026-09-01 12:27:33 +00:00
# - tracing.stdout.level / tracing.file.{level,max_log_files}: an
# operator reading Cuprated.toml on disk should be able to see and
# tune the log level directly instead of the file silently omitting
2026-09-03 08:20:52 +00:00
# the whole [tracing] table (verified live on the affected node
2026-09-01 12:27:33 +00:00
# 2026-09-01: the deployed file had no [tracing] section at all, and
# the level was only discoverable by running `cuprated
# --generate-config` and diffing). file.level is set to "info", NOT
# cuprated's own raw default of "debug" — matches the reference dev
2026-09-03 08:20:52 +00:00
# config this app was built and tested against (verified 2026-09-01),
# which deliberately runs file logging quieter
2026-09-01 12:27:33 +00:00
# than the binary default. max_log_files similarly follows that
# reference (14, not the binary default of 7).
2026-08-21 13:58:06 +00:00
files :
- path : /var/lib/archipelago/cuprate/Cuprated.toml
content : |
network = "Mainnet"
2026-09-03 08:20:52 +00:00
fast_sync = true
target_max_memory = 8589934592
2026-08-21 13:58:06 +00:00
[rpc.restricted]
enable = true
2026-09-01 12:19:20 +00:00
[tracing.stdout]
level = "info"
[tracing.file]
2026-09-01 12:27:33 +00:00
level = "info"
max_log_files = 14
2026-08-21 13:58:06 +00:00
overwrite : false
health_check :
type : tcp
2026-08-22 03:10:53 -04:00
# Restricted RPC — the only RPC surface published now.
2026-08-23 01:43:09 -04:00
endpoint : localhost:18090
2026-08-21 13:58:06 +00:00
interval : 30s
timeout : 5s
retries : 3
start_period : 5m
metadata :
icon : /assets/img/app-icons/cuprate.svg
category : money
tier : optional
author : Cuprate
repo : https://github.com/Cuprate/cuprate