Kaniko builds failed with context deadline exceeded pulling node:24-alpine from index.docker.io through the egress proxy. Seed node:24-alpine into abrban/ and pass BASE_IMAGE build-arg so builds use the internal registry. Co-authored-by: Cursor <cursoragent@cursor.com>
☁️ CloudHost — Self-Service PaaS Platform
A self-service Platform-as-a-Service (PaaS) that lets developers deploy applications onto Kubernetes with zero DevOps overhead. Source code is turned into a container image inside the cluster with Kaniko (no Docker daemon), then rolled out with Helm — complete with managed databases, wallet-based billing, automated lifecycle management, live logs, and a bilingual (Persian/English) panel.
Supported runtimes — each built from a platform-maintained Dockerfile template:
Node.js, Laravel, Go, PHP, Python, Django, .NET, and WordPress (official image +
custom wp-content entrypoint).
🇮🇷 Production deployment on the
abrban.comk3s cluster — including all the Iran-network workarounds — is documented step-by-step in RUNBOOK.fa.md (Persian).
Architecture Overview
┌──────────────┐ REST ┌──────────────────┐ K8s API ┌──────────────┐
│ Next.js 16 │ /api/v1 │ NestJS 11 API │ + Helm │ Kubernetes │
│ Frontend │◄─────────►│ Backend │◄───────────►│ Cluster(s) │
└──────────────┘ └────────┬─────────┘ └──────┬───────┘
│ │ build Jobs
┌─────────────────┼─────────────────┐ ▼
▼ ▼ ▼ ┌──────────┐
PostgreSQL Redis Registry │ Kaniko │
16 (cache + Bull) (:2) └──────────┘
| Layer | Technology |
|---|---|
| Frontend | Next.js 16 (App Router, SSR), React 19, Tailwind CSS v4, React Query, Zustand |
| Backend API | NestJS 11, TypeORM, Passport JWT, Bull (Redis) |
| Build engine | Kaniko (daemon-less in-cluster builds) with platform-generated per-runtime Dockerfiles |
| Source ingestion | Uploaded archive (zip/tar.gz) streamed into a build PVC, or git clone |
| Deployment | Helm v3 charts, @kubernetes/client-node |
| Database | PostgreSQL 16 (control plane); per-app MySQL/MariaDB/PostgreSQL/MongoDB |
| Queue / cache | Redis 7 + Bull (service-access grants, app migrations) |
| Registry | In-cluster registry:2 |
| Auth | Mobile number + OTP (SMS) and password, JWT access/refresh |
📖 See ARCHITECTURE.md for detailed system design. 🔼 See UPGRADE.en.md (فارسی) for dependency-upgrade notes. 📋 See RUNBOOK.en.md (فارسی) for operations.
Features
For Developers
- 🚀 Deploy from a code archive (zip/tar.gz) or a git URL (public or private via token)
- 🟢 Multi-runtime — Node.js, Laravel, Go, PHP, Python, Django, .NET, each built from a maintained Dockerfile template
- 🔵 WordPress — official image + custom entrypoint that merges your
wp-content - 🗄️ Managed databases & services — PostgreSQL, MySQL, MariaDB, MongoDB, Redis, RabbitMQ provisioned via Helm
- 💰 Wallet system — deposit funds, pay per plan (hourly / monthly / yearly), coupons & discounts
- 📊 Live build & runtime logs (Elasticsearch-backed) + deployment history with rollback
- 🔒 Environment variables stored as Kubernetes Secrets
- ⚙️ Resource controls — CPU, memory, replicas, expandable disk
- 🌐 Custom domains with automatic TLS
- 📸 Snapshots — backup & restore application state
- 🎫 Support tickets with technical/sales departments
For Super Admins
- 🖥️ Multi-cluster management — register/remove Kubernetes clusters (kubeconfig stored encrypted)
- 👥 User management — activate, deactivate, change roles, per-user detail dashboard
- 📈 Quotas & pricing — per-cluster limits and a configurable pricing catalog
- 💳 Billing oversight — transactions, invoices, wallet deposits, global discount
- ⏱️ Lifecycle settings — grace periods per billing cycle
- 🔐 RBAC — role-based guards on every endpoint (
user/admin/technical/sales)
How the Build & Deploy Pipeline Works
When a user triggers a deploy, the backend runs the build-and-deploy pipeline and creates
the build as a Kubernetes Job in the cloudhost-builds namespace:
1. SOURCE
├─ uploaded archive → saved to disk (UPLOAD_DIR) → streamed into a per-build PVC
│ via a short-lived helper pod + `kubectl cp`, then unpacked (init: prepare source)
└─ git URL → cloned in-pod; private repos inject the token into the clone URL (init: git-clone)
2. DOCKERFILE
The platform detects the runtime (or uses the app's selected runtime) and generates a
Dockerfile for it — Node.js, Laravel, WordPress, Go, PHP, Python, Django, or .NET.
3. BUILD (container: kaniko)
Kaniko builds the image (layer cache per user) and pushes it to the in-cluster
registry — no Docker daemon, no privileged pod.
4. DEPLOY
Helm installs/upgrades the `cloudhost-app` chart → Deployment, Service, Ingress,
per-app DB/Redis/RabbitMQ, PVCs, Secrets, log shipper. App goes live at its subdomain.
The build runs inline within the deploy request and its progress/logs are tracked in memory, then polled by the frontend. (Bull/Redis queues are used elsewhere — service-access grants and application migrations — but not for image builds.)
Project Structure
cloud-host/
├── README.md # This file
├── ARCHITECTURE.md # Detailed system design
├── RUNBOOK.fa.md # Persian runbook: local dev + abrban/k3s production deploy
├── CHANGELOG.md / CONTRIBUTING.md / UPGRADE.md / UPGRADE.en.md
├── docker-compose.yml # Local dev stack (Postgres + Redis + API + UI)
│
├── backend/ # NestJS 11 API (REST under /api/v1)
│ ├── Dockerfile
│ ├── helm/
│ │ ├── cloudhost-platform/ # Helm chart — control plane (API, UI, Postgres, Redis)
│ │ ├── cloudhost-app/ # Helm chart — a single user application + its services
│ │ └── cloudhost-logging/ # Helm chart — Elasticsearch / Kibana / Fluent-bit
│ ├── k8s/ # Standalone manifests (logging, mail)
│ ├── migrations/ # SQL migrations (applied via one-off Jobs in prod)
│ └── src/
│ ├── main.ts / app.module.ts
│ ├── auth/ # Mobile-OTP + password login, JWT strategies, role guards
│ ├── users/ # User CRUD, profile, phone verification
│ ├── admin/ # Super-admin user-detail dashboard & ops
│ ├── applications/ # App CRUD, code upload (→ disk), git config
│ ├── application-migrations/ # Import/migrate existing apps (Bull queue)
│ ├── deployments/ # Deploy orchestration, history, stop/restart
│ ├── build/ # Kaniko build (build.service): per-runtime Dockerfile generation
│ ├── kubernetes/ # K8s client, Helm wrapper, registry service
│ ├── clusters/ # Multi-cluster management, kubeconfig storage
│ ├── billing/ # Wallet, transactions, invoices, pricing catalog, coupons
│ ├── lifecycle/ # Scanner: auto-suspend/delete expired apps
│ ├── snapshots/ # App snapshot/restore
│ ├── tickets/ # Support tickets
│ ├── notifications/ # User notifications
│ ├── access/ # Time-limited external service access (NodePort grants, Bull queue)
│ ├── common/ # Enums, guards, decorators
│ └── config/ # Env configuration loader + TypeORM config
│
├── frontend/ # Next.js 16 App Router (bilingual fa-IR / en-US)
│ ├── Dockerfile # ARG NEXT_PUBLIC_API_URL baked at build time
│ └── src/
│ ├── middleware.ts # Locale routing + landing (abrban.com) vs panel split
│ ├── app/[lang]/
│ │ ├── page.tsx # Landing
│ │ ├── login/ register/
│ │ ├── blog/
│ │ └── dashboard/ # apps, deploy, logs, invoices, wallet, services,
│ │ │ # tickets, account, staff, admin
│ │ └── ...
│ ├── components/ hooks/ lib/ (API client, auth store) types/
│ └── i18n/ # Dictionaries, provider, language switcher
Quick Start (Local Development)
Prerequisites: Node.js ≥ 20, Docker & Docker Compose, and (for actually building/deploying user apps) a Kubernetes cluster reachable via kubeconfig.
ℹ️ The API and UI run fine locally against Postgres + Redis. The build/deploy pipeline itself runs as Kubernetes Jobs, so triggering a real user-app build requires a cluster (with the in-cluster registry). For pure UI/API development you don't need one.
1. Clone & install
git clone <repo-url> cloud-host && cd cloud-host
cd backend && npm install && cd ..
cd frontend && npm install && cd ..
2. Environment variables
cp backend/.env.example backend/.env
cp frontend/.env.local.example frontend/.env.local
# Edit both — at minimum DB, JWT, Redis. See the Configuration table below.
3. Start Postgres + Redis
docker compose up -d postgres redis
4. Run the apps
# Terminal 1 — Backend (http://localhost:4000, prefix /api/v1, Swagger at /docs)
cd backend && npm run start:dev
# Terminal 2 — Frontend (http://localhost:3000)
cd frontend && npm run dev
In development NODE_ENV=development, so TypeORM synchronize builds the schema
automatically and the pricing catalog self-seeds. Set frontend NEXT_PUBLIC_API_URL
to the backend URL.
To run the whole stack (API + UI + Postgres + Redis) in containers instead:
docker compose up --build(backend on:4000, frontend on:3000).
Deploy on Kubernetes (Helm)
This is the generic path. For the production
abrban.comk3s cluster — base-image mirroring, the Iran-network proxy/npmmirror, the wildcard TLS cert, registry bootstrap, and the exact image-build flow — follow RUNBOOK.fa.md.
Prerequisites: a Kubernetes cluster, an Ingress controller (Traefik on k3s by default,
or set INGRESS_CLASS=nginx), a default StorageClass for PVCs, and a container registry
reachable by the cluster.
1. Build & push the platform images
export REG=your-registry.example.com
docker build -t $REG/cloudhost-backend:1.0.0 ./backend
docker build -t $REG/cloudhost-frontend:1.0.0 \
--build-arg NEXT_PUBLIC_API_URL=https://api.platform.example.com ./frontend
docker push $REG/cloudhost-backend:1.0.0
docker push $REG/cloudhost-frontend:1.0.0
2. Install the control plane
cp backend/helm/cloudhost-platform/values-production.example.yaml my-values.yaml
# Edit my-values.yaml: image tags, ingress hosts, postgres password, jwtSecret, registry, SMS/OTP
helm upgrade --install cloudhost ./backend/helm/cloudhost-platform \
-n cloudhost --create-namespace \
-f my-values.yaml \
--set images.backend.tag=1.0.0 \
--set images.frontend.tag=1.0.0
Key values: ingress.enabled, ingress.tls.*, ingress.frontend.host, ingress.api.host,
postgres.password, secrets.jwtSecret, migrations.enabled. Chart defaults live in
backend/helm/cloudhost-platform/values.yaml; post-install notes via
helm get notes cloudhost -n cloudhost.
3. Cluster-side prerequisites for the build pipeline
Ensure the cloudhost-builds namespace has:
- the in-cluster registry (
registry:2) reachable atREGISTRY_URL, - a
kaniko-builderServiceAccount with animagePullSecretfor the registry, - enough ephemeral storage for the per-build source PVC + helper pod.
4. Verify
kubectl get deploy -n cloudhost # backend & frontend 1/1
helm status cloudhost -n cloudhost # STATUS: deployed
curl -s -o /dev/null -w '%{http_code}\n' https://<frontend.host>
Configuration (key env vars)
| Variable | Description | Default |
|---|---|---|
PORT |
Backend port | 4000 |
DB_HOST / DB_PORT / DB_USERNAME / DB_PASSWORD / DB_DATABASE |
PostgreSQL connection | localhost / 5432 / cloudhost / — / cloudhost |
JWT_SECRET / JWT_EXPIRES_IN |
Access token secret + TTL | — / 1h |
JWT_REFRESH_SECRET / JWT_REFRESH_EXPIRES_IN |
Refresh token secret + TTL | — / 7d |
REDIS_HOST / REDIS_PORT |
Redis (cache + Bull queues) | localhost / 6379 |
SMS_PROVIDER |
OTP provider (mizbansms | kavenegar) |
mizbansms |
MIZBANSMS_USERNAME / MIZBANSMS_PASSWORD / MIZBANSMS_FROM |
OTP SMS credentials (required or OTP send 503s) | — |
REGISTRY_URL / REGISTRY_PULL_URL |
In-cluster registry (push / pull) | registry.cloudhost-builds.svc.cluster.local:5000 |
BUILD_NAMESPACE / BUILD_SERVICE_ACCOUNT |
Build Jobs namespace + SA | cloudhost-builds / kaniko-builder |
KANIKO_IMAGE |
Kaniko executor image | gcr.io/kaniko-project/executor:v1.23.2 |
UPLOAD_DIR |
Disk path for uploaded source archives | ./uploads |
INGRESS_CLASS |
Ingress controller for app Ingress objects | traefik |
PLATFORM_DOMAIN / PREVIEW_BASE_DOMAIN |
Base domain for app subdomains / previews | apps.cloudhost.local / — |
PLATFORM_STORAGE_CLASS |
StorageClass for new PVCs (needs volume expansion) | cloudhost-expandable |
ELASTICSEARCH_HOST / ELASTICSEARCH_PORT |
Log search backend | cluster DNS / 9200 |
LIFECYCLE_SCAN_INTERVAL_MS |
Lifecycle scanner tick | 60000 |
Authentication
Login is mobile-number based: the user receives a one-time SMS code (OTP) and can also
set a password. On every request JwtStrategy re-reads the user's role and active status
from the database (not from the token), so promotions/deactivations take effect immediately.
Tokens: JWT access (JWT_EXPIRES_IN, default 1h) + refresh (7d).
API
All endpoints are prefixed with /api/v1. Interactive Swagger docs at
http://localhost:4000/api/docs. Major route groups: auth (OTP request/verify, login,
refresh), applications, deployments, clusters, billing (wallet, invoices,
transactions, pricing), snapshots, tickets, users, admin, notifications.
Security
- JWT access + refresh tokens; live role/active-status enforcement from DB
- Bcrypt password hashing
- Helmet HTTP security headers, class-validator on all DTOs
- RBAC role-based route guards (
@Roles(...)) - Namespace isolation — each user deploys to their own Kubernetes namespace
- Secrets — env vars stored as K8s Secrets
License
MIT