A full-stack movie / TV tracker web app, using the TMDB API.
- User accounts and profiles
- Library: watched / to watch
- Filter, sort and view library as grid/list
- Collections: organize titles in collections
- Visibilty: set library, collections and titles as public/private
- Follow users and get updates on your feed
- Rate titles and add comments
- Search, get data and posters from TMDB
- Frontend: React, Vite, Tailwind CSS
- Backend: NestJS
- Database: PostgreSQL
- ORM: Prisma
- Containerization: Docker, Docker Compose
- Nginx
- Cloudflare Tunnel (optional)
- CI/CD: GitHub Actions, GHCR, webhook
- Make
- Docker
- Docker Compose
# copy and fill env
cp .env.example .env
# build and start dev
make dev
# build and start prod
make prodExpose the app publicly via Cloudflare Tunnel.
Create a tunnel in Protect & Connect → Networking → Tunnels then add a "Published application" route to http://frontend:80.
# build prod and start tunnel
make cloudCLOUDFLARE_TOKEN needs to be filled in .env.
Optional: if you want to deploy via webhook, create a route to http://host.docker.internal:9000 that will be your public deploy URL and follow deployment setup instructions.
| Command | Description |
|---|---|
make dev |
Build and start dev |
make redev |
Rebuild and start dev |
make prod |
Start prod |
make cloud |
Prod + Cloudflare Tunnel |
make deploy |
Deploy (cloud) |
make down |
Stop all containers |
make logs |
Logs |
make shback |
Shell in backend container |
make shdb |
Shell in db container |
make prisma |
Prisma Studio (dev) |
Push to main runs checks, builds Docker images, pushes them to GHCR, then auto-deploys on the production server via a webhook.
Continuous Integration (CI)
- Run checks: lint, typecheck, test and build, for frontend and backend.
- Build and push: build images from
Dockerfile.prod, tag:latest/:commit-shathen push to GHCR.
Continuous Deployment (CD)
- Compute HMAC-SHA256 signature of the payload and send it to webhook.
- Production server verifies signature, pulls the new images and restarts production.
⚠️ WithoutDEPLOY_WEBHOOK_URLandWEBHOOK_SECRETset, the pipeline still builds and pushes images to GHCR but skips the deploy step.
GitHub
Add these variables in Settings → Secrets and variables → Actions:
| Name | Type | Value |
|---|---|---|
DEPLOY_WEBHOOK_URL |
Secret | e.g.: https://deploy.yourdomain.com/hooks/deploy-watched |
WEBHOOK_SECRET |
Secret | Same value as WEBHOOK_SECRET in .env |
Note: To pull private images, create a GHCR PAT (scope: read:packages) at github.com/settings/tokens and auth to GHCR: docker login ghcr.io -u <username> -p <PAT>.
Production server
Requires webhook.
# create dedicated user
sudo adduser --system --group --home /opt/watched deploy
sudo usermod -aG docker deploy
# clone + configure
sudo -u deploy git clone https://github.com/jeromeberg/watched.git /opt/watched
cd /opt/watched
sudo -u deploy cp .env.example .env
# fill in .env (WEBHOOK_SECRET must match GitHub secret)
# install webhook
sudo apt update && sudo apt install -y webhook
sudo cp deploy/webhook.service /etc/systemd/system/webhook.service
sudo systemctl daemon-reload
sudo systemctl enable --now webhook.service
# first deployment
sudo -u deploy make deployMaintenance
# logs
journalctl -u webhook -f
tail -f /opt/watched/deploy/deploy.log
# rollback
make deploy IMAGE_TAG=<commit-sha>