Deployment
Deploy Hone with Docker.
Quick Start
1. Get the files
mkdir -p ~/hone && cd ~/hone
curl -O https://raw.githubusercontent.com/heskew/hone/main/deploy/docker-compose.yml
curl -O https://raw.githubusercontent.com/heskew/hone/main/deploy/.env.example
mv .env.example .env
2. Configure
Edit .env and set your database encryption key:
HONE_DB_KEY=your-secure-passphrase
3. Deploy
docker compose pull
docker compose up -d
Access Hone at http://localhost:3000
Configuration
Environment Variables
| Variable | Required | Description |
|---|---|---|
HONE_DB_KEY |
Yes | Database encryption passphrase |
OLLAMA_HOST |
No | Ollama server URL for AI features |
OLLAMA_MODEL |
No | Ollama model (default: gemma3) |
CF_TEAM_NAME |
Recommended | Cloudflare team name for JWT validation |
CF_AUD_TAG |
Recommended | Cloudflare Access application audience tag |
HONE_API_KEYS |
No | Comma-separated API keys for internal service auth |
HONE_TRUSTED_NETWORKS |
No | Comma-separated IPs/CIDRs that bypass auth |
HONE_TRUSTED_PROXIES |
No | Comma-separated proxy IPs/CIDRs to trust X-Forwarded-For from |
Authentication
Hone supports four authentication methods:
- Cloudflare Access JWT (recommended) - Cryptographically validates
Cf-Access-Jwt-Assertionheader - Cloudflare Access header (fallback) - Trusts
CF-Access-Authenticated-User-Emailheader - API Keys - For internal services, use
Authorization: Bearer <key>header - Trusted Networks - Requests from configured IP addresses/subnets bypass auth
For local/development use, add --no-auth to the command:
command: ["serve", "--host", "0.0.0.0", "--db", "/data/hone.db", "--no-auth"]
Security Requirements
Important: Enable JWT validation for production deployments behind Cloudflare Access.
JWT Validation (Recommended)
When CF_TEAM_NAME and CF_AUD_TAG are configured, Hone validates the JWT in the
Cf-Access-Jwt-Assertion header against Cloudflare's public keys. This provides
cryptographic proof that requests came through Cloudflare Access.
-
Get your team name from the Cloudflare Zero Trust dashboard URL:
https://one.dash.cloudflare.com/<account-id>/<team-name>/... -
Get your audience tag from Access > Applications > your app > Overview: Look for "Application Audience (AUD) Tag"
-
Add to
.env:CF_TEAM_NAME=your-team-name CF_AUD_TAG=your-aud-tag-here
Header-Only Authentication (Fallback)
Without JWT config, Hone trusts the CF-Access-Authenticated-User-Email header.
This is safe only when behind Cloudflare Tunnel, which strips and rewrites
CF headers. If you bypass Cloudflare, anyone can spoof these headers.
Safe configurations:
- Behind Cloudflare Tunnel + Access with JWT validation (production, recommended)
- Behind Cloudflare Tunnel + Access without JWT validation (production, less secure)
- Local network only + trusted networks (local browser access)
- Local network only + API keys (internal services)
--no-authon localhost only (development)
Unsafe configurations:
- Port forwarded to internet without Cloudflare
- Exposed to untrusted networks with auth enabled but no Cloudflare
API Key Setup
For machine-to-machine auth (e.g., Mac training script accessing Pi server):
-
Generate an API key (64 hex chars = 256 bits):
openssl rand -hex 32 -
Add to
.envon the Pi:HONE_API_KEYS=your-generated-key -
Use the key in requests:
curl -H "Authorization: Bearer your-generated-key" http://pi:3000/api/training/tasks
API key security notes:
- Keys are compared using constant-time comparison (timing attack resistant)
- Store keys securely; treat them like passwords
- Rotate keys if compromised
- Multiple keys supported (comma-separated) for key rotation
Trusted Networks Setup
For local network access without authentication (e.g., accessing Hone from your home network):
-
Identify your local network CIDR (e.g.,
192.168.1.0/24for most home networks) -
Add to
.env:HONE_TRUSTED_NETWORKS=192.168.1.0/24 -
Multiple networks/IPs (comma-separated):
HONE_TRUSTED_NETWORKS=192.168.1.0/24,10.0.0.0/8,172.16.0.5
Trusted networks security notes:
- Only use for networks you fully trust (e.g., home LAN behind firewall)
- Individual IPs are automatically treated as /32 (IPv4) or /128 (IPv6)
- Client IP is determined from the TCP connection by default (X-Forwarded-For is NOT trusted unless from a trusted proxy)
- Combine with Cloudflare Access for remote access while allowing local network bypass
Trusted Proxies Setup
When Hone runs behind a reverse proxy (e.g., Traefik in k3s, nginx), it sees the proxy's IP instead of the real client IP. To get the real client IP from X-Forwarded-For headers, configure the proxy as trusted:
-
Identify your proxy's IP/CIDR (e.g.,
10.42.0.0/16for k3s pod network) -
Add to
.env:HONE_TRUSTED_PROXIES=10.42.0.0/16 -
For k3s/Kubernetes with Traefik:
env: - name: HONE_TRUSTED_PROXIES value: "10.42.0.0/16"
Trusted proxies security notes:
- Only trust proxies you control (never trust arbitrary IPs)
- X-Forwarded-For is only parsed when the TCP connection comes from a trusted proxy
- This is required for trusted networks to work behind reverse proxies
- Common proxy CIDRs:
10.42.0.0/16(k3s),10.244.0.0/16(standard k8s),172.17.0.0/16(Docker)
Network Isolation
Hone only makes outbound connections to configured AI hosts (Ollama). There's no telemetry, no cloud APIs. For defense-in-depth, you can restrict egress at the network level.
Kubernetes - NetworkPolicy
Use a NetworkPolicy to restrict Hone's egress to only:
- DNS (for hostname resolution)
- Your Ollama host on port 11434
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: hone-egress
namespace: default # Change to your namespace
spec:
podSelector:
matchLabels:
app: hone
policyTypes:
- Egress
egress:
# Allow DNS resolution
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow Ollama host only (adjust IP/subnet to match your setup)
- to:
- ipBlock:
cidr: 192.168.1.100/32 # Your Ollama host IP
ports:
- protocol: TCP
port: 11434
Adjust the namespace, pod labels, and cidr to match your deployment. Use /32 for a single IP or a broader subnet like 192.168.1.0/24 if needed.
Docker Compose - Firewall Rules
For Docker deployments where Ollama runs on a separate machine, use host firewall rules:
# Get the hone container's IP
docker inspect hone | grep IPAddress
# Block all outbound except Ollama (replace IPs as needed)
iptables -I DOCKER-USER -s 172.17.0.2 -j DROP
iptables -I DOCKER-USER -s 172.17.0.2 -d 192.168.1.100 -p tcp --dport 11434 -j ACCEPT
For setups where Ollama is a sidecar container on the same host:
services:
hone:
image: ghcr.io/heskew/hone-money:latest
networks:
- hone-internal
ollama:
image: ollama/ollama
networks:
- hone-internal
networks:
hone-internal:
internal: true # No internet access - containers can only reach each other
Operations
Update
docker compose pull
docker compose up -d
View Logs
docker compose logs -f hone
Debug Logging
Enable debug logging with the RUST_LOG environment variable:
# In docker-compose.yml
environment:
- RUST_LOG=hone_server=debug
# Or for k3s deployment
env:
- name: RUST_LOG
value: "hone_server=debug"
This shows detailed logs for authentication, trusted network checks, and API requests.
Backup
docker compose exec hone /app/hone backup create
Stop
docker compose down
Mac Training Setup
Run model fine-tuning on a Mac while Hone runs on another machine (e.g., Raspberry Pi).
Architecture
┌─────────────────┐ ┌─────────────────┐
│ Mac Studio │ │ Pi (Docker) │
│ │ HTTP │ │
│ train.sh ─────┼────────►│ hone-server │
│ mlx-lm │ API │ SQLite DB │
│ ollama │ │ │
└─────────────────┘ └─────────────────┘
Prerequisites
On the Mac:
# MLX for Apple Silicon fine-tuning
pip install mlx-lm
# Ollama for model serving
brew install ollama
Setup
-
Generate an API key on the Pi:
openssl rand -hex 32 # Output: e.g., a1b2c3d4e5f6... -
Add to Pi's
.env:HONE_API_KEYS=a1b2c3d4e5f6... -
Restart Hone on the Pi:
docker compose up -d -
Configure the Mac:
# Add to ~/.zshrc or ~/.bashrc export HONE_API_URL="http://pi-hostname:3000" export HONE_API_KEY="a1b2c3d4e5f6..."
Usage
# List available training tasks
./scripts/train.sh --list
# Train a model
./scripts/train.sh --task classify_merchant --branch main
# Train with specific base model
./scripts/train.sh --task normalize_merchant --base-model gemma3:27b
The script:
- Fetches training data from the Pi via API
- Runs MLX LoRA fine-tuning locally
- Creates an Ollama model from the adapter
Network Options
The Mac needs to reach the Pi's port 3000. Options:
- Same LAN - Use Pi's local IP (e.g.,
http://192.168.1.x:3000) - Tailscale - Use Pi's Tailscale IP (e.g.,
http://100.x.x.x:3000) - SSH Tunnel -
ssh -L 3000:localhost:3000 pithen usehttp://localhost:3000