PaynEat POSInstall guide
Four ways to run your restaurant on PaynEat POS
Pick the one that fits and follow it step by step. No sign-up, no fees, and your restaurant’s data stays on your own machine.
- Free · Apache 2.0
- No sign-up
- Runs on the devices you already have
What do you want to do?
Not sure yet? Start with the first one — tap around until you’re happy, then install it for real.
Try it in the browser
Open the demo- Recommended
Windows, one line
How to install Docker for every device
How to installRun from source
How to run
Install on Windows with one line
The real system — server, live updates across windows, a simulated scale and PDF receipts — without downloading code or building anything: the installer fetches ready-built images.
- Time
- 3–5 minutes (about 200 MB the first time)
- You need
- 64-bit Windows + Docker Desktop
- You get
- PaynEat at http://localhost:8080 on this PC
Install and open Docker Desktop
Don’t have it? Download it from docker.com, install it and open it. Wait until the bottom-left corner says Engine running.
Open PowerShell
Press the Windows key, type PowerShell and press Enter.
Paste this line and press Enter
Press Copy in the corner of the box, then right-click inside PowerShell to paste.
PowerShell[Net.ServicePointManager]::SecurityProtocol = 3072; iex ((New-Object Net.WebClient).DownloadString('https://github.com/SuruchBoss/PaynEat/releases/download/demo/install-demo.ps1'))Wait, then sign in
Your browser opens http://localhost:8080 by itself. Sign in with
admin/admin123, or tap a demo account button on the sign-in screen.
✅PowerShell prints PaynEat is ready: http://localhost:8080 in green and the browser shows the sign-in screen.
Day to day
- Start / stop / reset
- The PaynEat-Demo folder in your user folder has double-click files: Start, Stop and Reset PaynEat data (Reset erases every bill you tried and restores the sample data).
- After a restart
- Open Docker Desktop and PaynEat comes back by itself.
- Update to the latest version
- Paste the same line again. Your data stays.
⚠️ Set up for trying it out only (demo accounts, simulated scale, emails never really sent), and it opens on this PC only — to let the restaurant’s phones and tablets connect, use way 3. Want to read what the script does first? install-demo.ps1
Run it with Docker so every device connects
The server lives on one computer in the restaurant; staff phones, the cashier’s tablet and the kitchen screen open it in a browser. An order placed on one device shows up on the kitchen screen at once.
- Time
- 10–15 minutes the first time (under a minute after that)
- You need
- Docker Desktop on Windows, Mac or Linux
- You get
- PaynEat at http://<this computer’s IP>:8080 from every device on the same Wi-Fi
Download the code
Easiest: download the ZIP and extract it — you get a folder called
PaynEat-main. If you have git, this works too:Terminalgit clone https://github.com/SuruchBoss/PaynEat.gitFind this computer’s IP address
Windows: open PowerShell, type
ipconfigand look for IPv4 Address. Mac/Linux:ifconfig | grep inet. It looks like192.168.1.15.Set the server secret
Create a file named
.envin the code folder (next todocker-compose.yml) containing aJWT_SECRET=line with your own random value. Without it the system does not start and explains how to set it. On Mac/Linux use the command below; in Windows PowerShell use("JWT_SECRET=" + [guid]::NewGuid().ToString("N") + [guid]::NewGuid().ToString("N")) | Out-File -Encoding ascii .env.Terminal (Mac/Linux)echo "JWT_SECRET=$(openssl rand -hex 32)" > .envTell the app where the server is
Add this line to the same
.envfile, using your own IP. Skip this step if only this computer will use it..envAPI_BASE_URL=http://192.168.1.15:3000Start it
Open a terminal (Windows: PowerShell) in the code folder and run this. The first time takes 5–10 minutes while the app is built.
Terminaldocker compose up --buildOpen it on every device
On the same Wi-Fi, open http://192.168.1.15:8080 (your IP) on phones, tablets or the kitchen screen. On the server itself, http://localhost:8080 works.
✅The terminal shows payneat-api and payneat-web, and http://localhost:3000/health answers.
Day to day
- Stop it
- Press Ctrl+C in the terminal, then run
docker compose down. - Start over with fresh data
docker compose down -v(erases every bill and all data).- Turn on the simulated scale and test emails
- Add
SCALE_DRIVER=simulatorandMAIL_TRANSPORT=jsonto the same.envfile, then rundocker compose up --buildagain. Never use the simulator in a real shop.
💡 Another device can’t open it? Check it’s on the same Wi-Fi and that the server’s firewall allows ports 8080 and 3000. Before staff use it for real, go through the go-live checklist.
Run it from source
For changing the code, reading the log request by request, or running on a real phone through Flutter.
- Time
- About 5 minutes
- You need
- Node.js 20 or later (22 recommended) + Flutter 3.35 or later
- You get
- The API at http://localhost:3000 and the app in Chrome
Get the code
Terminalgit clone https://github.com/SuruchBoss/PaynEat.git cd PaynEatWindow 1 — the server
.envholds aJWT_SECRETfor local use; without it the server refuses to start (on Windows usecopyinstead ofcp). The database and sample data are created for you.Terminal 1cd backend npm install cp .env.example .env npm run devWindow 2 — the app
Open a new window and keep the first one running.
Terminal 2cd app flutter pub get flutter run -d chrome
✅Window 1 prints a line with PaynEat POS API listening on http://localhost:3000 and Chrome opens the sign-in screen.
Handy shortcuts
- Just the app, no server
flutter run -d chrome --dart-define=DEMO_MODE=true(sample data in the browser, reset on refresh)- A real phone on the same Wi-Fi
flutter run --dart-define=API_BASE_URL=http://<your computer’s IP>:3000- Android emulator
- Points at
10.0.2.2:3000by itself, nothing to set. - Run the tests
cd backend && npm test·cd app && flutter test
Architecture, the API and every test are described in the README.
Back up and restore
All of the shop's data is in one file on the server. PaynEat backs it up by itself after each shift close, every 6 hours when data changed, and before an update. The status is in Settings for the admin; if no backup has succeeded for more than 26 hours, the owner and managers see a warning on the home screen.
- Where
- data/backups next to the database, plus a second location you set
- How long
- Every file for 48 hours, then one a day for 30 days
- Restoring
- A command on the server; the app has no restore button

Set a second location on another disk
The first copy is on the same disk as the database, so a failed disk loses both. Plug in a USB drive or use a folder on a NAS in the shop, add this line to
backend/.env(with your folder) and restart the serverbackend/.env (running from code)BACKUP_COPY_DIR=E:\PaynEat-backupDocker: keep the second copy outside the container
Uncomment
- /mnt/usb-backup/payneat:/backup-copyundervolumes:ofapiindocker-compose.yml(change the part before:to your folder; on Linux let uid 1000 write to it:sudo chown 1000:1000 /mnt/usb-backup/payneat), add this line to.env, then rundocker compose up -d.env (Docker)BACKUP_COPY_DIR=/backup-copyMake one backup to check
Sign in as the admin, open Settings → Backups and press Back up now. Both locations should show a green tick
Restoring: stop the server first
The restore command refuses to run while the server is up · From code: press Ctrl+C in the server window · Docker: run this in the code folder
Terminal (Docker)docker compose stop apiPick the file to restore
The name says when and why, e.g.
payneat-2026-09-29T221530+0700-shift-close.sqliteis the one after the 22:15 shift close on the 29th · From code: look inbackend/data/backups· Docker: list them with this commandTerminal (Docker)docker compose run --rm api ls data/backupsRestore it
From code:
cd backend, thennpm run db:restore -- data/backups/<file name>· Docker: the command below · PaynEat checks the file first and always keeps the current data as apre-restorefile, so a wrong pick can be undoneTerminal (Docker)docker compose run --rm api npm run db:restore -- data/backups/payneat-2026-09-29T221530+0700-shift-close.sqliteStart the server again
From code:
npm start(ornpm run dev) · Docker: the command belowTerminal (Docker)docker compose start api
✅The restore command prints Restored … from … with the name of the pre-restore file, and the Audit Log shows Restore from a backup
If the command refuses
- The PaynEat server is still running
- Stop the server first. If the machine just went down by itself (a power cut), wait 30 seconds and try again
- The backup file is damaged
- Pick an earlier file, or the file of the same name from the second location
- made by a newer PaynEat
- The file comes from a newer PaynEat. Update PaynEat first, then restore again
🔒 A backup holds customer phone numbers and staff password hashes. Keep the USB drive somewhere locked and never share or upload it to a cloud · One-line Windows installer (demo): use the same Docker commands in PowerShell, adding -f "$env:USERPROFILE\PaynEat-Demo\docker-compose.yml" after docker compose
Sign in as any role
The sign-in screen has a button for every demo account — one tap and you’re in, nothing to type.
| Role | Username | Password | What it sees |
|---|---|---|---|
| Administrator | admin | admin123 | Everything — dashboard, menu, staff, reports, settings |
| Manager | manager | manager123 | Same as the administrator, but can’t delete user accounts |
| Waiter | waiter1 | waiter123 | Floor plan, orders, kitchen screen |
| Kitchen | kitchen | kitchen123 | The kitchen screen only |
| Cashier | cashier | cashier123 | Floor plan, orders, reports |
| Waiter, two branches | waiter2 | waiter123 | Like waiter1 but can pick a branch — typed by hand, and only with a real server (ways 2–4) |
The browser demo keeps its data on each device separately — an order from a phone won’t reach a kitchen screen on another tablet. To watch orders move between devices, use way 3, or open several windows on the one PC of way 2. The globe button top right switches ไทย / English / 한국어 even before you sign in.
Checklist before staff use it for real
The demo accounts and passwords above are published on the internet, so the system guards against them: in production mode it refuses to start while they’re still in place.
Every item in detail, and how to report a vulnerability: SECURITY.md
- Set
JWT_SECRETto your own random value, for example fromopenssl rand -hex 32. WithNODE_ENV=production, the server refuses to start with a sample value or one shorter than 32 characters. - Set your own passwords for all six starting accounts (
SEED_ADMIN_PASSWORD…SEED_CASHIER_PASSWORD), or turn offAUTO_SEEDand create real staff accounts yourself. - Set
CORS_ORIGINto the restaurant’s real address instead of*. - Keep the database file and
.envon the restaurant’s machine — never upload or share them. SetBACKUP_COPY_DIRto another disk and try one restore before opening, as in Back up and restore. - Don’t use the one-line Windows installer for a real shop: it is set up for trying things out.
- If you connect PaynEat ERP, use its
https://address. The POS never sends its credential overhttp://except to the same machine, andERP_ALLOW_INSECURE_HTTPis only for a closed network. If the ERP’s certificate comes from the chain’s own CA, see “PaynEat ERP’s HTTPS certificate isn’t trusted” under troubleshooting.
Stuck? Tap the problem
It says "port is already allocated"
Something else is using port 3000 or 8080. Close it (or a PaynEat you left running) and try again.
It says "Docker Desktop is not running" or "is not installed"
Install Docker Desktop from docker.com if needed, open it, wait for Engine running in the bottom-left corner, then paste the line again.
It says "PaynEat started but is not answering yet"
It is up but slow to answer. Wait a moment and open http://localhost:8080; if it still doesn’t open, check Docker Desktop → Containers → payneat.
Docker sits on the Flutter step for ages
The first time downloads the Flutter SDK, about 2 GB. Let it finish (5–10 minutes); next time it’s cached.
The page opens but sign-in fails
Open http://localhost:3000/health; if nothing answers, the server isn’t running. If you set API_BASE_URL, check the IP and run docker compose up --build again after changing it.
A phone or tablet can’t connect
It has to be on the server’s Wi-Fi, use the IP rather than localhost, and the firewall must allow ports 8080 and 3000.
Docker says "required variable JWT_SECRET is missing a value"
The server secret is not set yet. Create the .env file next to docker-compose.yml as in the "Set the server secret" step of way 3, then run it again.
It says JWT_SECRET must be set
There is no .env yet: run cp .env.example .env in backend (Windows: copy), then npm run dev again.
Settings say “PaynEat ERP’s address must start with https://”
The POS sends its credential only over https://. Ask the ERP administrator for the ERP’s https:// address. A http:// address works only for an ERP on the same machine (localhost). If you connected over http:// before this update, the POS has stopped sending the credential: save the https:// address with the credential in Settings.
Settings say “PaynEat ERP’s HTTPS certificate isn’t trusted”
The POS server doesn’t trust the certificate authority that issued the chain’s ERP certificate. Ask the ERP administrator for the CA file (PEM), and check it first: curl --cacert chain-ca.pem https://your-erp-address/health must answer "status":"ok". Then restart the POS server with NODE_EXTRA_CA_CERTS=/path/to/chain-ca.pem in its environment (putting it in backend/.env has no effect: Node reads it at startup, before that file). Docker: mount the CA file into the api container and set it in docker-compose.override.yml. Never turn certificate checks off.
npm install fails on better-sqlite3
Build tools are missing. macOS: xcode-select --install · Linux: sudo apt install build-essential python3 · Windows: install Visual Studio Build Tools.
I want to start over with fresh data
Way 2: double-click Reset PaynEat data · way 3: docker compose down -v · way 4: cd backend && npm run db:reset
Still stuck?Ask on GitHub Issues or LinkedIn — say which way you used, your operating system and the message on screen.