A minimal full-stack web app for submitting customer support tickets, analyzing their sentiment with a Hugging Face model, and storing them in PostgreSQL. Built for the SUST CSE Carnival 2026 Codex Community Hackathon.
- Frontend: React + Vite, served by Nginx on port
80inside the container (published as host port3000by Compose). - Backend: FastAPI + Uvicorn on port
8000, with a DistilBERT sentiment model loaded once at startup. - Database: PostgreSQL with a named Docker volume.
- Orchestration: Docker Compose (one command to run everything).
React/Vite Frontend -> Nginx (:80) -- /api/* --> FastAPI (:8000) -> PostgreSQL (:5432)
|
+-> Hugging Face DistilBERT (baked into image)
Requirements: Docker and Docker Compose.
docker compose up --buildThen open http://localhost:3000.
To stop and remove containers (keeping the database volume):
docker compose downTo wipe the database volume too:
docker compose down -vThese defaults are baked into docker-compose.yml and the Dockerfiles — change them there if needed.
| Service | Variable | Default |
|---|---|---|
| backend | DATABASE_URL |
postgresql://postgres:postgres@db:5432/ticket_db |
| backend | MODEL_NAME |
distilbert-base-uncased-finetuned-sst-2-english |
| backend | HF_HOME |
/opt/hf-cache |
| backend | TRANSFORMERS_OFFLINE |
1 (no network access to HuggingFace at runtime) |
| frontend | VITE_API_BASE_URL |
/api (build-time, baked into the React bundle) |
Returns {"status": "ok"} when the backend is up.
Body:
{ "title": "Lab VM issue", "message": "My VM is not opening.", "category": "lab" }category is optional.
Response (the saved ticket):
{
"id": 1,
"title": "Lab VM issue",
"message": "My VM is not opening.",
"category": "lab",
"sentiment": "NEGATIVE",
"confidence": 0.998,
"created_at": "2026-05-20T10:30:00"
}Returns all saved tickets, newest first.
# Build images
docker compose build
# Run in foreground
docker compose up
# Run in background
docker compose up -d
# Push to DockerHub (replace <dockerhub-username>)
docker tag ticket-analyzer-backend <dockerhub-username>/ticket-analyzer-backend:v1
docker tag ticket-analyzer-frontend <dockerhub-username>/ticket-analyzer-frontend:v1
docker push <dockerhub-username>/ticket-analyzer-backend:v1
docker push <dockerhub-username>/ticket-analyzer-frontend:v1The backend image bakes the Hugging Face model weights during build, so the running container does not need internet access to HuggingFace.
- Push the two images to DockerHub as shown above.
- SSH into the VM and install Docker + Docker Compose.
- Copy
docker-compose.ymlto the VM. - On the VM, override the image names in a small override file or edit the compose file:
services: backend: image: <dockerhub-username>/ticket-analyzer-backend:v1 frontend: image: <dockerhub-username>/ticket-analyzer-frontend:v1 ports: - "80:80" db: image: postgres:16-alpine ...
- Run
docker compose up -d. - Open
http://<vm-public-ip>in a browser.
ticket-analyzer/
PRD.md
README.md
docker-compose.yml
.env.example
backend/
Dockerfile
requirements.txt
app/
__init__.py
main.py
database.py
models.py
schemas.py
sentiment.py
frontend/
Dockerfile
nginx.conf
package.json
index.html
src/
main.jsx
App.jsx
styles.css
- "Failed to download model" at build time — the backend Dockerfile needs internet access to HuggingFace during
docker compose build. This only happens at build; the running container does not need it (TRANSFORMERS_OFFLINE=1). - Backend logs show DB connection errors on first start — the compose file uses a
dbhealthcheck, so the backend waits for PostgreSQL to be ready. If you started containers out of order, rundocker compose restart backend. - Frontend cannot reach the backend — confirm Nginx is reverse-proxying
/api/tohttp://backend:8000/. The browser should hit/api/healthand see{"status":"ok"}. - Reset everything —
docker compose down -vremoves containers and thepgdatavolume.
- GitHub repository: add your link here
- DockerHub images: add your links here
- Live deployed URL: add your URL here