Files
2026-04-21 09:07:49 -05:00

11 KiB

Deploying on Unraid

The MRP app is a single Docker container that stores everything (SQLite database + uploaded files + backups) under a single /data volume. This guide walks through installing it on an Unraid server using the Docker GUI, with images coming from a Gitea Actions runner that rebuilds on every push and publishes to registry.alwisp.com.

You can skip the registry and have Unraid build locally — both paths are documented below.


Architecture at a glance

  git push main ─► Gitea Actions (.gitea/workflows/docker-build.yml)
                     │
                     ├── docker login registry.alwisp.com
                     ├── docker build -f Dockerfile .
                     └── docker push registry.alwisp.com/<owner>/<repo>:latest
                                                 │
                                                 ▼
                        Unraid Docker tab  ──►  pull image
                                                 │
                                                 ▼
                        Container: /data volume, :3000, reverse-proxied

1. Prerequisites

  • Unraid 6.11+ with the Docker service enabled.
  • A subdomain (e.g. mrp.yourdomain.tld) pointed at Unraid through your existing reverse proxy (SWAG, Nginx Proxy Manager, Caddy, Traefik, Zoraxy — whichever you already use). The subdomain is what operators will scan into from their phones.
  • A Gitea server hosting this repo with:
    • Actions enabled, runner online
    • Two repo secrets: REGISTRY_USER and REGISTRY_TOKEN with push rights to registry.alwisp.com
    • The workflow at .gitea/workflows/docker-build.yml (already committed)

If you don't have the CI path, skip to §5: Unraid builds locally.


2. Prepare the data directory on Unraid

Create the appdata folder. Unraid conventionally uses /mnt/user/appdata/<app>:

mkdir -p /mnt/user/appdata/mrp-qrcode
chown -R 1001:1001 /mnt/user/appdata/mrp-qrcode

UID 1001 is the nextjs user inside the container (Dockerfile creates it). Getting this wrong is the #1 cause of "can't write to /data" errors at first boot.

Generate a secret you will paste into the APP_SECRET env var:

openssl rand -base64 48

Keep it somewhere safe. Changing it logs every existing session out.


3. Gitea Actions: auto-build the image

The workflow at .gitea/workflows/docker-build.yml triggers on every push to main. It runs in catthehacker/ubuntu:act-latest and does exactly:

- docker/login-action@v3   # registry.alwisp.com, REGISTRY_USER / REGISTRY_TOKEN
- docker build -t registry.alwisp.com/${{ gitea.repository }}:latest .
- docker push registry.alwisp.com/${{ gitea.repository }}:latest

${{ gitea.repository }} expands to <owner>/<repo>, so the published image path is:

registry.alwisp.com/<owner>/<repo>:latest

One-time repo setup:

  1. Gitea repo → Settings → Actions → Runners — confirm your runner is online and labelled ubuntu-latest.
  2. Gitea repo → Settings → Secrets — add:
    • REGISTRY_USER — a user that can push to registry.alwisp.com
    • REGISTRY_TOKEN — that user's password or personal access token

Pull credentials on Unraid (required if the registry is private):

# As root on Unraid
docker login registry.alwisp.com -u <user> -p <token>

Credentials are stored in /root/.docker/config.json; Unraid reuses them automatically on pull.


4. Install on Unraid (Docker GUI, docker pull path)

  1. Open the Unraid web UI → Docker tab → Add Container.
  2. Fill the template. Bold fields matter; the rest can stay default:
Field Value
Name mrp-qrcode
Repository registry.alwisp.com/<owner>/<repo>:latest
Network Type Bridge
Console shell command sh
Privileged Off
Icon URL (optional) point at any PNG you host; public/icon.png in the repo is a reasonable pick
  1. Port mappingAdd another Path, Port, Variable, Label or DevicePort:
Name Container Port Host Port Protocol
Web UI 3000 3000 (or any free port) TCP
  1. Volume mappingAddPath:
Name Container Path Host Path Access
Data /data /mnt/user/appdata/mrp-qrcode Read/Write
  1. Environment variablesAddVariable for each:
Key Required Example Notes
APP_URL https://mrp.yourdomain.tld Public URL; baked into QR payloads.
APP_SECRET (paste from §2) ≥32 chars. Signs sessions + QR tokens.
BOOTSTRAP_ADMIN_EMAIL on first boot you@yourdomain.tld Only used if no admin exists.
BOOTSTRAP_ADMIN_PASSWORD on first boot (strong password) Change in the UI after login.
BOOTSTRAP_ADMIN_NAME Plant Manager Display name.
ADMIN_SESSION_HOURS 8 Admin cookie TTL.
OPERATOR_SESSION_HOURS 12 Operator device TTL.
PIN_MAX_ATTEMPTS 5 Lockout threshold.
PIN_LOCKOUT_MINUTES 15 Lockout window.

DATABASE_URL and UPLOAD_DIR are pre-set inside the image (file:/data/app.db and /data/uploads). Do not override them unless you really mean it.

  1. Apply. Unraid pulls the image, starts the container, and the entrypoint:

    1. Creates /data/uploads and /data/backups if missing.
    2. Runs prisma migrate deploy.
    3. Creates the bootstrap admin if no admin exists yet.
    4. Starts Next.js on :3000.
  2. Check the log (click the container icon → Logs). You should see ✓ Ready on http://0.0.0.0:3000.

  3. Point your reverse proxy at http://<unraid-ip>:3000 with your TLS cert, and browse to https://mrp.yourdomain.tld/login/admin.

Updates

Every push to main produces a new :latest image at registry.alwisp.com/<owner>/<repo>.

  • Manual pull: Docker tab → container row → Force update (Unraid does docker pull then recreates the container).
  • Automated: install the CA Auto Update Applications plugin from Community Applications and let it pull fresh :latest on a schedule.

The /data volume is preserved across recreations. Migrations run automatically on start; if a release adds a new Prisma migration, it will apply once on first boot of the new image.

Pinning (optional)

The current docker-build.yml only publishes :latest. If you want immutable tags for rollback, extend the workflow to also push ${{ gitea.sha }} or a release tag, then point the Unraid Repository field at the pinned tag (e.g. registry.alwisp.com/<owner>/<repo>:v0.4.0). Rollback becomes a one-field change + Apply.


5. Alternative: Unraid builds locally (docker build path)

If you don't want a registry at all, Unraid can build from a local clone of the repo.

  1. On Unraid:

    mkdir -p /mnt/user/appdata/mrp-qrcode-src
    cd /mnt/user/appdata/mrp-qrcode-src
    git clone https://<your-gitea-host>/<owner>/<repo>.git .
    docker build -t mrp-qrcode:local .
    
  2. In the Docker GUI template (§4), set:

    • Repository: mrp-qrcode:local
    • Everything else identical.
  3. To update: git pull && docker build -t mrp-qrcode:local . && docker restart mrp-qrcode.

A tiny convenience script lives at docker/rebuild.sh if you want to cron it.


6. Alternative: docker compose on the Unraid CLI

The repo also ships a docker-compose.yml for users who prefer CLI. This path bypasses the Unraid GUI entirely — state goes to a Docker-managed volume instead of /mnt/user/appdata/…, unless you override the mount.

cp .env.example .env
# edit .env with real APP_URL, APP_SECRET, bootstrap admin creds
docker compose up -d --build

To mount /data under /mnt/user/appdata/mrp-qrcode instead of the named volume, change docker-compose.yml:

volumes:
  - /mnt/user/appdata/mrp-qrcode:/data

and chown -R 1001:1001 /mnt/user/appdata/mrp-qrcode first.


7. Reverse proxy notes

APP_URL must match the externally reachable URL — it is embedded in QR code payloads and used for absolute links on traveler cards. If operators scan a card and land on http://10.x.x.x:3000, their phone probably cannot reach that IP; always set APP_URL to the public subdomain.

Minimal Nginx Proxy Manager config:

  • Domain: mrp.yourdomain.tld
  • Scheme: http, Forward host: <unraid-ip>, Forward port: 3000
  • Block Common Exploits: on
  • Websockets: on (Next.js hot reload uses them in dev; in prod they're unused but harmless)
  • SSL: request a Let's Encrypt cert; Force SSL + HTTP/2 on.

8. Backups

The container does not yet run automatic backups (shipping in step 10 of the build plan). Until then, your Unraid backup strategy should cover:

  • /mnt/user/appdata/mrp-qrcode/app.db (SQLite file)
  • /mnt/user/appdata/mrp-qrcode/app.db-wal and app.db-shm if present
  • /mnt/user/appdata/mrp-qrcode/uploads/ (STEP / PDF / DXF / SVG assets)

A safe live snapshot of SQLite (works while the app is running):

docker exec mrp-qrcode sqlite3 /data/app.db \
  ".backup '/data/backups/app-$(date +%F).db'"

Cron it nightly via User Scripts plugin.


9. First-login checklist

  1. Sign in at https://mrp.yourdomain.tld/login/admin with the bootstrap email + password you set in step 4.
  2. Users → change your own password; create operators (each gets a name + 4-digit PIN).
  3. Machines → add your shop equipment (NCT punch, press brake, rivet, weld, …).
  4. Operation templates → pre-author your common recipes so they appear in the operation picker later.
  5. Projects → create your first project, add assemblies, add parts, upload STEP / drawing / cut files.
  6. Operations on each part → author steps, reorder, verify QR tokens appear in the table.

Step 4 of the build plan (the operator scan flow) lands next; until then, the QR tokens are visible and tested but not yet scannable to an operator view.


10. Troubleshooting

Symptom Fix
APP_SECRET must be at least 32 chars on start Regenerate via openssl rand -base64 48 and paste into the env var.
EACCES / permission errors writing to /data chown -R 1001:1001 /mnt/user/appdata/mrp-qrcode on the host.
Healthcheck failing docker logs mrp-qrcode — usually a missing env var or a broken reverse proxy.
Unraid won't pull the image Run docker login registry.alwisp.com on the host with the same user/token the runner uses. Confirm the repo name matches ${{ gitea.repository }} exactly (case-sensitive).
Migrations didn't run The entrypoint calls prisma migrate deploy. Logs will show which migration failed. Don't manually touch _prisma_migrations.
QR codes link to localhost APP_URL was left at default. Update the env var and restart the container.
Can't log in after redeploy APP_SECRET changed → every session cookie is invalid. Log in again.