Self-hostable deployment platform. One docker compose. Your fleet. Comparable in scope to Coolify / Dokploy, simpler than Vercel.
Status: Phase 1. Auth, organizations, projects, API keys, REST API, RBAC, Solid Queue, React/Inertia dashboard, all running on Postgres. See
docs/PHASE_0.mdfor the design and the Phase 2/3 backlog.
- Backend — Rails 8.1, Ruby 3.4
- Frontend — Inertia.js + React 19 + Tailwind v4
- DB — PostgreSQL 16
- Jobs / cache / websockets — Solid Queue, Solid Cache, Solid Cable (all Postgres-backed, no Redis in Phase 1)
- Bundler — Bun 1.3 (also drives Vite at
:3036) - Auth (web) — Devise (cookie sessions)
- Auth (API) — opaque
klx_…bearer tokens - Authorization — Pundit (role-based per organization)
Prereqs: Docker Desktop (or any Docker ≥ 24 + Compose v2), nothing else.
docker compose up --buildWhat you get:
| Service | Port | What it is |
|---|---|---|
db |
5432 | PostgreSQL 16 |
web |
3000 | Rails (Puma + Inertia + Tailwind + Vite) |
worker |
— | Solid Queue supervisor + workers |
On first boot the entrypoint:
- Waits for Postgres
- Installs gems + JS deps into named volumes (cached across restarts)
db:prepare(loadsdb/schema.rbinto the dev DB) + seeds a demo user- Starts the web (foreman with web + vite + css) and the worker
Visit http://localhost:3000 and sign in with the seeded user:
email: demo@kaelix.local
password: demopass123
Or create a new account from /users/sign_up — your first organization is
created automatically.
docker compose logs -f web # tail Rails logs
docker compose logs -f worker # tail Solid Queue supervisor
docker compose exec web bash # shell into the web container
docker compose exec web bin/rails db:migrate
docker compose exec web bin/rails console
docker compose down # stop (keeps volumes)
docker compose down -v # stop + delete volumes (full reset)Prereqs: Ruby 3.4.9 (via mise/rbenv/asdf), Postgres 16, Node 22+, Bun 1.3+.
bundle install
bun install
bin/rails db:prepare db:seed
bin/devbin/dev is foreman; it runs web (Puma) + vite (HMR) + css
(Tailwind watch). Open http://localhost:3000.
┌──────────────────────────────────────────────┐
│ kaelix (control plane) │
Browser ───► │ Rails 8.1 │
(Inertia) │ ├─ Web (Puma) — Inertia + JSON API │
│ ├─ Worker (Solid Queue) — build/deploy jobs │
CLI ───────► │ └─ Solid Queue supervisor / cron │
(API token) └─────┬───────────────────────────┬──────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ Postgres │ │ Target nodes │
│ (one) │ │ (ssh agents) │
└──────────┘ └──────────────┘
See docs/PHASE_0.md for the full architecture, schema,
API contracts, RBAC matrix, and the Phase 2/3 plan (deployments, servers,
domains, templates, volumes, build workers).
app/
controllers/
application_controller.rb # Inertia + Pundit + shared props
users/ # Devise overrides (Inertia-shaped)
dashboard_controller.rb
organizations_controller.rb
projects_controller.rb
memberships_controller.rb
api_keys_controller.rb
api/v1/ # JSON API (Bearer token auth)
base_controller.rb # ApiKey.authenticate + Pundit
auth_controller.rb # POST /api/v1/auth/login
me_controller.rb # GET /api/v1/me
organizations_controller.rb
projects_controller.rb
memberships_controller.rb
api_keys_controller.rb
models/
user.rb organization.rb membership.rb project.rb api_key.rb
policies/ # Pundit
services/ # PORO service objects (Auth, ApiKey, Membership, Project)
jobs/
application_job.rb
deployments/create_job.rb # Phase 2 stub: logs "would deploy", flips status
projects/ping_job.rb
projects/notify_job.rb
frontend/
entrypoints/inertia.tsx
pages/ # React pages, resolved by Inertia
components/ # Layout, Field, Button
db/
schema.rb # primary + Solid Queue/Cache/Cable tables
seeds.rb # demo user + org + project in development
config/
database.yml # single PG; primary connection
initializers/
cors.rb # public API
devise.rb
inertia_rails.rb
docs/
PHASE_0.md # design doc
docker-compose.yml # db, web, worker
Dockerfile # dev-friendly, ~10s build (deps in entrypoint)
Procfile # web (Puma) + vite + css (Tailwind)
bin/docker-entrypoint # wait-db, bundle, bun, db:prepare, exec
All endpoints under /api/v1/. Bearer token (the klx_… string, shown once
when the key is created) in the Authorization header.
# Login → returns a token
curl -X POST localhost:3000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"demo@kaelix.local","password":"demopass123"}'
# Use the token
curl localhost:3000/api/v1/me -H "Authorization: Bearer klx_…"
curl localhost:3000/api/v1/organizations
curl -X POST localhost:3000/api/v1/organizations/demo-org/projects \
-H "Authorization: Bearer klx_…" -H 'Content-Type: application/json' \
-d '{"project":{"name":"web","repository_url":"https://github.com/me/web","branch":"main"}}'Full contract: docs/PHASE_0.md.
Phase 0 (doc) — planning, schema, API contracts, RBAC matrix.
Phase 1 (this commit) — backend foundation:
- Auth: Devise (web) + bearer tokens (API)
- Organizations, memberships, RBAC (Pundit, 4 roles)
- Projects CRUD (web + API)
- API keys with HMAC-stored secrets, scopes, expiry
- Service objects under
app/services/ - Solid Queue wired (worker container, deployments/create_job stub)
- Inertia/React dashboard for everything above
- One-command Docker bring-up
Phase 2 — deployments: servers, deployments, domains, env_vars,
logs, webhooks, build workers on target nodes via SSH.
Phase 3 — templates, volumes, notifications, audit log UI, billing hooks.
MIT.