Production AI Wallet Deployment: GHCR Image with Auto-Provision and Secrets
Production AI Wallet Deployment: Self-Hosting WAIaaS with Docker, Auto-Provision, and Secrets
Deploying a self-hosted AI wallet means your agent's private keys never leave your server — and with WAIaaS's Docker image on GHCR, you can have the whole stack running in a single command. If you've ever asked yourself whether you'd hand over signing authority for an autonomous agent to a third-party service you don't control, this post is for you. We'll walk through exactly how to get WAIaaS running in production on your own hardware, with proper secret management and automatic provisioning from the start.
Why Self-Hosting an AI Wallet Actually Matters
There's a philosophical argument and a practical one. The philosophical argument is simple: private keys are the root of custody. If someone else holds them — even behind a well-intentioned API — they hold the assets. For autonomous AI agents that sign transactions without human approval for every action, that's a significant trust delegation.
The practical argument is about control over your own infrastructure. Self-hosting means you define the network exposure, the security policies, the update schedule, and the backup strategy. There are no rate limits imposed by a third party on your wallet operations. You're not subject to a remote service going down during a critical trading window. Your agent's transaction history stays on your server. For developers building serious agent infrastructure, or privacy-conscious enthusiasts running homelab setups, that level of ownership matters a lot.
WAIaaS is an open-source, self-hosted Wallet-as-a-Service designed specifically for AI agents. It runs as a Docker container, exposes a REST API your agents call, and never requires your keys to leave the machine. Let's get it running properly.
The WAIaaS Docker Image
WAIaaS publishes a Docker image at ghcr.io/waiaas/waiaas:latest. There are actually two Docker images in the project — the main WAIaaS daemon and a push-relay service for notifications — but for the core wallet infrastructure, you're working with the daemon image.
The daemon listens on port 3100 by default, bound to 127.0.0.1 so it's not exposed to the network unless you explicitly configure that. The data directory at /data is where everything persistent lives — wallets, sessions, policy configuration, and the SQLite database.
Here's the simplest possible production-ready deployment:
docker run -d \
--name waiaas \
-p 127.0.0.1:3100:3100 \
-v waiaas-data:/data \
-e WAIAAS_AUTO_PROVISION=true \
ghcr.io/waiaas/waiaas:latest
# Retrieve auto-generated master password
docker exec waiaas cat /data/recovery.key
The WAIAAS_AUTO_PROVISION=true flag is important. On first start, instead of blocking and waiting for you to set a master password interactively, the container generates a cryptographically random password and writes it to /data/recovery.key. You retrieve it once, store it somewhere safe, and the daemon is running without any manual intervention. This is the pattern that makes WAIaaS deployable in automated infrastructure — no interactive prompts blocking your container startup.
A Proper Docker Compose Setup
For anything beyond a quick test, you want a Compose file. Here's the production-ready version:
services:
daemon:
image: ghcr.io/waiaas/waiaas:latest
container_name: waiaas-daemon
ports:
- "127.0.0.1:3100:3100"
volumes:
- waiaas-data:/data
environment:
- WAIAAS_DATA_DIR=/data
- WAIAAS_DAEMON_HOSTNAME=0.0.0.0
env_file:
- path: .env
required: false
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3100/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3
volumes:
waiaas-data:
driver: local
A few things worth noting in this configuration:
Port binding: 127.0.0.1:3100:3100 means the API is only reachable from localhost. If your AI agent runs on the same machine, this is correct. If you're putting a reverse proxy in front (nginx, Caddy, Traefik), the proxy connects to this port locally and you expose TLS externally. Never bind to 0.0.0.0:3100 in production without a firewall rule or auth layer in front.
Named volume: Using waiaas-data as a named volume rather than a bind mount gives Docker responsibility for managing the data path. docker compose down preserves the volume. docker compose down -v removes it. Be aware of that difference when running maintenance commands.
Healthcheck: The built-in healthcheck polls /health every 30 seconds. This integrates with Docker's health status, which means orchestrators and monitoring tools can detect when the daemon is actually ready rather than just when the container process has started.
restart: unless-stopped: The daemon comes back automatically after a server reboot, which is what you want for anything running agents that need to be continuously available.
Managing Secrets in Production
The auto-provision flow is great for getting started, but in production you want secrets managed properly — not sitting in environment variables that show up in docker inspect output or in your shell history.
WAIaaS supports Docker Secrets through a docker-compose.secrets.yml overlay file. The pattern is to create secret files with restricted permissions, then deploy with both compose files:
# Create secret files
mkdir -p secrets
echo "your-secure-password" > secrets/master_password.txt
chmod 600 secrets/master_password.txt
# Deploy with secrets overlay
docker compose -f docker-compose.yml -f docker-compose.secrets.yml up -d
The overlay approach is deliberate — your base docker-compose.yml can be committed to version control without secrets, and docker-compose.secrets.yml stays out of git (add it to .gitignore). This is a cleaner separation than environment variable files and fits well with secret management tools like Vault or cloud-provider secret stores that can write files to the secrets directory.
Key Environment Variables
Once you're past the initial setup, you'll want to configure RPC endpoints and other runtime parameters. The most important ones:
WAIAAS_AUTO_PROVISION=true # Auto-generate master password on first start
WAIAAS_DAEMON_PORT=3100 # Listening port
WAIAAS_DAEMON_HOSTNAME=0.0.0.0 # Bind address
WAIAAS_DAEMON_LOG_LEVEL=info # Log level (trace/debug/info/warn/error)
WAIAAS_DATA_DIR=/data # Data directory
WAIAAS_RPC_SOLANA_MAINNET=<url> # Solana mainnet RPC endpoint
WAIAAS_RPC_EVM_ETHEREUM_MAINNET=<url> # Ethereum mainnet RPC endpoint
The RPC endpoint variables are important for self-hosters. WAIaaS supports 18 networks across EVM and Solana chain types, and you'll want to point to your own RPC nodes or a service where you control the API key. Running your own agents against a shared public RPC endpoint under load is a recipe for rate limiting at the worst possible moment.
From Zero to Running Agent: The Full Sequence
If you're starting fresh on a machine with Docker installed, here's the complete path from nothing to a wallet your agent can use:
Step 1: Clone and start
git clone https://github.com/waiaas/WAIaaS.git
cd WAIaaS
docker compose up -d
Step 2: Retrieve your master password
docker exec waiaas cat /data/recovery.key
Store this somewhere safe. This is your masterAuth credential — it controls wallet creation, session management, and policy configuration.
Step 3: Create a wallet
curl -X POST http://127.0.0.1:3100/v1/wallets \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{"name": "trading-wallet", "chain": "solana", "environment": "mainnet"}'
Step 4: Create a session for your agent
curl -X POST http://127.0.0.1:3100/v1/sessions \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{"walletId": "<wallet-uuid>"}'
The session token you get back is what your agent uses. It authenticates as sessionAuth — a JWT the agent includes as a Bearer token. The master password never goes anywhere near your agent code.
Step 5: Set a policy before funding
Before you put real funds in, set a spending limit:
curl -X POST http://127.0.0.1:3100/v1/policies \
-H "Content-Type: application/json" \
-H "X-Master-Password: my-secret-password" \
-d '{
"walletId": "<wallet-uuid>",
"type": "SPENDING_LIMIT",
"rules": {
"instant_max_usd": 100,
"notify_max_usd": 500,
"delay_max_usd": 2000,
"delay_seconds": 900,
"daily_limit_usd": 5000
}
}'
WAIaaS has a default-deny policy model: transactions are blocked unless you've explicitly configured what's allowed. The policy engine has 21 policy types covering everything from spending limits to DeFi-specific guardrails like maximum perpetual futures leverage and loan-to-value limits for lending positions. Getting at least a SPENDING_LIMIT and ALLOWED_TOKENS policy in place before funding is the self-hoster equivalent of configuring your firewall before opening ports.
The Three-Layer Authentication Model
One thing that surprises people coming from simpler wallet setups is that WAIaaS has three distinct authentication layers, each with a different principal:
- masterAuth — The system administrator layer. Uses Argon2id password hashing. Controls wallet creation, session management, and policy configuration. Your agent code should never have this credential.
- sessionAuth — The agent layer. JWT tokens (HS256) scoped to a specific wallet. This is what goes in your agent's environment variables. Tokens have configurable TTL, max renewals, and absolute lifetime.
- ownerAuth — The fund owner layer. Signs with ed25519 or secp256k1 (SIWS/SIWE). Used for approving high-value transactions that the policy engine has flagged for human review, and for kill-switch recovery.
This separation means that even if an agent's session token is compromised, an attacker can't create new wallets, modify policies, or bypass the approval flow for large transactions. The policy engine sits in the middle — a 7-stage transaction pipeline that every outgoing transaction passes through regardless of which API path initiated it.
Checking Everything Is Running
The API has 39 REST route modules and a full OpenAPI 3.0 spec auto-generated at /doc, with an interactive Scalar reference UI at /reference:
# Download OpenAPI 3.0 spec
curl http://127.0.0.1:3100/doc -o openapi.json
# View interactive API reference
open http://127.0.0.1:3100/reference
For day-to-day operations, the useful Docker commands are:
docker compose up -d # Start daemon
docker compose logs -f # Follow logs
docker compose down # Stop (data preserved in named volume)
docker compose down -v # Stop + delete data volume
The -v flag on down is the one to be careful about. It's there when you need it, but you don't want to run it reflexively during a restart.
The Philosophy Behind Self-Hosting This Stack
Running your own email server used to be the canonical example of self-hosting done right — full control, but genuinely complicated to operate. The knock against it was always the operational burden.
WAIaaS is built to avoid that tradeoff. The Docker image handles auto-provision, Docker Secrets, healthchecks, and runs as a non-root user (UID 1001). The CLI has 20 commands covering everything from initial setup (waiaas init, waiaas start) to backup management (waiaas backup create, waiaas backup list, waiaas backup inspect) to owner key management (waiaas owner connect, waiaas owner disconnect). The waiaas quickset command creates wallets and MCP sessions in one step.
For a homelab running AI agents with real assets, self-hosting this way is meaningfully different from using a hosted wallet service. Your transaction data doesn't leave your server. Your private keys are generated and stored on hardware you control. The policy engine runs locally. If the WAIaaS project went offline tomorrow, your self-hosted instance would keep running indefinitely — there's no cloud dependency in the critical path.
Your keys, your server, your rules. That's not just a slogan when the alternative is autonomous agents signing transactions against keys held by a third party.
What's Next
Once your daemon is running, the next natural step is connecting your AI agents — either through the MCP integration for Claude and other MCP-compatible frameworks, or directly via the TypeScript or Python SDK. The policy engine deserves dedicated time: with 21 policy types covering spending, token whitelists, DeFi position limits, and network restrictions, configuring it properly is what separates a wallet that's safe to leave running overnight from one that isn't.
Explore the full project at https://github.com/waiaas/WAIaaS and the official site at https://waiaas.ai. The codebase is open source, the Docker image is on GHCR, and the 684+ test files give you a reasonable level of confidence in what you're running on your own hardware.