16 KiB
Unraid Installation Guide
Storybid runs as four Docker containers (PostgreSQL, Redis, server, client) managed
by a single docker-compose.yml. This guide covers two installation paths:
- CLI — SSH into Unraid and use the terminal directly. Recommended; always works.
- GUI — Use the Community Applications Compose Manager plugin to manage the stack from the Unraid web interface.
Both methods produce an identical running system.
Prerequisites
| Requirement | Notes |
|---|---|
| Unraid 6.12 or later | Earlier versions lack the built-in Compose support CLI needs |
| Community Applications plugin | Required for GUI method; install from CA store |
| Docker enabled | Unraid → Settings → Docker → Enable Docker = Yes |
| At least 2 GB free RAM | PostgreSQL + Redis + Node API + Nginx |
| A user share for appdata | Default: /mnt/user/appdata/ |
| A public domain name | For Stripe webhooks and the public bidder URL |
| Stripe account | Live or test; publishable + secret key + webhook secret |
| Twilio Verify service | For SMS OTP login (optional but recommended) |
| SMTP relay | Any transactional email provider (Gmail, Postmark, Mailgun…) |
Directory Layout
All persistent data lives under a single appdata directory. The recommended path is:
/mnt/user/appdata/storybid/
├── repo/ ← cloned source code + docker-compose.yml
│ ├── .env ← your live config (never commit this file)
│ └── docker-compose.yml
├── postgres/ ← PostgreSQL data volume (managed by Docker)
├── redis/ ← Redis data volume (managed by Docker)
└── uploads/ ← uploaded item media (mapped from media_data volume)
Tip: Keep
repo/on a cache-backed share so builds are fast, but ensure the share hasUse Cache: PreferorOnlyso data doesn't move to array disks mid-event.
Environment Variables Reference
Copy .env.example to .env and fill in every value before starting the stack.
cp /mnt/user/appdata/storybid/repo/.env.example \
/mnt/user/appdata/storybid/repo/.env
Core app
| Variable | Example | Description |
|---|---|---|
NODE_ENV |
production |
Must be production for live deployments |
PORT |
3001 |
Internal port the API server listens on |
PUBLIC_URL |
https://bid.example.org |
Externally reachable HTTPS URL. Used in magic-link emails and as the first-choice socket endpoint |
LOCAL_HOSTNAME |
auction.event.lan |
LAN hostname for event-night failover (see unifi-dns.md). Must resolve on the event Wi-Fi. |
JWT_SECRET |
64-char random string |
Signs all auth tokens. Generate with openssl rand -hex 32 |
CLIENT_URL |
https://bid.example.org |
Allowed CORS origin for the client; usually same as PUBLIC_URL |
Database
| Variable | Example | Description |
|---|---|---|
DATABASE_URL |
postgresql://storybid:PASS@db:5432/storybid |
Postgres connection string. The hostname is db (the Docker service name) in production |
Change the password in both
DATABASE_URLand thedocker-compose.ymlPOSTGRES_PASSWORDentry before first boot.
Redis
| Variable | Example | Description |
|---|---|---|
REDIS_URL |
redis://redis:6379 |
Redis connection string. The hostname is redis inside the compose network |
Stripe
| Variable | Example | Description |
|---|---|---|
STRIPE_SECRET_KEY |
sk_live_… |
Stripe secret key from Dashboard → Developers → API keys |
STRIPE_PUBLISHABLE_KEY |
pk_live_… |
Stripe publishable key — returned to bidder browsers to initialise payment forms |
STRIPE_WEBHOOK_SECRET |
whsec_… |
Signing secret for POST /api/webhooks/stripe. Created when you register the webhook endpoint in Stripe Dashboard |
Twilio Verify (SMS OTP)
| Variable | Example | Description |
|---|---|---|
TWILIO_ACCOUNT_SID |
ACxxxx… |
Found on your Twilio Console dashboard |
TWILIO_AUTH_TOKEN |
xxxx… |
Found on your Twilio Console dashboard |
TWILIO_VERIFY_SERVICE_SID |
VAxxxx… |
Create a Verify service in Twilio Console → Verify → Services |
Leave all three blank to disable SMS login (email magic links still work).
| Variable | Example | Description |
|---|---|---|
SMTP_HOST |
smtp.postmarkapp.com |
Outbound SMTP server |
SMTP_PORT |
587 |
SMTP port (587 = STARTTLS, 465 = SSL) |
SMTP_USER |
noreply@example.org |
SMTP username / sender address |
SMTP_PASS |
… |
SMTP password or API key |
EMAIL_FROM |
Storybid <noreply@example.org> |
Display name + address in the From header |
Media storage
| Variable | Default | Description |
|---|---|---|
UPLOAD_DIR |
/app/uploads |
Absolute path inside the server container where media files are written. Maps to media_data Docker volume |
MEDIA_BASE_URL |
/media |
URL prefix the server uses to serve media. Change to an absolute URL if you front uploads with a CDN |
Method 1 — CLI via SSH
1. Enable SSH on Unraid
Unraid web UI → Settings → Management Access → SSH → Enable SSH = Yes
Connect from your workstation:
ssh root@192.168.1.X # replace with your Unraid IP
2. Clone the repository
mkdir -p /mnt/user/appdata/storybid
cd /mnt/user/appdata/storybid
git clone https://github.com/YOUR_ORG/storybid.git repo
cd repo
If you don't have git on Unraid, install it via the NerdTools Community
Applications plugin, or transfer a zip via SCP:
# From your workstation
scp storybid.zip root@192.168.1.X:/mnt/user/appdata/storybid/
# On Unraid
cd /mnt/user/appdata/storybid && unzip storybid.zip && mv storybid repo
3. Configure the environment file
cd /mnt/user/appdata/storybid/repo
cp .env.example .env
nano .env # or vi .env
Minimum required edits:
NODE_ENV=production
PUBLIC_URL=https://bid.example.org
JWT_SECRET=$(openssl rand -hex 32) # paste output, don't run inline in .env
DATABASE_URL=postgresql://storybid:CHANGE_ME@db:5432/storybid
STRIPE_SECRET_KEY=sk_live_...
STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
SMTP_HOST=smtp.example.com
SMTP_USER=noreply@example.org
SMTP_PASS=...
EMAIL_FROM=Storybid <noreply@example.org>
Also update docker-compose.yml to match the Postgres password you set above:
# In docker-compose.yml, under the db service:
POSTGRES_PASSWORD: CHANGE_ME
# And in the server service environment:
DATABASE_URL: postgresql://storybid:CHANGE_ME@db:5432/storybid
4. Update volume paths (optional — recommended)
By default Docker manages named volumes internally. To make them visible in the Unraid GUI and ensure they survive array changes, pin them to explicit paths:
# In docker-compose.yml, replace the volumes block at the bottom:
volumes:
postgres_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/user/appdata/storybid/postgres
redis_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/user/appdata/storybid/redis
media_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/user/appdata/storybid/uploads
Create the directories first:
mkdir -p /mnt/user/appdata/storybid/{postgres,redis,uploads}
5. Build and start the stack
cd /mnt/user/appdata/storybid/repo
docker compose up -d --build
First build downloads base images and compiles the TypeScript source — allow 3–5 minutes. Watch progress:
docker compose logs -f
All four containers should reach Up state:
NAME STATUS
storybid-db-1 Up (healthy)
storybid-redis-1 Up (healthy)
storybid-server-1 Up
storybid-client-1 Up
6. Run database migrations
Run once after the first boot, and again after any update that includes schema changes:
docker compose exec server npx prisma migrate deploy
7. Create the first admin user
docker compose exec server node -e "
const { prisma } = await import('./dist/lib/prisma.js');
await prisma.organization.create({
data: {
name: 'My Charity',
staffUsers: {
create: {
name: 'Admin',
email: 'admin@example.org',
role: 'admin',
passwordHash: null,
}
}
}
});
console.log('Done');
await prisma.\$disconnect();
"
Then use the magic-link login flow at http://UNRAID-IP:8080 to sign in with
admin@example.org. The magic link will arrive at the SMTP address you configured.
Method 2 — GUI via Compose Manager Plugin
The Docker Compose Manager plugin lets you manage compose stacks entirely from the Unraid web interface without touching the terminal.
1. Install the plugin
- Open Unraid web UI → Apps (Community Applications).
- Search for "Compose Manager".
- Click Install on the result by dcflachs.
- After installation, a new Compose tab appears in the Docker section.
2. Upload the source files
The plugin reads compose files from /boot/config/plugins/compose.manager/projects/.
Each project is a subdirectory containing docker-compose.yml and optionally a .env.
Option A — File Manager plugin (easiest)
- Install Unraid File Manager from Community Applications.
- Navigate to
/boot/config/plugins/compose.manager/projects/. - Create a folder named
storybid. - Upload
docker-compose.ymland your completed.envinto that folder.
Option B — SCP from your workstation
# From your workstation
scp docker-compose.yml root@192.168.1.X:/boot/config/plugins/compose.manager/projects/storybid/
scp .env root@192.168.1.X:/boot/config/plugins/compose.manager/projects/storybid/
3. Set explicit volume bind mounts
Before starting, edit docker-compose.yml to replace Docker-managed volumes with
explicit host paths so Unraid can show them in the GUI (same change as step 4 of the
CLI method above):
volumes:
postgres_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/user/appdata/storybid/postgres
redis_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/user/appdata/storybid/redis
media_data:
driver: local
driver_opts:
type: none
o: bind
device: /mnt/user/appdata/storybid/uploads
Create the host directories via Unraid → Tools → New Terminal:
mkdir -p /mnt/user/appdata/storybid/{postgres,redis,uploads}
4. Configure environment variables in the GUI
-
In the Compose tab, click the storybid project row.
-
Click Edit to open the compose file editor — verify paths look correct.
-
Click the ⚙ Environment button (or edit the
.envfile directly via File Manager). -
Set every variable listed in the Environment Variables Reference above. At minimum:
Key Value NODE_ENVproductionPUBLIC_URLhttps://bid.example.orgJWT_SECRET(64-char random string — generate in Terminal with openssl rand -hex 32)DATABASE_URLpostgresql://storybid:CHANGE_ME@db:5432/storybidSTRIPE_SECRET_KEYsk_live_…STRIPE_PUBLISHABLE_KEYpk_live_…STRIPE_WEBHOOK_SECRETwhsec_…SMTP_HOSTyour SMTP server SMTP_USERsender address SMTP_PASSSMTP password EMAIL_FROMStorybid <noreply@example.org> -
Click Save.
5. Build and start the stack
- In the Compose tab, click Up (▶) next to storybid.
- The compose manager opens a build log window. First build takes 3–5 minutes.
- When all containers show Running, close the log.
6. Run migrations via the terminal
The GUI plugin does not yet have a built-in exec interface, so open a terminal for
this one step:
Unraid → Tools → New Terminal
docker exec storybid-server-1 npx prisma migrate deploy
The container name format is
<project>-<service>-1. If yours differs, rundocker psto find the exact name.
Reverse Proxy (Nginx Proxy Manager)
Storybid needs HTTPS for Stripe webhooks, PWA installation, and the camera API used by the check-in page. Nginx Proxy Manager is the easiest option on Unraid.
Install Nginx Proxy Manager
Search "Nginx Proxy Manager" in Community Applications and install it. Default ports: HTTP 80, HTTPS 443, Admin UI 81.
Add a proxy host for the client
| Field | Value |
|---|---|
| Domain Names | bid.example.org |
| Scheme | http |
| Forward Hostname / IP | Unraid LAN IP (e.g. 192.168.1.50) |
| Forward Port | 8080 |
| SSL Certificate | Request via Let's Encrypt (built into the GUI) |
| Force SSL | ✅ |
| HTTP/2 Support | ✅ |
Add a proxy host for the API + WebSocket
Create a second proxy host (or add a location block) if your client and server are
on different subdomains, otherwise the single bid.example.org host handles both
because the Nginx config inside the client container already proxies /api and
/socket.io to server:3001.
If you prefer separate subdomains:
| Field | Value |
|---|---|
| Domain Names | api.example.org |
| Scheme | http |
| Forward Hostname / IP | 192.168.1.50 |
| Forward Port | 3001 |
| SSL | Let's Encrypt |
| WebSockets Support | ✅ |
Then update PUBLIC_URL and CLIENT_URL in .env accordingly.
Register the Stripe Webhook
-
Go to Stripe Dashboard → Developers → Webhooks → Add endpoint.
-
Endpoint URL:
https://bid.example.org/api/webhooks/stripe -
Events to listen for:
payment_intent.succeededpayment_intent.payment_failed
-
Click Add endpoint.
-
Copy the Signing secret (
whsec_…) intoSTRIPE_WEBHOOK_SECRETin.env. -
Restart the server container to pick up the new value:
# CLI docker compose restart server # GUI — Compose tab → storybid → Restart
Updating Storybid
CLI
cd /mnt/user/appdata/storybid/repo
git pull
docker compose up -d --build
docker compose exec server npx prisma migrate deploy
GUI
- Transfer the updated
docker-compose.ymlto the project folder (same path as installation). - Compose tab → storybid → Pull + Up — this rebuilds images and restarts containers.
- Open Unraid → Tools → New Terminal and run:
docker exec storybid-server-1 npx prisma migrate deploy
Port Reference
| Port | Service | Exposed to |
|---|---|---|
3001 |
API server | Reverse proxy / LAN |
8080 |
Client (Nginx) | Reverse proxy / LAN |
5432 |
PostgreSQL | LAN only (close in production if not needed) |
6379 |
Redis | LAN only (close in production if not needed) |
To restrict Postgres and Redis to the Docker internal network only (recommended for
production), remove their ports: entries from docker-compose.yml:
# Remove or comment out these blocks:
db:
ports:
- "5432:5432" # ← remove
redis:
ports:
- "6379:6379" # ← remove
Troubleshooting
Containers restart immediately
docker compose logs server # check for DATABASE_URL or JWT_SECRET errors
docker compose logs db # check for volume permission issues
Migration fails with "relation already exists"
The database was already migrated. This is safe to ignore, or run:
docker compose exec server npx prisma migrate status
STRIPE_SECRET_KEY is not configured error in logs
The .env file is not being loaded. Verify:
.envis in the same directory asdocker-compose.yml.- The
serverservice indocker-compose.ymlhasenv_file: .env. - No trailing whitespace or Windows line endings in
.env(dos2unix .envfixes CRLF issues).
Media uploads return 404
Check that the uploads bind-mount directory exists and is writable:
ls -la /mnt/user/appdata/storybid/uploads
chmod 755 /mnt/user/appdata/storybid/uploads
docker compose restart server
Bidders can't connect via local LAN hostname
- Verify the DNS record resolves from a device on the event Wi-Fi:
nslookup auction.event.lan - Confirm
LOCAL_HOSTNAMEin.envmatches the DNS record exactly. - See unifi-dns.md for full UniFi DNS setup steps.
Compose Manager shows "project not found"
The project directory must be inside /boot/config/plugins/compose.manager/projects/.
Files on /mnt/user/ shares are not read by the plugin. Use SCP or File Manager to
place files in the correct location on /boot.