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

ContainerRole
wtracker-apiFastAPI — HTML pages + REST API (port 8000)
wtracker-workerCelery worker — runs Scrapy spiders and maintenance
wtracker-beatCelery beat — cron-style scrape schedules
wtracker-dbPostgreSQL 16 — bottles, prices, users, collections
wtracker-redisRedis 7 — Celery broker + cache
wtracker-torSOCKS5 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

RoutePurpose
/Home — search, stats, trending preview
/bottlesPaginated catalog
/bottles/{id}Detail + Chart.js price history
/marketAuction volume charts from market_stats
/brandsBrand directory with filters
/searchFull-text bottle search
/trendingRecently 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
   └────────────────┘