4.0 KiB
Docker Deployment (On Host)
Alternative way to run the project for local stack and portable deployment. On the production Flatlogic VM Docker is not used — there it's PM2 + local PostgreSQL
- Cloudflare tunnel (see
deployment-vm.md).
The project consists of two applications:
frontend/— Vite + React + TypeScript (SPA). Build output →frontend/dist/.backend/— Express + Sequelize on TypeScript/ESM. Build output →backend/dist/.
Files
In the repository root:
| File | Purpose | Port |
|---|---|---|
Dockerfile |
production single-image: compiled backend serves API and SPA (from public) |
8080 |
Dockerfile.dev |
staging VM replica: nginx + frontend vite preview (3001) + backend (3000), dev_stage |
8080 |
docker/docker-compose.yml |
local stack: PostgreSQL + app (from Dockerfile), NODE_ENV=development |
8080 |
All stages use node:24-alpine, npm ci. Native bcrypt is compiled with the toolchain
(python3 make g++) in the builder stage; the compiled node_modules is copied to runtime
(without recompilation). rolldown (Vite bundler) has musl bindings, so alpine works.
1. Quick Start — docker compose (Recommended)
Starts PostgreSQL + application with a single command. Backend in development serves API
and SPA on the same port (login works over http).
cd docker
docker compose up --build
# open http://localhost:8080
Parameters (env vars set in docker-compose.yml, modify as needed):
SECRET_KEY=local_dev_secret_change_meDB_*point to thedbservice (Postgres 16, DB/userapp_local)- Seed passwords are hardcoded in the seeder (see
CLAUDE.mdfor credentials)
Stop and remove (including DB data):
docker compose down -v
2. Production Single-Image (Dockerfile)
One container: compiled backend on NODE_ENV=production listens on 8080 and
serves both /api and the built SPA (frontend is placed in public).
docker build -t schoolchain:prod .
docker run --rm -p 8080:8080 \
-e NODE_ENV=production \
-e PORT=8080 \
-e SECRET_KEY=<secret> \
-e ALLOWED_ORIGINS=https://<your-domain> \
-e DB_HOST=<host> -e DB_PORT=5432 -e DB_NAME=<db> -e DB_USER=<user> -e DB_PASS=<pass> \
schoolchain:prod
In
NODE_ENV=production,SECRET_KEYandALLOWED_ORIGINSare required, and cookies have theSecureflag (HTTPS frontend needed in front of the container). The startup command (npm run start:production) automatically runs migrations and seeders — the DB must be accessible.
3. Staging VM Replica (Dockerfile.dev)
Replicates the VM setup in one image: nginx (8080) → frontend vite preview (3001) + backend
(3000), NODE_ENV=dev_stage, source maps. Run behind HTTPS (in dev_stage
cookies are Secure).
docker build -t schoolchain:staging -f Dockerfile.dev .
docker run --rm -p 8080:8080 \
-e SECRET_KEY=<secret> \
-e DB_HOST=<host> -e DB_PORT=5432 -e DB_NAME=<db> -e DB_USER=<user> -e DB_PASS=<pass> \
schoolchain:staging
4. .dockerignore
Excludes node_modules, dist, public, **/.env (to avoid baking dev secrets
into the image — environment is set at runtime), .git, logs.
5. NODE_ENV — What to Choose
NODE_ENV |
Where | Characteristics |
|---|---|---|
development |
local docker compose |
http login works; no ALLOWED_ORIGINS requirement; cookie without Secure |
dev_stage |
Dockerfile.dev (staging) |
production-like but lenient; requires HTTPS (Secure cookie); reflects origin |
production |
Dockerfile (prod) |
requires SECRET_KEY + ALLOWED_ORIGINS; Secure cookie; strict CORS |
6. Scheduled maintenance: refresh-token cleanup
Run the retention cleanup periodically (host cron or a scheduled one-off container), e.g. daily:
docker exec <backend-container> npm run db:cleanup-tokens:prod
Deletes refresh-token rows past AUTH_REFRESH_TOKEN_RETENTION_MS (default 7
days); idempotent and session-safe. See backend/docs/cookie-auth.md.