Web-based raffle management system for schools, nonprofits, and community organizations.
You need three things before starting:
Admin password hash (SHA-256 — no $ signs, safe for Docker Compose and Portainer):
# Linux/Mac:
echo -n "yourpassword" | sha256sum | cut -d' ' -f1
# Windows (PowerShell):
python -c "import hashlib; print(hashlib.sha256(b'yourpassword').hexdigest())"
# Node.js:
node -e "const c=require('crypto'); console.log(c.createHash('sha256').update('yourpassword').digest('hex'))"Session secret (must be 32+ characters):
openssl rand -hex 32
Strong database password: pick anything secure.
Copy .env.example to .env and fill in the values:
cp .env.example .env
| Variable | Description |
|---|---|
POSTGRES_USER |
DB username (e.g. raffle) |
POSTGRES_PASSWORD |
Strong DB password |
POSTGRES_DB |
DB name (e.g. raffle) |
APP_PORT |
Port on the NAS (e.g. 3456) |
ADMIN_USERNAME |
Login username for admin panel |
ADMIN_PASSWORD_HASH |
bcryptjs hash of your admin password |
SESSION_SECRET |
32+ char random string |
NEXT_PUBLIC_BASE_URL |
Full URL students use (e.g. http://nas3:3456) |
docker-compose up -d --buildAccess the admin panel at: http://nas3:3456/admin/dashboard
Since Portainer cannot read .env files from NFS, you must provide environment variables via the Portainer UI when creating the stack.
Portainer: http://10.1.10.21:9000 · Endpoint: 14 (dockerLXC)
-
Build the image (first time or after code changes):
# SSH into NAS3 or run from the NAS console cd /volume1/docker/zz_claude/raffle docker build -t raffle-app:latest .
-
In Portainer → Stacks → Add Stack:
- Name:
raffle - Web editor: paste
docker-compose.ymlcontent - Important: Portainer caches compose files. Always DELETE the stack and CREATE a new one for updates.
- Name:
-
Set environment variables in Portainer's "Environment variables" section:
POSTGRES_USER=raffle POSTGRES_PASSWORD=your-strong-password POSTGRES_DB=raffle APP_PORT=3456 ADMIN_USERNAME=admin ADMIN_PASSWORD_HASH=240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9 SESSION_SECRET=your-32-char-secret NEXT_PUBLIC_BASE_URL=http://nas3:3456(The hash above is SHA-256 of
admin123— generate your own withecho -n "yourpassword" | sha256sum) -
Deploy — the app will automatically run database migrations on startup.
- Go to
/admin/dashboard, sign in - Prizes: Add prizes with a ticket cost (e.g. "Gaming Chair = 5 tickets/entry")
- Students: Add students individually or import a CSV, assign ticket balances
- Share magic links with students (copy from the Students table, or export a CSV with all links)
- Open the raffle on the Dashboard
- Monitor entries in the Entries tab
- When ready: Close the raffle, then go to Draw and run the draw
- Winners are displayed and can be exported as CSV
- Student receives their unique magic link (e.g.
http://nas3:3456/enter?token=...) - Opens the link — no login/password needed
- Allocates tickets to prizes using the +/− buttons
- System enforces they can't use more tickets than their balance
- Can re-allocate freely while the raffle is open
name,email,tickets
Alice Smith,alice@school.edu,10
Bob Jones,bob@school.edu,8
Carol White,,12After import, a CSV of magic links is available for download.
- Each prize has a ticket cost — the number of tickets per entry slot
- Students allocate tickets in multiples of that cost
- E.g. if Gaming Chair costs 5 tickets: 5 tickets = 1 entry, 10 tickets = 2 entries (better odds)
- The weighted random draw gives proportionally higher odds for more entries
When the raffle moves to the school's public website:
-
Replace
raffle-dbcontainer with a managed PostgreSQL service: -
Update
DATABASE_URLto the managed Postgres connection string -
Update
NEXT_PUBLIC_BASE_URLto the public domain -
The
raffle-appcontainer can be deployed to:- Railway (auto-deploys from the Dockerfile)
- Render, Fly.io, or any Docker-compatible host
No code changes required — only environment variable updates.