Self-hosted workspace booking — desks, assets, and floor plans, fully under your control.
Roomer is a self-hosted platform for managing desk and asset reservations across offices, buildings, and floors. Upload floor plans, place bookable assets on a canvas, and let your team book space — without handing your occupancy data to a third-party SaaS.
| Floor plan editor | Upload image, PDF or DXF floor plans and place assets on an interactive canvas |
| Desk & asset booking | Book by date with live availability (available, booked, assigned, queued, restricted) |
| Permanent assignments | Assign desks to users with primary/secondary ownership; bulk-assign via CSV |
| Zone management | Group assets into colour-coded zones with optional conflict rules |
| Building & floor management | Building admins and floor managers for delegated admin without full super-admin access |
| Queue & waitlist | Users join a waitlist and are automatically promoted when a desk frees up |
| Bulk CSV import | Import buildings, floors, zones, and assets in a single pass |
| Asset registry | Track non-bookable inventory (laptops, monitors, etc.) alongside bookable space |
| Role-based access | Super admin, building admin, floor manager, and user roles |
| Multi-provider login | Local, LDAP, OIDC, and SAML — mix providers with a configurable default and URL overrides |
| SCIM 2.0 provisioning | Automated user and group sync from Okta, Azure AD / Entra ID, OneLogin, etc. |
| Email notifications | Booking confirmations and queue promotions via SMTP |
The fastest way to run Roomer is with the pre-built Docker images.
1. Create a .env file in the project root:
SESSION_SECRET=<run: openssl rand -hex 32>
APP_ORIGIN=http://localhost
COOKIE_SECURE=false
WEB_PORT=80
EMAIL_FROM=noreply@roomer.local
# Admin account credentials (see "Seeding" below)
SEED_ADMIN_EMAIL=admin@roomer.local
SEED_ADMIN_PASSWORD=changeme2. Start everything:
docker compose up -dThe web app will be at http://localhost (or WEB_PORT if changed). The API container automatically runs prisma migrate deploy on startup, then seeds the admin account on first run.
Find the admin password: the seed step prints it to the API logs:
docker compose logs api | grep "seed"If SEED_ADMIN_PASSWORD is not set, a random password is generated and logged there. Set it in your .env to use a known password.
3. (Optional) Seed demo buildings and desks:
Add SEED_DEMO_DATA=true to your .env (before first start, or re-run the seed manually):
docker compose exec api npx prisma db seedThis adds a sample building ("Acme HQ") with a floor, two zones, and six desks, plus a regular test user (user@roomer.local). Change all passwords after first login.
| Variable | Default | Description |
|---|---|---|
SESSION_SECRET |
— | Required. 32+ character random string. |
APP_ORIGIN |
http://localhost |
Public URL used in CORS and email links. |
COOKIE_SECURE |
false |
Set to true when serving over HTTPS. |
WEB_PORT |
80 |
Host port for the web container. |
EMAIL_FROM |
noreply@roomer.local |
Sender address for system emails. |
SEED_ADMIN_EMAIL |
admin@roomer.local |
Email for the super-admin account created on first seed. |
SEED_ADMIN_PASSWORD |
(random) | Password for the super-admin account. If unset, a random password is generated and printed to the API container logs. |
SEED_DEMO_DATA |
false |
Set to true to seed demo buildings, floors, desks and a test user on first start. |
SEED_USER_PASSWORD |
(random) | Password for the demo test user (user@roomer.local). Only used when SEED_DEMO_DATA=true. |
docker compose -f docker-compose.build.yaml up --build| Image | Description |
|---|---|
c0dewhacker/roomer-api |
Multi-stage Node 24 Alpine. Runs migrations then starts Fastify. |
c0dewhacker/roomer-web |
Multi-stage Vite build served by nginx 1.27. Proxies /api to the API. |
Both images are built from the monorepo root so they can access packages/shared.
| Layer | Technology |
|---|---|
| API | Fastify + TypeScript |
| Database | PostgreSQL 18 + Prisma ORM |
| Frontend | React 18 + Vite + Tailwind CSS + shadcn/ui |
| Canvas | Konva (react-konva) |
| Job queue | pg-boss (PostgreSQL-backed) |
| Monorepo | pnpm workspaces + Turborepo |
git clone https://github.com/c0dewhacker/Roomer.git roomer
cd roomer
pnpm installdocker compose up -dThis starts PostgreSQL and Mailpit (local SMTP catcher for dev email):
| Service | URL | Notes |
|---|---|---|
| PostgreSQL | localhost:5435 |
User/pass/db: roomer |
| Mailpit SMTP | localhost:1025 |
Dev email relay |
| Mailpit Web | http://localhost:8025 |
View outbound email |
Create apps/api/.env:
# Required
DATABASE_URL=postgresql://roomer:roomer@localhost:5435/roomer
SESSION_SECRET=<openssl rand -hex 32>
# Defaults — override as needed
NODE_ENV=development
PORT=3001
CORS_ORIGIN=http://localhost:5173
# Email (matches Mailpit defaults above)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_SECURE=false
EMAIL_FROM=noreply@roomer.local
APP_URL=http://localhost:5173
# Seed credentials — set these before running db:seed so you know the password.
# If SEED_ADMIN_PASSWORD is omitted, a random password is generated and printed to stdout.
SEED_ADMIN_EMAIL=admin@roomer.local
SEED_ADMIN_PASSWORD=admin123
SEED_DEMO_DATA=truepnpm --filter api db:migrate # apply migrations
pnpm --filter api db:seed # seed demo dataWith the .env values above, the seed creates:
| Resource | Value |
|---|---|
| Admin login | admin@roomer.local / admin123 (from your .env) |
| Organisation | Acme Corp (renamed when SEED_DEMO_DATA=true) |
| Building | Acme HQ with Ground Floor — 6 sample desks across two zones |
| Test user | user@roomer.local (password from SEED_USER_PASSWORD, or logged if unset) |
Without SEED_DEMO_DATA=true, only the admin account and a blank organisation ("My Organisation") are created.
pnpm dev # both apps via Turborepo
# or individually:
pnpm --filter api dev # API → http://localhost:3001
pnpm --filter web dev # Web → http://localhost:5173Open http://localhost:5173 and sign in with admin@roomer.local / admin123.
The Docker images are production-ready as-is. Key checklist:
- Set
COOKIE_SECURE=trueand serve over HTTPS - Set
APP_ORIGINto your public domain (e.g.https://roomer.example.com) - Set
API_PUBLIC_URLto your public API URL if different fromAPP_ORIGIN(used for SCIM endpoint links) - Use a managed PostgreSQL instance by overriding
DATABASE_URLin the API environment - Mount a persistent volume at
/app/uploadsfor floor plan storage (already indocker-compose.yml) - For horizontal API scaling, point
FILE_STORAGE_PATHat shared network storage rather than a local volume
The REST API runs on port 3001 by default.
- Swagger UI:
http://localhost:3001/docs(enabled in development; setSWAGGER_ENABLED=trueto enable in production) - All routes are prefixed
/api/v1
| Prefix | Description |
|---|---|
/api/v1/auth |
Login, logout, session, enterprise SSO (OIDC/SAML/LDAP) |
/api/v1/buildings |
Buildings CRUD, managers, access groups |
/api/v1/floors |
Floors, zones, floor plan upload, availability |
/api/v1/assets |
Asset registry, bookable desks, user assignments, bulk assignment CSV |
/api/v1/bookings |
Booking lifecycle |
/api/v1/queue |
Waitlist management |
/api/v1/import/bulk |
Global CSV bulk import |
/api/v1/users |
User management |
/api/v1/groups |
Group management |
/api/v1/settings |
Organisation settings, auth provider config, SCIM provisioning |
/scim/v2 |
SCIM 2.0 provisioning (separate prefix, bearer token auth) |
All API environment variables:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
— | PostgreSQL connection string (required) |
SESSION_SECRET |
— | Cookie signing secret, min 32 chars (required) |
PORT |
3001 |
API listen port |
HOST |
0.0.0.0 |
API bind address |
CORS_ORIGIN |
http://localhost:5173 |
Allowed frontend origin |
COOKIE_SECURE |
false in dev |
Require HTTPS for session cookies |
TRUST_PROXY |
false in dev |
Trust X-Forwarded-For headers |
ALLOW_BEARER_AUTH |
true in dev |
Accept Authorization: Bearer tokens |
SWAGGER_ENABLED |
true in dev |
Expose Swagger UI at /docs |
FILE_STORAGE_PATH |
./uploads |
Directory for floor plan images |
MAX_FILE_SIZE_MB |
20 |
Maximum upload size |
API_PUBLIC_URL |
http://localhost:3001 |
Public URL of the API — shown in the SCIM settings panel as the endpoint base URL |
SMTP_HOST |
localhost |
Outbound email relay host |
SMTP_PORT |
1025 |
Outbound email relay port |
SMTP_SECURE |
false |
Use TLS for SMTP |
SMTP_USER |
— | SMTP username (if required) |
SMTP_PASS |
— | SMTP password (if required) |
EMAIL_FROM |
noreply@roomer.local |
Sender address for system emails |
APP_URL |
http://localhost:5173 |
Public URL used in email links |
| Role | Scope | Permissions |
|---|---|---|
SUPER_ADMIN |
Global | Full access to all resources and settings |
BUILDING_ADMIN |
Per building | Superset of floor manager for every floor in the building (full implementation in progress) |
FLOOR_MANAGER |
Per floor | Manage zones, assets, and bookings on assigned floors |
USER |
Global | Book available assets, manage own bookings |
Run from the monorepo root:
| Command | Description |
|---|---|
pnpm dev |
Start all apps in development mode |
pnpm build |
Build all apps for production |
pnpm lint |
Lint all packages |
pnpm --filter api db:migrate |
Run pending Prisma migrations |
pnpm --filter api db:seed |
Seed demo data |
pnpm --filter api db:studio |
Open Prisma Studio (database browser) |
roomer/
├── apps/
│ ├── api/ # Fastify REST API
│ │ ├── prisma/ # Schema, migrations, seed
│ │ └── src/
│ │ ├── routes/
│ │ ├── middleware/
│ │ └── lib/
│ └── web/ # React SPA
│ └── src/
│ ├── components/
│ ├── pages/
│ ├── hooks/
│ └── stores/
├── packages/
│ └── shared/ # Shared types and Zod schemas
├── docker-compose.yml # Pull pre-built images
├── docker-compose.build.yaml # Build from source
└── turbo.json
Roomer is licensed under the Elastic License 2.0.
In short: you can use, modify, and self-host Roomer freely — including for commercial internal use. You may not provide Roomer as a managed hosted service to third parties (i.e. you cannot run a Roomer-as-a-Service business).
See the LICENSE file for the full terms.
