Run a node
The eiou-docker image bundles everything a node needs — nginx, PHP-FPM, MariaDB, Tor, the P2P processors, and the web GUI — so a single docker compose up brings a wallet online. This page is split into Setup (one-time install and configuration, ending with docker compose up and the GUI sign-in) and Node management (storage, recovery from seed, container control, multi-node, troubleshooting). CLI examples run from the host; everything inside the container is reachable through docker exec <container>. For the full environment-variable, volume, SSL, and network reference, see Docker Configuration.
One-time steps to install, configure, and bring up your node. The defaults — Docker-internal address https://eiou, auto-generated self-signed certificate, auto-generated wallet and Tor onion — are enough to test on a single host. Skip past the configuration sections unless you need to: rename the container (running more than one node), publish externally (peers reach you over the public internet or LAN), or replace SSL (real Let's Encrypt / CA-signed cert instead of self-signed).
Prerequisites
Everything else — PHP, MariaDB, Tor, nginx, processors — ships inside the image. You don't install anything else on the host.
- Docker 20.10+ (the Compose plugin is included)
- ~1 GB free disk for the image and database
- 512 MB RAM minimum (1 GB recommended)
- Free TCP ports for whatever you want to publish (default
80/443)
Get the code (or the image)
Two ways to start: clone the repo and build locally, or pull the prebuilt image straight from Docker Hub. Either way the only configuration file you'll touch is docker-compose.yml — it is heavily commented; uncomment what you need and everything else has working defaults.
- Clone & build — gets you the source, the
docker-compose.yml, the helper scripts, and lets you patch or audit anything. Subsequentdocker compose upcalls without--buildreuse the local image - Pull from Docker Hub — faster, no source on disk. You still need a
docker-compose.yml— either grab it from the repo or write a minimal one and replace thebuild:block withimage: eiou/eiou:latest
git clone https://gitlab.com/eiou-org/eiou-docker.gitcd eiou-dockerdocker pull eiou/eiou:latestdocker-compose.yml so you have something to compose up — edit the build: block to image: eiou/eiou:latest afterwardscurl -O https://gitlab.com/eiou-org/eiou-docker/-/raw/main/docker-compose.ymlName your node
The container name and every volume name derive from NODE_NAME (default eiou-node). Set it to anything else when you want to run more than one node side by side without name collisions, or just to give the container a recognizable label. EIOU_NAME is separate — it sets the cosmetic display name in the local GUI header and is never sent to other nodes.
NODE_NAME (and every other env var on this page) can be supplied three ways — pick whichever fits your setup; the container reads the same variable either way:
- A
.envfile next todocker-compose.yml—docker composeauto-loads it. Best for values you want under version control or that change between hosts - An
environment:block directly indocker-compose.yml— what the External access, SSL, and Restore examples use. Best for values that travel with the compose file - A
-eflag ondocker runif you're not using compose at all (e.g.docker run -e NODE_NAME=my-wallet ... eiou/eiou) — one-off launches and CI scripts
With the bundled Compose file, setting NODE_NAME=my-wallet creates container my-wallet and prefixes its seven named volumes with my-wallet-. A raw docker run command must declare those mounts explicitly.
.env file (with docker compose)Drop a file called .env next to docker-compose.yml with one line per variable. docker compose up picks it up automatically.NODE_NAME=my-walletenvironment: block in docker-compose.ymlInline the value in the service definition. Same result, but lives in the compose file rather than a sibling .env.environment: - NODE_NAME=my-walletdocker run -eIf you skip docker compose, declare all seven persistent mounts yourself. Pass environment variables before the image name.docker run -d --restart unless-stopped --name my-wallet -e NODE_NAME=my-wallet -p 80:80 -p 443:443 -v my-wallet-mysql-data:/var/lib/mysql -v my-wallet-config:/etc/eiou/config -v my-wallet-plugins:/etc/eiou/plugins -v my-wallet-plugin-scratch:/var/lib/eiou/plugin-scratch -v my-wallet-backups:/var/lib/eiou/backups -v my-wallet-backup-locks:/var/lib/eiou/backup-locks -v my-wallet-ssl-cert:/var/lib/eiou/ssl eiou/eiou:latestReach the node from outside Docker
Three ways to expose a node:
- Default (
QUICKSTART=eiou) — the bundleddocker-compose.ymlexposes bothhttp://eiouandhttps://eiou(with an auto-generated self-signed cert) inside the Docker network only. Private-address delivery fails closed unless you explicitly setEIOU_ALLOW_PRIVATE_DELIVERY=true. With that development-only opt-in, practical node-to-node reachability is HTTP: the defaultP2P_SSL_VERIFY=truestill rejects self-signed HTTPS certificates unless you share a CA (see SSL certificates). Neither surface is reachable outside the Docker network - Public IP or FQDN — set
EIOU_HOSTto a real address and (optionally)EIOU_PORTfor a non-standard port. Pair with Let's Encrypt or a CA-signed cert (see SSL certificates) so peers don't trip over a self-signed warning - Tor-only — comment out both
QUICKSTARTandEIOU_HOSTindocker-compose.yml. The node skips HTTP/HTTPS entirely and is reachable only at its.onionaddress
When both EIOU_HOST and QUICKSTART are set, EIOU_HOST wins for the registered address (and for the SSL CN) — QUICKSTART stays around as the in-Docker hostname only. EIOU_NAME is purely the cosmetic display label in the local GUI; it never goes out on the wire.
QUICKSTART from docker-compose.yml (or leave the default; EIOU_HOST overrides it). The node registers https://88.99.69.172:8443 as its address.environment: - EIOU_NAME=My eIOU Node - EIOU_HOST=88.99.69.172 - EIOU_PORT=8443SSL certificates
SSL only matters if the node is serving HTTP/HTTPS — i.e. you have a hostname set via QUICKSTART or EIOU_HOST. Tor-only nodes skip this entirely (Tor's onion routing handles encryption itself). When there is a hostname, the container generates a self-signed certificate on first boot whose Common Name matches the resolved address — priority SSL_DOMAIN > EIOU_HOST > QUICKSTART. For anything more than local testing, replace the self-signed cert. The four supported approaches are checked in priority order:
- External certs mounted at
/ssl-certs(server.crt,server.key) - Let's Encrypt — set
LETSENCRYPT_EMAIL; needs a real FQDN inEIOU_HOSTand port 80 reachable from the internet - Local CA — mount
./ssl-cawith aca.crtthe container will trust - Reverse proxy or Cloudflare Tunnel — terminate SSL outside the container and let it keep its self-signed cert internally
Cert validation on outbound P2P is strict. The default P2P_SSL_VERIFY=true rejects any self-signed cert, so stock containers can reach each other over HTTP but not HTTPS — you need either P2P_SSL_VERIFY=false (dev/testing only), a shared CA via P2P_CA_CERT, or a real Let's Encrypt / CA-signed cert before HTTPS P2P starts working between nodes. Browser-side cert behavior is independent — you'll still get a one-time warning for self-signed regardless of the P2P setting.
For multi-node SSL setups, wildcard certificates, and browser CA-trust installation see SSL Certificate Configuration.
EIOU_HOST needs to resolve in public DNS to this container's host. Certbot inside the container uses the HTTP-01 challenge on port 80, then nginx switches to the issued cert automatically.environment: - EIOU_HOST=node.example.com - LETSENCRYPT_EMAIL=admin@example.comStart the node
Configuration done — bring it up. The first boot generates a fresh BIP39-derived wallet, initializes MariaDB, brings up nginx and the P2P processors, and starts Tor. The container's healthcheck flips to healthy once MariaDB and the GUI are reachable — usually around two minutes. Tor reachability is a separate, slower stage and is not on the healthcheck path. (If you're migrating an existing wallet rather than starting fresh, see Restore from a seed phrase first.)
- MariaDB is the slowest step on first boot (encrypted redo log + TDE setup) — default timeout is 60 s with auto-recovery; total wait is typically 30–90 s
- Tor hidden service needs to (1) generate the
.onionkey, (2) bootstrap the local Tor process, and (3) publish the descriptor to the Tor directory authorities. Steps 1–2 finish in about a minute; step 3 commonly takes another 5–15 minutes before other nodes can actually reach you over Tor. The defaultsEIOU_HS_TIMEOUT=60andEIOU_TOR_TIMEOUT=120only gate the in-container waits — raise them on WSL2 or slow links - The container watchdog auto-restarts any processor that crashes; if Tor goes silent it republishes the descriptor automatically (
~90 sverification window) - Subsequent restarts skip wallet generation and come up in seconds. The
.onionaddress is reused (it's derived from the seed), but Tor still needs to republish the descriptor — expect a few minutes before peers can reach the node again
--build if you used the Docker Hub path), create the volumes, and start the container detached. Run this once.docker compose up -d --builddocker compose logs -fUp (healthy) once MariaDB and the GUI are ready.docker compose psOpen the GUI
Point your browser at the node. With the default QUICKSTART=eiou setting, HTTP redirects to HTTPS and the certificate is self-signed — expect a one-time browser warning. The Tor address is reachable from Tor Browser with no warning at all.
- Local browser:
https://localhost - Tor Browser: open the node's
.onionaddress (printed in the startup banner) - Sign in with the auth code shown in the OPEN ALPHA banner the container prints on first boot
On first boot the container also writes the seed phrase + auth code to a single tmpfs file at /dev/shm/eiou_wallet_info_<random>. That file lives in RAM, has mode 0400, and is auto-deleted after 15 minutes — so copy the seed phrase out into your password manager before it vanishes. After it's gone (or any time on a restored node), eiou info --show-auth regenerates the auth-code-only file on demand with the same 15-minute TTL.
docker exec eiou-node ls /dev/shm/<random-hex> with the suffix you saw above (e.g. a1b2c3...):docker exec eiou-node cat /dev/shm/eiou_wallet_info_<random-hex>docker exec eiou-node eiou info --show-authDay-to-day operations and recovery once your node is up: storage, restoring from a seed, container control, multi-node, and troubleshooting.
Persistent storage
Seven named Docker volumes survive restarts and rebuilds. The bundled Compose file prefixes their names with NODE_NAME. Treat the seven as one recovery set: mixing volumes from different recovery points is unsupported.
{NODE_NAME}-mysql-data→/var/lib/mysql— transactions, contacts, balances (critical){NODE_NAME}-config→/etc/eiou/config— wallet keys, encryption keys (critical){NODE_NAME}-plugins→/etc/eiou/plugins— installed plugin directories{NODE_NAME}-plugin-scratch→/var/lib/eiou/plugin-scratch— durable private plugin state{NODE_NAME}-backups→/var/lib/eiou/backups— encrypted backups (critical){NODE_NAME}-backup-locks→/var/lib/eiou/backup-locks— backup lifecycle and recovery coordination state (critical){NODE_NAME}-ssl-cert→/var/lib/eiou/ssl— persisted TLS identity
Backups also run automatically every night, encrypted with AES-256-GCM — see the Backup section in System & Advanced for the live commands. To wipe these volumes (e.g. starting fresh) see Manage the container.
Restore from a seed phrase
You don't need this to start a new node — first boot generates a fresh wallet on its own. Reach for it when you're recovering: rebuilding on a new host after losing the volumes, migrating, or coming back from a docker compose down -v. Wallet keys, the Tor identity (so the same .onion), and the encryption master key are all deterministically derived from the 24-word BIP39 seed, which is why this works at all.
- Restoration only takes effect on a node with no existing wallet — if the
configvolume already holds keys,RESTORE_FILEis ignored. To force restore on a host that has run before, wipe the volumes first and bring the seed - The seed is mounted as a read-only file at
RESTORE_FILE— never appears indocker inspect, process listings, or shell history - Restoration replays Tor identity and wallet keys; transaction history is not in the seed — that's what backups are for. Combine the two: seed for identity, encrypted backup for ledger
As soon as the node has restored and you can sign in, unmount the seed volume from docker-compose.yml, restart the container, and shred the host file (shred -u seed.txt on Linux). Leaving a 24-word seed on persistent storage defeats the entire encryption-at-rest model — anyone with read access to that file owns the wallet.
docker-compose.yml service block: tells startup.sh to read the seed from /restore/seed inside the container, mounted from the host file you'll create next.environment: - RESTORE_FILE=/restore/seed volumes: - /secure/path/seed.txt:/restore/seed:rochmod 600 keeps other user accounts on the host from reading it.echo "word1 word2 ... word24" > seed.txt && chmod 600 seed.txtManage the container
Day-to-day operations — check status, tail logs, restart cleanly, stop without losing data, and (deliberately) wipe the wallet. Replace eiou-node with whatever you set in NODE_NAME. The CLI lives inside the container; prepend docker exec <container> from the host.
docker compose psdocker compose logs -fdocker-compose.ymldocker compose restartcompose up brings the same wallet backdocker compose downdocker exec eiou-node eiou infoAdding -v deletes every named volume along with the container — wallet keys, transaction history, and the on-disk encrypted backups all go with it. Recovery requires both your 24-word BIP39 seed phrase and any database backups you've copied off this host — both must already be safe somewhere else before you run this. With the seed alone you get the same identity back (Tor address, public key) but a blank ledger; with backups alone you get the ledger but no identity. You need both.
docker compose down -vRun multiple nodes
To run several independent nodes on one host, duplicate the service block in docker-compose.yml with a unique NODE_NAME, distinct published ports, and a separate set of volume names. Archived multi-node compose files (4-line, 10-line, 13-node cluster) live under tests/old/compose-files/ and serve as working starting points.
- Each node should publish its own host ports — pick a convention like
808X/844X - Tor-only nodes don't need any published ports at all
- See Port Mappings for recommended layouts
Troubleshooting
The most common first-boot snags. For the long form — including TDE recovery and per-issue log paths — see Troubleshooting in Docker Configuration.
Stuck at "Waiting for MariaDB"
Normal on first boot — MariaDB initialization can take up to two minutes. Make sure at least 512 MB of memory is available to the container.
Browser shows a certificate warning
Expected with the default self-signed certificate. Either accept the warning for testing, switch to Let's Encrypt with LETSENCRYPT_EMAIL, or generate a local CA with ./scripts/create-ssl-ca.sh ./ssl-ca and trust it on your machine.
P2P HTTPS fails between nodes
Self-signed certs are rejected on outbound P2P by default. For dev clusters set P2P_SSL_VERIFY=false; for production share a CA via P2P_CA_CERT or put the nodes behind a reverse proxy with valid certificates.
Slow startup on WSL2
Bump the Tor timeouts so Tor has time to bootstrap on a slower I/O path:
environment: - EIOU_HS_TIMEOUT=120 - EIOU_TOR_TIMEOUT=240Reading logs inside the container
nginx errors live at /var/log/nginx/error.log, PHP errors at /var/log/php_errors.log, and Tor at /var/log/tor/log.
docker exec eiou-node tail -f /var/log/php_errors.log