Deployment
Deployment
This chapter covers production deployment of CSMS. CSMS uses SQLite, which does not support concurrent multi-process writes, so every option keeps the server single-process.
Deployment Architecture
User ──▶ Nginx (:80 / :443) ──▶ Nuxt Server (:3000) ──▶ SQLite (/app/data/*.db)
HTTPS termination / single process physically isolated
static cache (SQLite limit) multi-tenant DBsRationale:
- SQLite needs no separate database server, suitable for small/medium schools.
- Because SQLite doesn't support concurrent multi-process writes, the Nuxt Server must run single-process (
ecosystem.config.cjsfixesinstances: 1— do not switch to cluster). - Nginx handles HTTPS termination, static caching, compression and security headers.
Environment Variables (all options)
| Variable | Required | Notes |
|---|---|---|
NUXT_SESSION_PASSWORD | ✅ | Session encryption key, effective at runtime. Generate: openssl rand -hex 32 |
SESSION_SECRET | ⚪ | Build-time fallback; runtime change has no effect |
HOST | ⚪ | Default :: |
PORT | ⚪ | Default 3000 |
NODE_ENV | ⚪ | Set to production for deployment |
⚠️
NUXT_SESSION_PASSWORDmust be set in the runtime environment even on Windows with a prebuilt artifact.
Option 1: Docker (Recommended)
Prerequisites: Docker and Docker Compose installed.
docker compose up -d --build # app + optional Nginx
docker compose up -d --build csms # app only
docker compose logs -f csms # view logs- App port
3000, Nginx port80. - HTTPS config under
deploy/nginx/. - Data volume
csms-datamounted to container/app/data— persist this volume. - The first super admin is created during the onboarding flow after first start.
Option 2: PM2 (Recommended for single host)
Prerequisites: Node.js 22+, npm i -g pm2.
npm install
npm run build
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup # auto-start on boot (run the printed command)If customizing ecosystem.config.cjs:
module.exports = {
apps: [{
name: "csms",
exec_mode: "fork", // ❗ no cluster, SQLite can't do concurrent writes
instances: 1, // ❗ must be 1
script: ".output/server/index.mjs",
env: { NUXT_SESSION_PASSWORD: "your-key" }
}]
};Nginx reverse proxy (PM2) target: http://127.0.0.1:3000.
Option 3: Windows
npm i -g pm2
npm install
npm run build
$env:NUXT_SESSION_PASSWORD = "your-key"
pm2 startup windows
pm2 start ecosystem.config.cjs
pm2 save
netsh advfirewall firewall add rule name="CSMS" dir=in action=allow protocol=TCP localport=3000Option 4: Baota Panel
- Install PM2 Manager and Nginx from the app store.
- Upload project to
/www/wwwroot/csms, thennpm install && npm run build. - PM2 start file:
.output/server/index.mjs(orpm2 start ecosystem.config.cjs). - Add a site, reverse proxy target
http://127.0.0.1:3000. - One-click SSL (Let's Encrypt) for the site.
- Backup by copying
/www/wwwroot/csms/data/.
HTTPS / SSL
- Docker: configure cert paths and 443 listener under
deploy/nginx/; Nginx terminates TLS. - PM2 / Baota: use Certbot or the panel to obtain a cert; Nginx redirects 80 → 443 and proxies to
127.0.0.1:3000.
PWA "Install to home screen" requires HTTPS.
Database Backup & Restore
Manual backup
cp data/csms.db /backup/csms.db
cp -r data/schools /backup/schoolsAutomatic backup (cron, daily 03:00)
# 0 3 * * * /usr/bin/rsync -a /app/data/ /backup/csms-data/ (Docker)To restore: stop the service → overwrite data/ with the backup → restart.
Troubleshooting
| Symptom | Possible Cause | Fix |
|---|---|---|
| Container won't start | Port in use / volume permission | Free port, check csms-data mount permission |
| Redirected back after login | NUXT_SESSION_PASSWORD mismatch / unset | Ensure runtime key matches build |
database is locked | Multi-process SQLite write | Confirm instances: 1, no cluster |
| Disk full | Audit logs / uploads pile up | Clean data/ and logs, expand disk |
| Broken after update | Migration not run | npm run db:migrate then restart |
Deployment Checklist
Default Admin
The first super admin is created via the school onboarding flow after first start. If you forget the super admin password, use "Forgot password" on the login page (email code), or have another super admin reset it via "User Management → Admins".
