Files
storybid/ops/unraid-install.md
T
2026-05-04 14:52:15 -05:00

16 KiB
Raw Blame History

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 has Use Cache: Prefer or Only so 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_URL and the docker-compose.yml POSTGRES_PASSWORD entry 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).

Email

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

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 35 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

  1. Open Unraid web UI → Apps (Community Applications).
  2. Search for "Compose Manager".
  3. Click Install on the result by dcflachs.
  4. 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)

  1. Install Unraid File Manager from Community Applications.
  2. Navigate to /boot/config/plugins/compose.manager/projects/.
  3. Create a folder named storybid.
  4. Upload docker-compose.yml and your completed .env into 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

  1. In the Compose tab, click the storybid project row.

  2. Click Edit to open the compose file editor — verify paths look correct.

  3. Click the ⚙ Environment button (or edit the .env file directly via File Manager).

  4. Set every variable listed in the Environment Variables Reference above. At minimum:

    Key Value
    NODE_ENV production
    PUBLIC_URL https://bid.example.org
    JWT_SECRET (64-char random string — generate in Terminal with openssl rand -hex 32)
    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 your SMTP server
    SMTP_USER sender address
    SMTP_PASS SMTP password
    EMAIL_FROM Storybid <noreply@example.org>
  5. Click Save.

5. Build and start the stack

  1. In the Compose tab, click Up (▶) next to storybid.
  2. The compose manager opens a build log window. First build takes 35 minutes.
  3. 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, run docker ps to 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

  1. Go to Stripe Dashboard → Developers → Webhooks → Add endpoint.

  2. Endpoint URL: https://bid.example.org/api/webhooks/stripe

  3. Events to listen for:

    • payment_intent.succeeded
    • payment_intent.payment_failed
  4. Click Add endpoint.

  5. Copy the Signing secret (whsec_…) into STRIPE_WEBHOOK_SECRET in .env.

  6. 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

  1. Transfer the updated docker-compose.yml to the project folder (same path as installation).
  2. Compose tab → storybid → Pull + Up — this rebuilds images and restarts containers.
  3. 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:

  • .env is in the same directory as docker-compose.yml.
  • The server service in docker-compose.yml has env_file: .env.
  • No trailing whitespace or Windows line endings in .env (dos2unix .env fixes 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

  1. Verify the DNS record resolves from a device on the event Wi-Fi:
    nslookup auction.event.lan
    
  2. Confirm LOCAL_HOSTNAME in .env matches the DNS record exactly.
  3. 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.