40227-vm/docs/deployment-docker.md
2026-06-12 06:55:35 +02:00

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

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.

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_me
  • DB_* point to the db service (Postgres 16, DB/user app_local)
  • Seed passwords are hardcoded in the seeder (see CLAUDE.md for 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_KEY and ALLOWED_ORIGINS are required, and cookies have the Secure flag (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.