5.8 KiB
btcprice
Bitcoin price tracker UI. Serves an HTML page with live price updates streamed from btcdata, which this server proxies over WebSocket and renders as HTML fragments for HTMX.
Quick Start
# Start btcdata first (provides price data)
cd ../btcdata && make run
# Local dev — creates .venv and installs requirements on first run
make run # starts on port 4000
make tailwind # watch Tailwind CSS (separate terminal)
Optionally cp .env.example .env to override BTCDATA_URL; without it the code default
(http://localhost:4100) applies. make run-local requires a .env defining
BTCDATA_LOCAL_URL.
Run the test suite with make test. Test-only packages are kept in requirements-dev.txt and are
not installed in the production image.
Docker
docker compose up -d starts btcprice only — it does not bring up btcdata. Both compose
files attach to the external btc-network, so create it once and start btcdata separately:
docker network create btc-network # once
cd ../btcdata && docker compose up -d
cd ../btcprice && docker compose up -d
| Service | Local dev | Docker |
|---|---|---|
| btcprice | localhost:4000 | localhost:4041 (container port 4040) |
| btcdata | localhost:4100 | localhost:4102 |
Inside btc-network, btcprice reaches btcdata as http://btcdata:4102.
CI images
Pushes to the main branch and tags matching v* run
.gitea/workflows/build.yml. The workflow tests the application and publishes linux/amd64 images
to harbor.pmagnus.cloud/btcprice/btcprice. Branch builds receive an immutable
main-<UTC timestamp> tag (for Flux selection) and a SHA-qualified companion tag; release tags
receive the version without v and a SHA-qualified companion tag. The image embeds the short Git
SHA, available from /api/version.
The workflow requires the repository secrets HARBOR_ROBOT_USER and HARBOR_ROBOT_TOKEN. It only
pushes images and intentionally has no Kubernetes credentials.
Production deployment
Production is GitOps-managed at https://btcprice.k8s.pmagnus.cloud:
git push → Gitea Actions on anvil → Harbor → Flux → k3s → ingress-nginx → Traefik TLS
Flux image automation commits the newest approved main-<UTC timestamp> image to the separate
magnus/deploy repository. The manifests are in ../deploy/apps/btcprice, and two replicas run with
one pod on each k3s node. CI never receives cluster credentials. Do not deploy production with
Docker Compose or manual kubectl apply; rollback by reverting the immutable image change in the
GitOps repository.
btcdata remains an external dependency running on citadel. Kubernetes represents it with a
selectorless Service and EndpointSlice, so pods use http://btcdata:4102 without embedding its IP in
the Deployment.
Legacy deployment (citadel)
./deploy-citadel.sh
Same method as ../elprice/deploy-citadel.sh: builds the image locally for the target's
architecture (citadel is x86_64 → linux/amd64), copies docker-compose.citadel.yml and
.env.citadel to /home/pmp/apps/btcprice on the host as docker-compose.yml / .env,
pipes the image over SSH with docker save | docker load, and runs
docker compose up -d --no-build. No registry involved.
The temporary migration fallback listens on http://192.168.50.195:4041 and proxies to the
btcdata running on citadel as well. It is retained only for the validation/rollback window; the
Kubernetes hostname is the production endpoint. Both attach to the external btc-network there, so BTCDATA_URL is
http://btcdata:4102 (set in .env.citadel) — the service name inside the network, not a LAN
address. The deploy script creates btc-network on the host if it does not exist yet.
Overridable: BTCPRICE_DEPLOY_HOST, BTCPRICE_DEPLOY_DIR, BTCPRICE_IMAGE_NAME,
BTCPRICE_PLATFORM, BTCPRICE_NETWORK.
Environment Variables
| Variable | Default | Description |
|---|---|---|
BTCDATA_URL |
http://localhost:4100 |
btcdata service URL the server proxies to |
APP_VERSION |
development |
Build identifier returned by /api/version |
BTCDATA_LOCAL_URL |
— | Used by make run-local instead of BTCDATA_URL |
DOCKER_BTCDATA_PORT |
4102 |
btcdata's port inside btc-network (local compose only) |
BTCPRICE_PORT |
4041 |
Host port in the citadel deployment (.env.citadel) |
The HTTP port is not configurable via environment: make run hardcodes 4000 and the Docker
image's CMD hardcodes 4040.
Health endpoints
/api/healthis a process-only liveness check and remains healthy during a btcdata outage./api/readychecks${BTCDATA_URL}/api/healthwith a three-second timeout and returns 503 when btcdata is unavailable./api/versionreturns the build SHA embedded by CI.
How It Works
The browser loads a page with HTMX and the WebSocket extension, which connects back to
btcprice at /ws/price and /ws/strike — the browser never contacts btcdata directly.
Each of those endpoints opens an upstream WebSocket to btcdata (/api/price/ws and
/api/strike/ws), converts each JSON message to an HTML fragment (price_json_to_html,
strike_json_to_html), and forwards it to the browser. HTMX swaps the received
<div id="price-display"> / <div id="strike-display"> into the DOM. Price changes are
color-coded (green up, red down). Reconnection is handled by HTMX.
Tech Stack
Python · FastAPI · Uvicorn · websockets · HTMX · Tailwind CSS v4
The Clojure implementation this replaced is on the clojure branch.
The 24-hour chart uses 48 half-hour candles from rustbtcdata’s
/api/kraken-half-hour endpoint, aggregated from minute OHLC data. It refreshes
every five minutes and includes the current partial half-hour candle.