Platform
Shipping a data product as a website.
DramValue ran as six Docker containers on a VPS — API, worker, beat, Postgres, Redis, and Tor — fronted by nginx with Let's Encrypt. This page covers the decisions that made it work in production.
Docker services
| Container | Role |
|---|---|
wtracker-api | FastAPI — HTML pages + REST API (port 8000) |
wtracker-worker | Celery worker — runs Scrapy spiders and maintenance |
wtracker-beat | Celery beat — cron-style scrape schedules |
wtracker-db | PostgreSQL 16 — bottles, prices, users, collections |
wtracker-redis | Redis 7 — Celery broker + cache |
wtracker-tor | SOCKS5 proxy for anonymous scraper egress |
API & service layer
Business logic lives in services (BottleService, PriceService), not route handlers.
Responses use a consistent envelope: success_response, paginated_response, error_response.
Server-side rendering
Jinja2 templates + Tailwind — no separate SPA build step. Fast first paint, simple deployment, SEO-friendly HTML.
Auth
JWT + bcrypt (passlib). Login/register forms rebuilt with vanilla JS timeouts after Alpine.js spinner bugs in production.
User features
Collections, price alerts, profile — all backed by async SQLAlchemy sessions.
Public pages
| Route | Purpose |
|---|---|
/ | Home — search, stats, trending preview |
/bottles | Paginated catalog |
/bottles/{id} | Detail + Chart.js price history |
/market | Auction volume charts from market_stats |
/brands | Brand directory with filters |
/search | Full-text bottle search |
/trending | Recently active bottles |
SEO & discoverability
Sitemaps
Dynamic XML sitemaps — static pages plus ~25K bottle URLs split across indexed files. Served at /sitemap.xml.
Meta tags
Shared Jinja macro for description, canonical, Open Graph, Twitter cards, and JSON-LD on bottle detail pages.
HEAD support
Middleware fix for crawler HEAD requests — nginx was returning 500s when Content-Length mismatched.
Production ops
One-command setup
scripts/setup-production.sh — Docker, .env, nginx, certbot, Kaggle imports, cron jobs.
Host crons
Weekly git backup, daily pg_dump, log rotation, API health check with auto-restart.
Pause / resume
pause-scheduling.sh and resume-scheduling.sh to stop beat/worker and crons while keeping the site live.
Sunset to Vercel
Production stack winds down; this /demo portfolio preserves the story of how it was built.
Architecture diagram
Internet
│
┌────▼────┐
│ nginx │ SSL (Let's Encrypt), static caching
└────┬────┘
│
┌────▼────────────┐
│ FastAPI (api) │ Jinja2 templates · REST /docs
└───┬────────┬────┘
│ │
┌────▼───┐ ┌──▼──────┐
│Postgres│ │ Redis │
└────▲───┘ └──┬──────┘
│ │
┌───┴────────▼───┐
│ Celery worker │ Scrapy crawlers
│ Celery beat │ Scheduled tasks
└────────────────┘