6.2 KiB
🐳 Docker Deployment — TRON Parity Ladder
Production-grade containerization for the TRON Parity Ladder dashboard. Multi-stage build produces a minimal (~25 MB) Alpine Nginx image that serves the static bundle with gzip, caching headers, and SPA routing.
📁 What's included
| File | Purpose |
|---|---|
Dockerfile |
Multi-stage build: Node 20 → Nginx 1.27 Alpine |
docker-compose.yml |
Service definition, healthcheck, resource limits |
docker/nginx.conf |
Production Nginx config (gzip, CSP, SPA routing, caching) |
.dockerignore |
Keeps build context lean (~500 KB) |
.env.example |
Environment variable reference |
Makefile |
Convenience shortcuts |
🚀 Quick start
# 1. Build & start in background
docker compose up --build -d
# 2. Visit the dashboard
open http://localhost:8080
# 3. Check health
curl http://localhost:8080/health
Or use the Makefile shortcuts:
make up # build & start
make logs # tail logs
make restart # restart container
make down # stop container
make clean # stop + remove image
make health # query /health
make status # resource usage
⚙️ Configuration
Copy the example environment file and customize:
cp .env.example .env
Available variables:
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Host port to expose the dashboard on |
TZ |
UTC |
Container timezone |
Example — run on port 3000:
PORT=3000 docker compose up --build -d
🏗️ Architecture
┌──────────────────────────────────────┐
│ Docker Build (multi-stage) │
│ │
│ Stage 1: node:20-alpine │
│ └── npm ci → npm run build │
│ └── outputs /app/dist │
│ │
│ Stage 2: nginx:1.27-alpine │
│ └── COPY dist → /usr/share/... │
│ └── custom nginx.conf │
│ └── runs as nginx (non-root) │
│ └── serves on :80 │
└──────────────────────────────────────┘
🔒 Security features
- Non-root execution — container runs as
nginxuser - Security headers — CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- CSP whitelists — only allows CoinGecko + Frankfurter APIs for outbound fetch
- Hidden dot-files —
.env,.git, etc. blocked from HTTP access - Resource limits — max 1 CPU / 256 MB memory
- Log rotation — 3 × 10 MB files per container
🏭 Production deployment
Behind a reverse proxy (Caddy / Traefik / Nginx)
# docker-compose.prod.yml
services:
web:
labels:
- "traefik.http.routers.parity.rule=Host(`parity.example.com`)"
- "traefik.http.routers.parity.tls=true"
Kubernetes
Generate a deployment spec:
docker compose convert --format kubernetes
# or use kompose:
kompose convert -f docker-compose.yml
CI/CD
# Build & tag for registry
docker build -t registry.example.com/tron-parity-ladder:${GIT_SHA} .
docker push registry.example.com/tron-parity-ladder:${GIT_SHA}
🩺 Health & monitoring
# Health endpoint
curl http://localhost:8080/health # → "OK"
# Container health status
docker inspect --format='{{.State.Health.Status}}' tron-parity-ladder
# Live resource stats
docker stats tron-parity-ladder
🧹 Troubleshooting
| Symptom | Fix |
|---|---|
| Port already in use | Change PORT in .env |
| SPA routes 404 | Verify try_files $uri /index.html in nginx.conf |
| API calls blocked | Check CSP header in docker/nginx.conf — add origins to connect-src |
| Build slow | Delete stale layers: docker system prune -a |
| Container unhealthy | docker compose logs web — check nginx error log |
📡 Live Data Architecture
The SPA pulls from three public APIs with a cascading fallback:
| Priority | Source | Endpoint | Used for |
|---|---|---|---|
| 1 (primary) | TronScan | apilist.tronscan.org/api/token_trc20 |
On-chain TRON token prices, supplies, market caps |
| 2 (fallback) | CoinGecko | api.coingecko.com/v3/coins/markets |
Cross-exchange aggregated prices when TronScan is rate-limited |
| 3 (forex) | Frankfurter | api.frankfurter.dev/v1/latest |
ECB-sourced fiat exchange rates (USD base) |
Every successful fetch is recorded to the browser's IndexedDB (tron-parity-history database), enabling time-series and synthetic-pair charts (e.g. BTT/JST, APENFT/WIN) locally on each user's device.
The header badge reports which source actually supplied the data:
- 🟢 TRONSCAN — full on-chain data
- 🔵 COINGECKO — TronScan offline, fallback active
- 🟡 MIXED — partial TronScan, partial CoinGecko fill
- ⚫ BASELINE — both APIs unreachable, using bundled constants
Path to a persistent backend
IndexedDB is per-browser — data doesn't sync across users or survive a cache clear. For a shared, server-side history that can power real charts, add a companion service:
┌─────────────┐ ┌──────────────────┐ ┌───────────┐
│ Node Worker │────▶│ Postgres + REST │◀────│ SPA │
│ (cron 60s) │ │ /api/history │ │ (browser)│
│ → TronScan │ │ /api/synthetic │ └───────────┘
└─────────────┘ └──────────────────┘
This worker lives in the same docker-compose.yml — swap the SPA's dataService.ts to point at /api/* instead of the public endpoints, and IndexedDB becomes a client-side cache rather than the source of truth.
📊 Image size
| Stage | Size |
|---|---|
node:20-alpine (builder) |
~200 MB (discarded) |
nginx:1.27-alpine (final) |
~25 MB ✅ |