A self-hosted personal tier list aggregator. Replace scattered tools like Goodreads, IMDB, BoardGameGeek, and others with a single app you own and run yourself.
β οΈ π€ This project has been created relying heavily on GenAI, including Agentic workflows and even Vibe Coding. This has both upsides (the project coming to life as quickly as it did, for one), and many downsides (that come along with using GenAI). We place this disclaimer here so anyone can make their own judgement about whether they want to use this project or not.
Another warning: the project is still in early alpha phase. Consider it a preview version. Try it out through the repository or Docker Hub, at your own risk.
Tierdom App has two sides:
- Public site β A read-only view for anyone to browse your tier lists, organized by topic (Games, Books, Movies, Board Games, etc.). Each section shows items you've consumed or experienced, ranked in tiers. Also includes a home page and about page managed via a lightweight built-in CMS.
- Admin back-office β A private, authenticated interface for adding and editing entries, managing sections, and updating CMS content.
The entire app ships as a single Docker image. The database is a SQLite file mounted as a volume, so it lives outside the container and can be backed up independently.
- Topic sections (Games, Books, Movies, Board Games, and more β fully configurable)
- Tier list view per section with customizable tier labels
- Lightweight built-in CMS for static pages (Home, About)
- Admin interface protected behind single-user authentication
- Self-contained: no external services required
- Single Docker image β runs on any VPS
High level features:
- Core tier list sections (Games, Books, Movies, Board Games)
- Tier list display (public)
- Admin interface (add/edit entries, manage sections)
- Built-in CMS (Home, About pages)
- Authentication and authorization
- Item images with placeholder gradients
- Create and publish Docker image
- Add significant unit test coverage where sensible (ADR-0015)
- Add a few end-to-end test cases for safety (ADR-0014)
- Complete accessibility review + all fixes (ADR-0016)
- Replace "tags" with key/value ("props") setup
- Improve "props" with category-default keys
- Improve "props" with special-support keys (ADR-0019)
- Option to add more static cms pages
- Option to customize the footer (ADR-0020)
- Live rendered-markdown preview alongside the CMS editor textarea
- Import from our own export format
- Import from external sources (Goodreads CSV, etc.)
- Export database to basic formats (markdown, json or yaml, etc.) (ADR-0023)
- Soft-delete for items + trash (and recover) feature, with housekeeping warning (ADR-0022)
- Build external API for automated operations
- Create MCP to access the API of a Tierdom APP instance with AI tooling
- Add a LICENSE file to the repository
- Add a CODE OF CONDUCT file to the repository
- Add a CONTRIBUTING file to the repository
- Add a SECURITY file to the repository
- Add CI/CD
Known issues, bugs, and small TODO's:
- Improve markdown in Footer (lists, code block, table, etc.)
- SortableList keyboard reordering (a11y follow-up)
- Full color contrast audit with manual verification (a11y follow-up)
-
aria-liveregions for loading states and form feedback (a11y follow-up) - Improve image upload (better checks on file type and limit, UX improvements)
- Require double-confirmation for heavy deletes (e.g. category)
- Add
eslint-plugin-better-tailwindcssto catch deprecated Tailwind v4 classes in lint - Session cookies are missing
httpOnlyandsecureflags - Security headers (CSP, X-Frame-Options, X-Content-Type-Options) not yet configured
- Nav from a client-side-routed public page (e.g.
/category/[slug]) into/adminvia the user menu leaves the outgoing page's DOM in<main>β URL updates but the admin dashboard appears below the stale content; refreshing fixes it. Upstream Svelte 5 dev-HMR quirk (see svelte#14885 family); does not occur in preview/production builds. A workaround usingdata-sveltekit-reloadon the Admin links (plus a regression E2E test) is parked in git stashadmin-navigation-bug-fixβ revive withgit stash list/git stash applyon a future branch once we pick a non-reload fix.
Early alpha β expect breaking changes between versions. Back up your
/app/datadirectory before upgrading.
- A VPS or any machine that can run Docker
docker run -p 3000:3000 tierdom/tierdom-appOpen http://localhost:3000.
Log in at /admin with username admin and password admin.
Data is ephemeral β it lives inside the container and is lost when you remove it.
Create a docker-compose.yml on your server:
services:
tierdom:
image: tierdom/tierdom-app:latest
ports:
- '3000:3000'
volumes:
- ./data:/app/data
environment:
DATA_PATH: /app/data
ADMIN_USERNAME: your_username
ADMIN_PASSWORD: your_password # only used on first boot
ORIGIN: https://your-domain.com
LOG_VERBOSE: false # Set to true for full request/response headers in logs
restart: unless-stoppedThen start it:
docker compose up -dThe SQLite database is stored in /app/data/db.sqlite and uploaded images in /app/data/images/.
ADMIN_USERNAME and ADMIN_PASSWORD create the admin account on first boot only β changing them later has no effect.
ORIGIN should match the URL users visit (needed for SvelteKit's CSRF protection).
If you expose the container directly to the internet (no reverse proxy), set TLS_DOMAIN to get automatic Let's Encrypt certificates:
services:
tierdom:
image: tierdom/tierdom-app:latest
ports:
- '443:443'
- '80:80'
volumes:
- ./data:/app/data
environment:
DATA_PATH: /app/data
ADMIN_USERNAME: your_username
ADMIN_PASSWORD: your_password
TLS_DOMAIN: tierdom.example.com
LOG_VERBOSE: false # Set to true for full request/response headers in logs
restart: unless-stoppedCaddy handles certificate provisioning and renewal automatically.
Ports 80 and 443 must be reachable from the internet for the ACME challenge.
ORIGIN is inferred from TLS_DOMAIN when not set explicitly.
The data/ directory on the host contains everything: the database and all uploaded images.
cp -r ./data /your/backup/path/tierdom-$(date +%Y%m%d)To get started after cloning, simply run:
npm ci
npm run devA database with example data will be seeded.
| Layer | Choice |
|---|---|
| Framework | SvelteKit + Svelte (TypeScript) |
| Database | SQLite via Drizzle ORM (better-sqlite3) |
| Styling | Tailwind CSS |
| Testing | Vitest + Playwright |
| Runtime | Node.js 24 LTS (Alpine) |
| Deployment | Single Docker image |
Architectural decisions are documented as ADRs in docs/decisions/.
| ADR | Title | Status |
|---|---|---|
| 0001 | Use Architecture Decision Records | Accepted |
| 0002 | System Architecture | Accepted |
| 0003 | Tooling and Developer Experience | Accepted |
| 0004 | Domain Model | Accepted |
| 0005 | Frontend Styling | Accepted |
| 0006 | Admin Interface | Accepted |
| 0007 | Markdown Rendering | Accepted |
| 0008 | Use lucide-svelte for Icons | Accepted |
| 0009 | Add created_at and updated_at Timestamps | Accepted |
| 0010 | Authentication and Authorization | Accepted |
| 0011 | Image Support for Tier List Items | Accepted |
| 0012 | Docker Packaging and Publishing | Accepted |
| 0013 | UUID Primary Keys | Accepted |
| 0014 | End-to-End Testing Strategy | Accepted |
| 0015 | Unit Testing Strategy | Accepted |
| 0016 | Accessibility and Semantic HTML | Accepted |
| 0017 | Replace Tags with Item Props | Accepted |
| 0018 | Category Prop Keys | Accepted |
| 0019 | Prop Keys with Icon Set Support | Accepted |
| 0020 | Customizable Site Content via Generalized CMS | Accepted |
| 0021 | Admin Confirmation Dialog | Accepted |
| 0022 | Soft Delete and Trash | Accepted |
| 0023 | Export Tooling β Streaming ZIP (fflate) | Accepted |
| Document | Description |
|---|---|
| DOMAIN.md | Core domain model β entities, tiers, scoring |
| CLAUDE.md | Development guidelines and AI instructions |