Install and operations
This guide covers installing Coldtill on a Linux server, the first-run setup, and day-to-day operations: backups, upgrades, monitoring and key rotation.
Requirements
- A Linux server with 2 GB of RAM or more. Any distribution with Docker works; these commands are for Ubuntu, Debian and Linux Mint.
- A DNS name pointing at the server (for example
pay.example.com), with ports 80 and 443 open. Caddy obtains the TLS certificate automatically. - For mainnet: an SMTP account for payout approval emails, and RPC endpoints you trust (see Nodes).
Install Docker and the Compose plugin:
sudo apt update
sudo apt install docker.io docker-compose-v2 git
sudo usermod -aG docker $USER # log out and back in afterwards
Install
git clone git@github.com:rollounden/coldtill.git
cd coldtill/deploy
./install.sh --mode testnet --site-url https://pay.example.com
install.sh accepts --mode testnet|mainnet, --site-url URL and --no-build (use a prebuilt image set in
COLDTILL_IMAGE). It:
- builds the
coldtill:localimage; - runs
coldtill init, which asks for a hot-wallet passphrase (at least 12 characters) and writes:deploy/.env: mode, site URL, the server secret, the database password and the age recipient;deploy/secrets/coldtill-identity.age: the hot-wallet identity, encrypted under your passphrase;deploy/secrets/hot_passphrase: the passphrase, so the sweeper and payouts can restart unattended;
- starts PostgreSQL, the four Coldtill processes and Caddy with Docker Compose. Database migrations run automatically when the processes start.
For a local trial without DNS, use --site-url http://localhost:8080.
Running without a stored passphrase
If you'd rather not keep the passphrase on disk, initialise with coldtill init --store-passphrase=false (or
delete deploy/secrets/hot_passphrase) and supply it each time you start the stack:
cd deploy
read -rs COLDTILL_HOT_PASSPHRASE && export COLDTILL_HOT_PASSPHRASE
docker compose up -d
unset COLDTILL_HOT_PASSPHRASE
Only the sweeper and payouts containers receive it. If either container restarts, it can't unlock the
hot wallets until you run this again.
First-run setup
Open the site URL and create the root account (the first account becomes root). Then, in order:
- Account: enable two-factor authentication. Anything that moves or redirects money needs a fresh two-factor code, so most of the steps below require it.
- Nodes & networks: enable the networks you want, set confirmations, and add RPC nodes. Public defaults are rate limited and fine only for testing.
- Projects: rename the default project or add more. Each project is a shop with its own settings.
- Deposit wallets (per project):
- Bitcoin: paste a BIP84 account xpub (
zpubon mainnet,vpubon signet). The browser derives the first address; confirm it matches your wallet's first receive address. - EVM: connect a browser wallet (your "master" account) and enter your cold wallet. Deploy the deposit factory on each EVM network from the same master account; the factory address is the same on every chain.
- Solana: connect a wallet, enter your treasury (a hardware wallet or a Squads vault address), save, then create the config by signing in your wallet. The browser checks the transaction before signing.
- Bitcoin: paste a BIP84 account xpub (
- Hot wallets (global admin): generate or import one EVM and one Solana key. Fund each with a little gas, then assign them to the project.
- Project settings: branding, sweep thresholds, payout limits and (optional) auto-convert. See Branding, Payouts and Auto-convert.
- API keys and webhooks: create an API key (project admin, with two-factor) and a webhook endpoint for your shop. See Merchant API and Webhooks.
Users and roles
- Root: the first account. It can do everything, and is the only account that can disable admins.
- Admin: global administrator. Manages nodes, networks, hot wallets and users.
- Member: access only to the projects they are added to, with a project role:
project_admin: settings, deposit wallets, creating API keys, the address book, payouts and approvals;developer: payments, webhooks, and revoking API keys, but not settings, payouts or creating keys;read_only: view only.
Nodes and networks
Coldtill trusts RPC nodes to report deposits. A malicious node can't redirect funds, but it could report a deposit that never happened. For production:
- run your own node, or use a paid provider (Alchemy, Infura, QuickNode, Helius, Blockstream) with an API key;
- add more than one node per network, so failover works and decisions that ask every node (Solana payout re-signing, Bitcoin dropped deposits) have something to compare;
- keep the default confirmation counts or raise them. Lowering them needs a two-factor code.
Backups
What to back up, and what losing it means:
| Item | Where | If lost |
|---|---|---|
| Database | pgdata Docker volume |
Payment history, settings, encrypted hot keys. Customer funds are safe on-chain, but you lose the records |
deploy/.env |
Server | The server secret: sessions, two-factor secrets and webhook secrets become unreadable |
deploy/secrets/ |
Server | The hot-wallet identity. You'd need to re-import or replace hot wallets |
| Cold wallet seed | Offline, with you | Everything swept so far. Coldtill can't help with this |
Nightly database dump:
cd ~/coldtill/deploy
docker compose exec -T postgres pg_dump -U coldtill coldtill | gzip > ~/backups/coldtill-$(date +%F).sql.gz
Add it to cron with crontab -e:
15 3 * * * cd $HOME/coldtill/deploy && docker compose exec -T postgres pg_dump -U coldtill coldtill | gzip > $HOME/backups/coldtill-$(date +\%F).sql.gz
Copy deploy/.env and deploy/secrets/ to encrypted offline storage once, and again whenever they change.
Restore
cd ~/coldtill/deploy
docker compose up -d postgres
gunzip -c ~/backups/coldtill-2026-10-01.sql.gz | docker compose exec -T postgres psql -U coldtill coldtill
docker compose up -d
Restore .env and secrets/ from the same point in time as the dump.
Upgrades
cd ~/coldtill
git pull
cd deploy
docker compose build
docker compose up -d
Migrations run on start-up. Take a database dump first. Read the release notes before upgrading across major versions.
Never run make contracts on a live install: rebuilding the EVM contracts changes the factory bytecode, and
with it every deposit address.
Monitoring
Health:
https://pay.example.com/healthzreturnsok. Theapicontainer has a Docker health check.Logs are JSON, one line per event:
docker compose logs -f --since 1h api watcher sweeper payouts docker compose logs sweeper | grep '"level":"WARN"'Things to alert on:
"level":"ERROR"from any process;conversion delivery mismatch,filled a nonce gap,sweep not possible,hot wallet … needs at least;- payouts in
needs-review(they also send apayout.needs-reviewwebhook); - a network's "last block processed" in Nodes & networks falling behind.
Hot wallet balances: shown under Hot wallets. Top them up before they run dry, or sweeps stop.
Rotating secrets
- Hot wallet: add a new hot wallet, assign it to the project, move any remaining balance out of the old one with your own wallet, then delete the old one.
- API key: create a new key, deploy it, then revoke the old one.
- Webhook secret: create a new endpoint, switch your receiver, then delete the old endpoint.
- Hot-wallet passphrase: re-run
coldtill init --forcein a scratch directory to create a new identity, then re-import the hot keys under the new identity. Plan this as a maintenance window. COLDTILL_SECRET: changing it signs everyone out and makes stored two-factor and webhook secrets unreadable. Users must re-enrol two-factor and webhooks must be recreated. Only rotate it after a suspected compromise.
Moving to mainnet
Run a separate install with --mode mainnet. Mainnet requires an https:// site URL and SMTP settings
(COLDTILL_SMTP_HOST, COLDTILL_SMTP_PORT, COLDTILL_SMTP_USERNAME, COLDTILL_SMTP_PASSWORD,
COLDTILL_SMTP_FROM). Before taking real payments, work through the mainnet checklist in
Audit preparation and the advice in
How to get Coldtill audited.