v2.0 — Multi-tenant + Docker deploy 🚀
v2.0 — Multi-tenant foundation + Docker deploy story
Major architectural shift: foundation for hosting multiple companies on one HRMS instance + production-grade Docker deployment.
🏢 Multi-tenant routing
- Each company can have a subdomain (e.g. `acme`)
- HRMS at `https://acme.hrms.example.com\` → `Current.company = Company.find_by(subdomain: "acme")`
- `https://hrms.example.com\` → falls back to first company (single-tenant compatibility)
- Subdomain `acme.hrms.example.com` with no matching company → 404 (JSON for API, HTML for browser)
- `IGNORED_SUBDOMAINS = [www, api, app]` → marketing pages keep working
Setting up: superadmin sets the subdomain in Settings → Коммуникации → 🏢 Subdomain компании. Validation: `[a-z0-9-]{1,50}`.
Migration path: 6 hot files (introduced in v1.6+) now use `Current.company` / `current_company` helper. 14 legacy callsites still use `Company.kept.first` — they work via fallback and will migrate incrementally in v2.1.
🐳 Docker deploy
- `scripts/update.sh` — zero-downtime upgrade:
- Backup DB + storage (auto-calls `scripts/backup.sh`)
- `git checkout` to target tag (or latest master)
- `docker compose build` new image
- Migrations run on new image BEFORE container swap (no broken intermediate state)
- Rolling restart of `app` + `worker`
- Healthcheck loop on `/up` for 60s
- On failure: prints rollback command
- `docker-compose.prod.yml` override:
- Pre-built image (`APP_IMAGE=ghcr.io/...`) instead of local build
- Healthcheck on app container
- PostgreSQL tuning: shared_buffers 512MB, work_mem 16MB
- JSON-file logging with rotation
- Puma: 3 workers × 10 threads
Run with: `docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
Migration
- `companies.subdomain` (unique partial index, nullable)
i18n
- +6 keys × 3 locales
Quality
- Zeitwerk: clean
- 120 specs, 0 failures
- Rubocop: 0 offenses on 255 files
Next: v2.1 (incremental tenant refactor)
- Replace remaining 14 legacy `Company.kept.first` callsites
- Test multi-tenant flow end-to-end with two real tenants
- Per-tenant settings ownership (one company can't read another's AppSetting)