A turnkey docker compose stack for wallet providers who want to offer Arkade to
their users. It bundles five provider-side services behind a single automatic-HTTPS
edge, configured by one .env and launched by one script.
This stack connects to an existing, hosted Arkade Service — it does not run its own. You bring the endpoints of the Arkade Service you integrate with (from your operator, e.g. Ark Labs); the stack runs the value-added services your wallet users talk to.
| Service | What it does | Public endpoint |
|---|---|---|
| fulmine (delegate mode) | Runs as a delegate: renews users' VTXOs so their funds don't expire while they're offline. | delegate.<domain> |
| lnurl-server | LNURL-pay / Lightning Address service. Users receive Lightning payments into their Arkade wallet. | lnurl.<domain> |
| covclaimd | Non-custodial covenant-claim daemon: automatically claims preimage-locked VTXOs. Holds no funds and no wallet. | covclaim.<domain> |
| contract-backup (bucket-sync-server) | End-to-end-encrypted backup sync. Stores only ciphertext; wallets authenticate with their own secp256k1 key. | backup.<domain> |
| payment-push (bitcoin-payment-push-service) | Push-notifies a user's phone when an inbound Lightning payment lands. Monitor-only; wallets register per-invoice. | push.<domain> |
Supporting containers: Caddy (TLS/reverse proxy) and Postgres (storage for contract-backup only).
Upstream projects: fulmine · lnurl-server · covclaimd · bucket-sync-server · bitcoin-payment-push-service
┌───────────────── Caddy (:80/:443, automatic HTTPS) ─────────────────┐
Internet ───▶│ delegate.<d> lnurl.<d> backup.<d> covclaim.<d> push.<d> │
└────┬──────────┬──────────┬───────────┬────────────┬─────────────────┘
▼ ▼ ▼ ▼ ▼
fulmine:7002 lnurl:3000 backup:8080 covclaimd:7071 payment-push:3000
(delegate) (LNURL/SSE) │ (REST) (Boltz monitor)
fulmine:7001 ─ loopback ▼
(admin/UI) postgres:5432 (internal)
│ │
│ external — you set these in .env │
▼ ▼
Arkade Service + Esplora Arkade Service + Emulator + Boltz
The services do not form a mesh — each is dialed into by the end-user wallet. The only shared external dependencies are the Arkade Service (used by fulmine and covclaimd) and an Esplora chain API (used by fulmine). Caddy is the sole ingress.
- A Linux server with a public IP (a small VPS is fine). Everything installs via
setup.sh; you don't need Docker preinstalled. - A domain you control, so you can create five subdomain DNS records.
- Endpoints for the Arkade Service you're integrating with:
- its URL (for fulmine) and its gRPC address (for covclaimd),
- a covenant emulator gRPC address (for covclaimd),
- an Esplora-compatible chain API URL (a public one such as
https://mempool.space/apiworks), - a Boltz API URL (for payment-push).
Architecture note: the
contract-backupimage is published forlinux/amd64only. On anarm64host, build it from source — see Caveats.
git clone https://github.com/ArkLabsHQ/arkade-wallet-stack.git
cd arkade-wallet-stack
./setup.shsetup.sh will:
- Install any missing prerequisites (git, Docker Engine, the Compose plugin).
- Prompt you for the required values and generate all secrets into
.envandsecrets/fulmine_password. - Pull the images and start the stack.
Then finish the two manual steps it prints: create the DNS records and perform the one-time fulmine wallet creation (both detailed below).
Prefer to configure by hand? Copy .env.example to .env, fill it in, and run make up.
All configuration lives in .env (git-ignored). setup.sh writes it for you; these are the keys:
| Key | Required | Description |
|---|---|---|
BASE_DOMAIN |
yes | Base domain. All service subdomains derive from it. |
ACME_EMAIL |
yes | Email Caddy uses to register Let's Encrypt certificates. |
ARKADE_SERVER_URL |
yes | Arkade Service as a URL (used by fulmine), e.g. https://arkd.example.com. |
COVCLAIM_ARK_URL |
yes | The same Arkade Service as a gRPC address (used by covclaimd), e.g. arkd.example.com:7070. |
COVCLAIM_EMULATOR_URL |
yes | Covenant emulator gRPC address (used by covclaimd). |
ESPLORA_URL |
yes | Esplora chain API base URL (used by fulmine). |
BOLTZ_API_URL |
yes | Boltz API base URL (used by payment-push). |
ARKADE_NETWORK |
default bitcoin |
Network literal for payment-push (bitcoin/signet/mutinynet/…). |
NTFY_BASE_URL |
default https://ntfy.sh |
Push provider for payment-push (or switch to GroundControl/Expo in compose). |
LNURL_TOKEN_ENCRYPTION_KEY |
generated | 32-byte hex key encrypting wallet tokens at rest in lnurl-server. |
COVCLAIM_SECRET_KEY |
generated | 32-byte hex secp256k1 key for covclaimd. |
POSTGRES_PASSWORD |
generated | Password for the bundled Postgres. |
FULMINE_DELEGATE_FEE |
default 0 |
Fee, in satoshis, the delegate charges per delegation. |
The fulmine wallet password is not stored in .env; it lives in secrets/fulmine_password
(mode 600), which fulmine reads to auto-unlock on startup.
Advanced: pin or upgrade image versions by uncommenting the *_IMAGE overrides in
.env.example.
Create four A records pointing at your server's public IP:
delegate.<BASE_DOMAIN> → <server IP>
lnurl.<BASE_DOMAIN> → <server IP>
backup.<BASE_DOMAIN> → <server IP>
covclaim.<BASE_DOMAIN> → <server IP>
push.<BASE_DOMAIN> → <server IP>
Once they resolve, Caddy obtains and renews TLS certificates automatically. No certificate files to manage.
The delegate stays offline until a wallet exists and is unlocked. This is a one-time step;
after it, fulmine auto-unlocks on every restart using secrets/fulmine_password.
The fulmine admin UI is bound to loopback only (127.0.0.1:7001) — it is never exposed
to the internet. Reach it over an SSH tunnel:
# your generated wallet password:
cat secrets/fulmine_password
# from your laptop, tunnel to the server's loopback:
ssh -L 7001:127.0.0.1:7001 <this-server>
# then open http://localhost:7001, create the wallet, and unlock it with that passwordmake ps # service status
make logs # follow logs
make update # pull latest pinned images and recreate changed containers
make down # stop (keeps volumes / data)
make config # render the effective compose configuration
make help # all targetsThis stack ships mainnet-safe defaults:
- fulmine keeps macaroon auth on. Its admin/API surface is loopback-only; only the public delegate API is exposed (through Caddy).
- lnurl-server always encrypts wallet tokens at rest — the insecure-storage shortcut is
never enabled. Its unauthenticated admin API (
:3001) is not exposed. - Postgres is never published to the host; it is reachable only on the internal network.
- payment-push is monitor-only (holds no keys); its public
POST /registeris rate-limited per client IP at the Caddy edge. - Secrets are strong-random and written with tight permissions;
.envandsecrets/are git-ignored. Never commit them. - All public traffic is TLS-terminated by Caddy.
contract-backupislinux/amd64-only. Onarm64, build it from source and point the stack at your local image:git clone https://github.com/Kukks/bucket-sync-server.git docker build -t bucket-sync-server:local -f bucket-sync-server/Dockerfile bucket-sync-server # then set BUCKET_SYNC_IMAGE=bucket-sync-server:local in .envcontract-backuphas no in-container healthcheck. Its image ships no shell, so health is observed at the Caddy edge rather than by Docker.covclaimdhas no:latesttag — it is pinned to a prerelease (v0.0.1-rc.0). BumpCOVCLAIMD_IMAGEwhen a newer tag is published.- Lightning Addresses default to
user@lnurl.<BASE_DOMAIN>. To serve bareuser@<BASE_DOMAIN>addresses, add a Caddy route on the apex domain proxying/.well-known/lnurlp/*tolnurl-server:3000. - payment-push pins an interim image built from a fork
(
ghcr.io/kukks/bitcoin-payment-push-service) while the upstream packaging PR is in review. SwitchPAYMENT_PUSH_IMAGEtoghcr.io/tiero/…once it's merged and published. - Caddy is built locally (
./caddy/Dockerfile) to bundle the rate-limit plugin, so the firstupcompiles it. SetCADDY_IMAGEto a prebuilt image if you maintain your own.
Caddy is bundled for turnkey TLS (built locally with the rate-limit plugin). If you already
run an edge (Traefik, nginx, etc.), remove the caddy service from docker-compose.yml,
publish the service ports you need, and route to them from your existing proxy — and
reproduce the POST /register rate-limit there. The internal service ports are: fulmine
delegate 7002, lnurl 3000, contract-backup 8080, covclaimd REST 7071,
payment-push 3000.
- A subdomain won't get a certificate — confirm its DNS
Arecord resolves to this server and that ports 80/443 are open. Checkmake logsfor Caddy ACME errors. docker compose upfails immediately with a variable error — a required.envvalue or thesecrets/fulmine_passwordfile is missing. Re-run./setup.sh.- The delegate isn't responding — verify the fulmine wallet was created and unlocked once
(see above), and check
docker compose logs fulmine.