Dojo: Ashigaru Wallet Server
Dojo is the wallet backend for the Ashigaru wallet (formerly Samourai). It stores your extended public keys, handles BIP47 payment codes, and answers your wallet's questions about balances and history. Run it yourself and no third party ever sees your wallet's metadata.
This guide takes you from a standard Ubuntu machine to a working Dojo. Every command is given in full. Every step shows what the screen should look like when it works, and what to do when it does not. You do not need server experience. You do need patience: the first chain sync takes days.
If you are brand new to the terminal, read these two short guides of mine first: terminal basics and the nano editor.
There are two ways to build this, and you pick once, up front:
- Path A, everything in Docker. You start with an empty machine. One install command brings Bitcoin Core, a Fulcrum indexer, a block explorer, Tor, a MariaDB database, and the Dojo API itself. The chain downloads from scratch, which takes 2 to 7 days. This is the setup the Ashigaru team tests against. If you are unsure, pick this.
- Path B, the hybrid. You already run Bitcoin Core and Fulcrum on this machine as regular system services, fully synced. Dojo runs in Docker beside them and points at them. Nothing is re-downloaded, so you skip the week of waiting. This path has more steps, because you must wire the Docker world to your existing services.
Either way you end at the same place: a Dojo your wallets pair to over Tor.
What you need before starting
Hardware:
- x86-64 computer, 2 GHz dual-core or better
- 8 GB of RAM
- 2 TB NVMe SSD (Path A ends up storing roughly 800 GB of chain and index data)
- Reliable internet, machine left on around the clock
Software: Ubuntu 26.04 LTS. A refurbished Optiplex, EliteDesk, or ThinkCentre is ideal. See my hardware guide.
Path B only: Bitcoin Core (with txindex=1) and Fulcrum already installed, synced, and running as services on this same machine. If that sentence means nothing to you yet, you want Path A.
How to read this guide:
- Lines in gray boxes are commands. Paste them into the terminal and press Enter. In most terminals, paste is
ctrl+shift+v. - Text in angle brackets, like
<MY-DOCKER-IP>, is a placeholder. Never paste it as-is. Part 4 has you discover your own values and swap them in. - A "healthy output" block shows what the screen looks like when a step works. Compare yours to it before moving on.
One thing to know before anything else: sudo means "run this as administrator." The first time, it asks for your password. While you type it, the screen shows nothing. That is normal. Keep typing and press Enter.
Part 1: Prepare the machine
Update the system and install the tools we need:
cd ~/
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common unzip torsocks
torsocks is in that list because the download in Part 2 is done over Tor.
Now Docker. Skip this block if docker run hello-world already works on this machine.
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
That last line adds your user to the docker group, so you will not need sudo for every docker command. It only takes effect after you log out and log back in. Do that now, then verify:
docker run hello-world
Healthy output starts with Hello from Docker!. If you do not see it, reboot once and try again before looking for deeper problems.
Part 2: Download and verify Dojo
This is money software, so we do not trust the download. We check two things: that the file came from the real developer (a PGP signature), and that it is the exact file released (a SHA256 hash). The full reasoning is in my guide on verifying software.
At the time of writing, the newest release is 1.29.3. Check the releases page of the Dojo-Open-Source-Project/samourai-dojo repository on GitHub. If a newer version exists, use that number everywhere you see 1.29.3 below.
cd ~/
DOJO_VER=1.29.3
That line creates a variable holding the version number, so the rest of the commands can reuse it.
Fetch the developer's signing key and the release itself, all over Tor:
torsocks wget https://keys.openpgp.org/vks/v1/by-fingerprint/E53AD419B242822F19E23C6D3033D463D6E544F6
gpg --import E53AD419B242822F19E23C6D3033D463D6E544F6
torsocks wget -O samourai-dojo-$DOJO_VER.zip https://github.com/Dojo-Open-Source-Project/samourai-dojo/archive/refs/tags/v$DOJO_VER.zip
torsocks wget https://github.com/Dojo-Open-Source-Project/samourai-dojo/releases/download/v$DOJO_VER/samourai-dojo-$DOJO_VER-fingerprints.txt
torsocks wget https://github.com/Dojo-Open-Source-Project/samourai-dojo/releases/download/v$DOJO_VER/samourai-dojo-$DOJO_VER-fingerprints.txt.sig
Check the signature:
gpg --verify samourai-dojo-$DOJO_VER-fingerprints.txt.sig
Healthy output looks like this (dates and names will differ):
gpg: Signature made Tue 01 Sep 22:28:00 2026 UTC
gpg: Good signature from "Developer Name" [unknown]
WARNING lines below that are normal and safe to ignore. The words that matter are Good signature. Anything else, like BAD signature, means stop. Delete everything and start Part 2 again.
Check the file itself:
sha256sum samourai-dojo-$DOJO_VER.zip
cat samourai-dojo-$DOJO_VER-fingerprints.txt
The first command prints one long string of letters and numbers, which is the fingerprint of your downloaded file. The second prints the official list of fingerprints. Find the line for the zip file in that list and compare the two strings, character for character. They must match exactly. If they do not, delete the zip and download it again.
Unpack:
unzip samourai-dojo-$DOJO_VER.zip -d .
mkdir -p ~/dojo-app
mv ~/samourai-dojo-$DOJO_VER/* ~/dojo-app/
rm -r samourai-dojo-$DOJO_VER samourai-dojo-$DOJO_VER.zip samourai-dojo-$DOJO_VER-fingerprints.txt samourai-dojo-$DOJO_VER-fingerprints.txt.sig E53AD419B242822F19E23C6D3033D463D6E544F6
You now have a folder called ~/dojo-app. Everything Dojo-related lives in there from now on.
Part 3: Generate your secrets
Dojo needs eight passwords before it will install. Make each one in your password manager (Bitwarden, KeePassXC, anything with a generator).
Settings for all eight:
- Length: 40 characters
- Letters and numbers only. No symbols. The config files reject characters like
! @ # $ - Each one different from the others
| Secret | Goes in | What it is for |
|---|---|---|
| BITCOIND_RPC_USER | docker-bitcoind.conf.tpl | Username for bitcoind's remote control |
| BITCOIND_RPC_PASS | docker-bitcoind.conf.tpl | Password for that username |
| MYSQL_ROOT_PASSWORD | docker-mysql.conf.tpl | Master password for the database |
| MYSQL_USER | docker-mysql.conf.tpl | Database account name |
| MYSQL_PASSWORD | docker-mysql.conf.tpl | Password for that account |
| NODE_API_KEY | docker-node.conf.tpl | Key wallets use to talk to Dojo |
| NODE_ADMIN_KEY | docker-node.conf.tpl | Your login for the Dojo dashboard |
| NODE_JWT_SECRET | docker-node.conf.tpl | Used to sign login sessions |
One note: some releases name the bitcoind password line BITCOIND_RPC_PASSWORD instead of BITCOIND_RPC_PASS. Use whichever line already exists in your file.
Generate all eight now and keep the list open. After the install, the only one you need to keep in your password manager forever is NODE_ADMIN_KEY, because it is your dashboard login. The others live inside the config files and can be looked up there later.
About the config files: Dojo reads settings from template files that end in .tpl. You put your values into those, and the installer generates the real .conf files from them. Two rules:
- Only edit files ending in
.tpl - Never edit files ending in
.conf, because upgrades overwrite them
The files live in ~/dojo-app/docker/my-dojo/conf/. Open one like this:
cd ~/dojo-app/docker/my-dojo/conf/
nano docker-mysql.conf.tpl
In nano, the text editor:
- Move around with the arrow keys. The mouse does nothing.
- Delete with Backspace.
- Paste with
ctrl+shift+v - Save and exit:
ctrl+x, theny, then Enter - Made a mistake and want to start over: exit without saving with
ctrl+x, thenn, then Enter, and reopen the file
You will repeat that open, edit, save cycle for every file in Parts 5A and 5B.
Part 4: Find your numbers (the worksheet)
Machines on a network have addresses, like houses on a street. Docker builds its own private street inside your machine, and the addresses there are different from the ones on your home network. They also differ from person to person, so this guide cannot print yours. You will discover them now and write them on paper.
Run each command, compare with the sample output, and write your value into the worksheet at the end.
Your Docker host address. This is the address of the machine itself, as seen from inside Docker:
ip -4 addr show docker0
Sample output:
4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
inet 172.17.0.1/16 ...
Find the line that starts with inet. The number before the slash is your value. In the sample it is 172.17.0.1. Write it in as MY-DOCKER-IP.
Dojo's container network. The file you unpacked defines the private street's address range:
grep 'subnet:' ~/dojo-app/docker/my-dojo/docker-compose.yaml
Sample output:
- subnet: 172.22.0.0/16
- subnet: 172.23.0.0/16
The first line is the network called dojonet, where the Dojo containers live. Write the whole thing, slash included, as MY-DOJET-SUBNET. In the sample that is 172.22.0.0/16. The /16 means "this entire range of addresses," so keep it. Do not trim it down to a single address.
Your LAN address. This is the machine's address on your home network:
ip -4 addr show scope global
Find the block belonging to your wired network card (its name usually starts with enp or eth) and its inet line. Write the number before the slash as MY-LAN-IP. Ignore blocks for docker0, tailscale0, or anything starting with veth.
Your Tailscale address, if you run Tailscale:
tailscale ip -4
It will look like 100.something.something.something. Write it in as MY-TAILSCALE-IP. Skip this if you do not use Tailscale.
The worksheet. Copy this onto paper and fill it in:
MY-DOCKER-IP: ______________________
MY-DOJONET-SUBNET: ______________________
MY-LAN-IP: ______________________
MY-TAILSCALE-IP: ______________________
For reference, a typical machine might have 172.17.0.1, 172.22.0.0/16, 192.168.8.20, and 100.101.102.103. Yours will differ. Trust only what your own terminal printed.
If you run Tailscale, one rule keeps everything simple: Docker containers always reach the machine's services at MY-DOCKER-IP. They never use the 100.x address. Your other devices, like your laptop and phone, use MY-LAN-IP or MY-TAILSCALE-IP. Later, one config line asks which address wallets should use to reach your electrum service. That is a wallet question, not a Docker question: use MY-LAN-IP if the device connects over your Wi-Fi, MY-TAILSCALE-IP if it connects through the tailnet.
Part 5A: Path A, everything in Docker
Four files to edit, then one command. Move into the config folder:
cd ~/dojo-app/docker/my-dojo/conf/
File 1: bitcoind settings
nano docker-bitcoind.conf.tpl
Set BITCOIND_RPC_USER and BITCOIND_RPC_PASS to your first two secrets. If the machine has 16 GB of RAM or more, also set BITCOIND_DB_CACHE=2048, which speeds up the sync. Leave BITCOIND_INSTALL=on, which is the default. Save and exit.
File 2: database settings
nano docker-mysql.conf.tpl
Set MYSQL_ROOT_PASSWORD, MYSQL_USER, and MYSQL_PASSWORD. If your machine has only 8 GB of RAM, also set MYSQL_CONF_PROFILE=low_mem to keep the database frugal. Save and exit.
File 3: indexer settings
nano docker-indexer.conf.tpl
Set these lines:
INDEXER_INSTALL=on
INDEXER_TYPE=fulcrum
INDEXER_BATCH_SUPPORT=active
INDEXER_EXTERNAL=on
INDEXER_EXTERNAL_IP=<MY-LAN-IP>
The last line tells wallets where to find the electrum service. Use your MY-LAN-IP if other devices (your phone, your laptop) will use it. If everything that matters runs on this one machine, leave whatever default value is already in the file. Save and exit.
File 4: Dojo settings
nano docker-node.conf.tpl
Set NODE_API_KEY, NODE_ADMIN_KEY, and NODE_JWT_SECRET. Also set NODE_ACTIVE_INDEXER=local_indexer. Save and exit.
Install
cd ~/dojo-app/docker/my-dojo
if grep -q 'node:20-alpine' node/Dockerfile; then
sed -i 's/node:20-alpine3.20/node:24-alpine3.22/g' node/Dockerfile
fi
./dojo.sh install
About that patch block: release 1.29.x ships with a packaging bug. The nodejs image is built on an old version of Node while the Dojo code needs a newer one, and without the fix the dashboard crashes after install. The check looks for the old version line and swaps in the new one. If a later release fixes the bug, the check finds nothing and changes nothing. It is safe on any version.
The installer asks whether you wish to continue. Type y and press Enter. It then builds the images. Expect 10 to 30 minutes of scrolling text.
When it finishes, the chain download starts on its own. Nothing to babysit. Check on it:
./dojo.sh logs bitcoind
Healthy output while downloading:
IBD progress = 0.051000 (5.1%)
Watch that percentage climb. Done is shown as progress = 1.000000. Press ctrl+c to stop watching. This only closes your view of the logs; the download keeps running. Then check the indexer:
./dojo.sh logs fulcrum
Fulcrum waits for bitcoind to finish, then builds its index. Look for a percentage climbing to 100. After both are done, the Dojo tracker catches up on wallet data, usually within a few hours.
Total time from install to fully green: 2 to 7 days, depending mostly on your internet. Leave the machine on. When you are done waiting, continue at Part 6.
Part 5B: Path B, borrow your existing node
The single line that matters in this path is BITCOIND_INSTALL=off. It tells the installer: do not download another copy of the chain, one already exists here. Everything else in this part is plumbing that lets the Docker containers reach your existing services.
Sanity check first:
sudo systemctl status bitcoind
sudo systemctl status fulcrum
Both must say active (running). If not, fix that before continuing.
5B.1: Let bitcoind accept visitors from Docker
bitcoind has a remote-control interface called RPC. By default it only accepts knocks from the machine itself. Containers knock from their own separate network, so we tell bitcoind two things: where to listen, and which visitors to trust.
nano /home/satoshis/.bitcoin/bitcoin.conf
(If your username is not satoshis, use your own in the path.) Add or change these lines. Leave everything else in the file alone:
rpcuser=satoshis
rpcpassword=Your40CharacterAlphanumericPassword
rpcbind=<MY-DOCKER-IP>
rpcallowip=127.0.0.1
rpcallowip=<MY-DOJONET-SUBNET>
zmqpubrawtx=tcp://<MY-DOCKER-IP>:28333
zmqpubhashblock=tcp://<MY-DOCKER-IP>:28334
Line by line:
rpcuserandrpcpasswordare the login for bitcoind's remote control. The password follows the same rule as your secrets: letters and numbers only, because Dojo stores it in its own config. If your file has a line starting withrpcauth, delete it; that hashed style does not work with Dojo.rpcbindsays which address bitcoind listens on. Containers knock at MY-DOCKER-IP, so it must listen there.- The two
rpcallowiplines say who may log in.127.0.0.1is the machine itself. The second line trusts the whole container network range. Why that range and not MY-DOCKER-IP: a container connects to that address, but its request carries the container's own address as the return label, and bitcoind checks the return label. Trust the range the labels come from. - The two
zmqpublines are announcement wires. bitcoind broadcasts every new transaction and block onto them, and Dojo listens so your wallet updates instantly. The port numbers, 28333 and 28334, must match the template in 5B.3.
If other software on this machine talks to bitcoind with the old RPC password, such as a block explorer, update its config with the new password too.
Restart and check:
sudo systemctl restart bitcoind
ss -ltnp | grep 8332
Healthy output: one line containing LISTEN and your MY-DOCKER-IP followed by :8332. If nothing prints, bitcoind did not load the new config. Run sudo systemctl status bitcoind and read the last few log lines for the reason.
5B.2: Open the firewall
If you use UFW, the default Ubuntu firewall, it blocks the containers' knocks. Allow the container range to reach three specific doors:
sudo ufw allow from <MY-DOJONET-SUBNET> to any port 8332 proto tcp comment 'Dojo to bitcoind RPC'
sudo ufw allow from <MY-DOJONET-SUBNET> to any port 28332:28334 proto tcp comment 'Dojo to bitcoind ZMQ'
sudo ufw allow from <MY-DOJONET-SUBNET> to any port 50001 proto tcp comment 'Dojo to Fulcrum'
The doors: 8332 is bitcoind's remote control, 28332 to 28334 are the announcement wires, 50001 is Fulcrum's electrum service. If you do not use UFW, skip this. If you run Tailscale and want tailnet devices to reach this machine:
sudo ufw allow in on tailscale0
Never forward any of these ports from your router to the internet. They are for your machine and your containers only.
5B.3: Configure the templates
Move into the config folder:
cd ~/dojo-app/docker/my-dojo/conf/
File 1, docker-bitcoind.conf.tpl:
BITCOIND_INSTALL=off
BITCOIND_IP=<MY-DOCKER-IP>
BITCOIND_RPC_PORT=8332
BITCOIND_ZMQ_RAWTXS=28333
BITCOIND_ZMQ_BLK_HASH=28334
BITCOIND_RPC_USER=satoshis
BITCOIND_RPC_PASS=Your40CharacterAlphanumericPassword
BITCOIND_INSTALL=off is the line that prevents the 700 GB re-download. BITCOIND_IP is where the containers knock, the same MY-DOCKER-IP as in 5B.1. The user and password must match your bitcoin.conf exactly.
Everything in this file under the headings about onion addresses, bloom filters, and exposing RPC switches itself off when BITCOIND_INSTALL=off. Do not spend time on those sections.
File 2, docker-indexer.conf.tpl:
INDEXER_INSTALL=off
INDEXER_TYPE=fulcrum
INDEXER_IP=<MY-DOCKER-IP>
INDEXER_RPC_PORT=50001
INDEXER_BATCH_SUPPORT=active
INDEXER_PROTOCOL=tcp
INDEXER_EXTERNAL=on
INDEXER_EXTERNAL_IP=<MY-LAN-IP>
INDEXER_INSTALL=off keeps Fulcrum on the metal. INDEXER_IP is where the containers find it. For the last line, choose by how your wallets reach this machine: MY-LAN-IP if they are on your Wi-Fi, MY-TAILSCALE-IP if they come through the tailnet.
File 3, docker-explorer.conf.tpl: set EXPLORER_INSTALL=off if you already run a block explorer such as mempool.space on this machine, because a second one is redundant. Otherwise leave it on and enjoy the built-in BTC-RPC Explorer.
File 4 and 5, docker-mysql.conf.tpl and docker-node.conf.tpl: the same secrets as Part 3, and in the node file set NODE_ACTIVE_INDEXER=local_indexer. The word "local" here means reachable, not containerized, so it stays correct on this path.
5B.4: Install
Same as Path A. Run the patch check and the installer:
cd ~/dojo-app/docker/my-dojo
if grep -q 'node:20-alpine' node/Dockerfile; then
sed -i 's/node:20-alpine3.20/node:24-alpine3.22/g' node/Dockerfile
fi
./dojo.sh install
No chain download this time, because your node is already synced. The Dojo tracker reads your existing Fulcrum index and catches up, usually within hours.
Confirm what is running:
docker compose ps
Healthy output lists exactly four containers: db, nodejs, nginx, and tor. If bitcoind or fulcrum show up as containers, one of your INSTALL flags is still on; recheck 5B.3. If fewer than four show up, run ./dojo.sh logs and read the errors.
Part 6: Verify it works
Watch the logs:
./dojo.sh logs -f
Healthy signs within the first few minutes:
Importer : Successfully connected to indexer
Tracker : Processing active Mempool
Block heights should climb to the current tip. Press ctrl+c when you have seen enough; this only closes your view.
Bad signs, with fixes in Part 9: Bitcoind RPC : API is still unreachable, or connect ECONNREFUSED.
The dashboard. Dojo ships with a control panel called the Dojo Maintenance Tool, and it is reachable only through Tor. Get your address:
./dojo.sh onion
Sample output:
Dojo Maintenance Tool : abcdef...onion
Dojo API : ghijkl...onion
Take the Maintenance Tool line. In Tor Browser, which you can get from torproject.org, set the security level to Standard or Safer, because the page needs JavaScript. Paste that address into the address bar, add /admin to the end, and press Enter. The page asks for your admin key: paste NODE_ADMIN_KEY from your password manager and click CONNECT.
If the status page shows green checks next to Full Node, Indexer, Tracker, and Dojo DB, your Dojo is live. Orange checks during a first sync are normal and mean work in progress. Red crosses mean Part 9.
Part 7: Pair your wallets
In the Maintenance Tool, open the Pairing tab. A QR code appears. In the Ashigaru app, go to Settings, choose to connect to your own server, and scan that code. From then on, the wallet talks to your Dojo, over Tor, end to end.
Sparrow Wallet can use your stack too. Add the Dojo as a server in Sparrow, or skip Dojo entirely and point Sparrow's electrum server setting straight at Fulcrum: host MY-LAN-IP (or MY-TAILSCALE-IP), port 50001.
Part 8: Everyday use and updates
Commands you will actually use, run from ~/dojo-app/docker/my-dojo:
| Command | What it does |
|---|---|
| ./dojo.sh logs | Watch all logs; ctrl+c to exit the view |
| ./dojo.sh stop | Shut everything down cleanly |
| ./dojo.sh start | Start it again |
| ./dojo.sh restart | Stop, then start |
| ./dojo.sh onion | Show your Tor addresses again |
| ./dojo.sh version | Show the installed version |
| ./dojo.sh help | List every available command |
Three rules that prevent pain:
- Run
./dojo.sh stopbefore powering off, rebooting, or updating. Pulling power while the databases are writing can corrupt them, and Fulcrum's database is the fragile one. - Never run
docker system prune --all --volumes, and never delete a Docker volume. The volume nameddata-mysqlis Dojo's entire database. On top of that, Dojo builds every image locally, which is why the install took a while, and the tor image can only be rebuilt from the local cache. Deleting what looks like junk can take out both. - Never forward ports 8332, 50001, or 28332 through 28334 from your router.
Updating to a new release: repeat Part 2's download and verification with the new version number, then:
cd ~/dojo-app/docker/my-dojo
./dojo.sh stop
cp -a ~/samourai-dojo-$DOJO_VER/. ~/dojo-app/
grep DOJO_VERSION_TAG .env
./dojo.sh upgrade -y
That grep line is a check. It must print the new version number. If it still shows the old one, open .env in nano and change DOJO_VERSION_TAG by hand, or the upgrade will reinstall the version you already have. After any upgrade, run the patch check from Part 5A again, because the new files can bring the old bug back with them.
Part 9: Troubleshooting
| Symptom | What it means | Fix |
|---|---|---|
| connect ECONNREFUSED, ending in :8332 | bitcoind is not listening where Docker expects | Check 5B.1: rpcbind must be MY-DOCKER-IP, and ss -ltnp must show LISTEN on it |
| Bitcoind RPC : API is still unreachable | bitcoind heard the knock but does not trust the visitor | rpcallowip must be the whole dojonet subnet. Run docker network inspect my-dojo_dojonet and match your rule to what it prints |
| Dashboard blank, or the nodejs container keeps restarting | the Node version bug from Part 5A | Apply the patch, then run docker compose build --no-cache node, then ./dojo.sh restart |
| Onion address will not load | tor container is wedged | docker restart tor |
| Fulcrum database corrupt after a power cut | the machine lost power without ./dojo.sh stop | Path A only: ./dojo.sh stop, docker container rm fulcrum, docker volume rm my-dojo_data-fulcrum, then ./dojo.sh upgrade. Expect 1 to 2 days of reindexing. On Path B, Fulcrum is a host service, so rebuild its database there |
| Fulcrum stuck for hours during an upgrade | database migration to a new format | Wait. Interrupting it corrupts the database |
If nothing here matches your problem, copy the exact error line plus the ten lines around it from ./dojo.sh logs, and ask in the Ashigaru community with that paste.
Don't trust. Verify.