tron/DOCKER.md

6.2 KiB
Raw Permalink Blame History

🐳 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 nginx user
  • 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