快乐 (kuàilè) · happiness, joy
The issue tracker with no paid tier.
Keyboard-driven, self-hosted, and available in one public Apache 2.0 repository.
Kuayle's published releases are runnable MVPs, not a mature enterprise platform. This development branch also contains unreleased work described below. The repository includes the Go API, SvelteKit frontend, database migrations, development tooling, and reference self-hosting configuration.
| Area | State |
|---|---|
| Core tracker | Available: auth, workspaces, RBAC, teams, custom statuses, issues, multiple assignees, labels, comments, history, sub-issues, relations, triage, templates, favorites, saved views, notifications, public sharing, uploads, and WebSocket events. |
| Planning | Implemented: cycles with burndown/velocity charts, project management with Gantt view, and full cycle/project UI. |
| Integrations | Available: workspace webhooks and a GitHub App with repository linking, branch/commit/PR activity, configurable status transitions, and WebSocket refresh events. Private networks require a webhook relay or tunnel. |
| Analytics | Workspace and team overviews, event-based burn-up trends, and configurable issue insights based on current issue data and stored lifecycle timestamps. |
| Dev Machines | Unreleased development-branch functionality implemented as an opt-in self-hosted subsystem: PostgreSQL control plane, multi-container runtime, manager, authenticated gateway, four agent providers, collector, and UI. Disabled by default; see TECHNICAL.md. |
| Self-hosting | Reference Docker Compose stack with Caddy, PostgreSQL, Redis, backend, frontend, an update script, and dedicated config in selfhosting/. |
Kuayle started with a narrow requirement: keep a fast, keyboard-oriented issue workflow while adding multiple assignees, self-hosting, and complete source access.
The distribution model is deliberately simple:
- one public repository under Apache 2.0;
- no paid tier or enterprise edition;
- no feature gate or license key;
- no per-user software fee;
- infrastructure, backups, monitoring, and updates remain the operator's responsibility.
The product is intentionally smaller than broad project-management suites. It covers issues, cycles, projects, saved views, analytics, GitHub automation, public sharing, and real-time events. It does not currently include import/export workflows, enterprise identity, a wiki, or modules.
| Feature | Description | |
|---|---|---|
| 🏢 | Workspaces | Multi-tenant with role-based access (owner, admin, member, guest) |
| 👥 | Teams | Custom workflows, each team gets its own statuses and triage settings |
| 📋 | Issues | Priority, due dates, sub-issues, multi-assignee, labels, comments, audit history |
| 🔗 | Issue Relations | Blocking/blocked, duplicate, and related issue links |
| 🔄 | Cycles | Sprint planning with burndown/velocity charts and time-boxed iterations |
| 📁 | Projects | Cross-team work grouped under a single umbrella with Gantt view |
| 🏷️ | Labels | Hierarchical, workspace-scoped, with soft delete and default labels on creation |
| 👁️ | Views | Saved views with personal/workspace/team scoping, drag-and-drop reorder |
| 🔔 | Notifications | Inbox with snooze, read status, and archive |
| 🔗 | Webhooks | Plug into external services and integrations |
| ⚡ | Real-time | Workspace WebSocket events for issues, comments, cycles, views, GitHub, and presence |
| 🖥️ | Dev Machines | Unreleased opt-in multi-container coding environments with agents, browser access, authenticated routing, and work tracking |
| 🐙 | GitHub | Link repos, match issue IDs in development activity, and apply status rules |
| 📊 | Analytics | Workspace/team overview, burn-up, and configurable insights |
| 🔗 | Public Sharing | Token-based read-only links for issues and views |
| 📦 | Asset Management | File uploads, signed URLs for prompt images, S3-compatible storage |
| ⌨️ | Command Palette | Global search with highlighting, keyboard shortcuts, and quick actions |
| 🎨 | Rich Text Editor | Tiptap-based with code blocks, slash commands, mentions, task lists |
| 🚀 | Release Changelog | Multi-release changelog modal with markdown rendering from static manifest |
Dev Machines are an unreleased opt-in multi-container subsystem for self-hosted Kuayle. Machines can be created generically, then attach issue worktrees when work starts. Every machine has collector and egress services; it may also include a shared code-server/native-terminal developer container and a browser, while agent containers are created on demand for runs. PostgreSQL-backed operations provide durable reconciliation and authenticated wildcard routing.
See
TECHNICAL.mdfor the full specification, architecture diagrams, schema, and API reference.
You (browser)
│
├─ 0123456789abcdef0123.kuayle-machines.example.net → code-server
├─ 0123456789abcdef0123-terminal.kuayle-machines.example.net → native xterm over ttyd.v1
└─ 0123456789abcdef0123-browser.kuayle-machines.example.net → Chrome via KasmVNC (in-browser web navigation)
│
└── All routed through Machine Gateway auth — no public ports, no port management
The implementation:
- Orchestrate cooperating containers per machine. Collector and egress services are always represented. The optional developer service runs code-server on
8080and ttyd on7681in one container (/workspace,HOME, tools, processes, and tmux); browser and on-demand agent services run separately on the isolated bridge network. The Kuayle UI renders@xterm/xtermnatively and speaksttyd.v1; it does not expose ttyd's web page. - Support multiple agent providers — Claude Code, OpenCode, Codex, or admin-configured generic CLIs. The shared developer image includes pinned OpenCode, Claude Code, and Codex CLIs for direct interactive terminal use. Kuayle's dashboard launches bounded autonomous runs in separately pinned provider images and normalizes their results.
- Assign random subdomains through a separate registrable wildcard domain (
*.kuayle-machines.example.net) with launch-ticket auth and host-restricted machine session cookies. The machine domain must be a completely separate registrable domain to prevent cookie leakage between the main application and machine workloads. - Authenticate through a dedicated unprivileged Machine Gateway; the privileged Machine Manager is the only Dev Machines runtime component with Docker socket access. The separately optional system updater also mounts the socket when enabled.
- Prepare issue worktrees from issue, project, team, then workspace repository/environment defaults. One machine has affinity to one repository and can hold several independent issue worktrees from that repository; use a separate machine for another repository/environment or concurrent conflicting work.
- Record best-effort activity from filesystem, shell, root-checkout Git, browser, agent, gateway, and lifecycle telemetry and attach it to project work
| Mode | Who | What happens |
|---|---|---|
| Agent-only | A user starts a bounded run | Agent works autonomously, records a normalized result, and optionally pushes a branch or opens a pull request with a repository-scoped GitHub App token |
| Human + Agent | Developer opens an issue workspace | Uses code-server or the dedicated persistent terminal while launching and monitoring bounded agent runs from Kuayle |
Machines are created without requiring a repository, branch, issue, project, or manual TTL. Friendly random names are case-insensitively unique per creator within a workspace and have an availability API. Opening an issue resolves its repository and environment using issue, project, team, then workspace defaults and creates an isolated Git worktree. Workspace policies control concurrency, maximum hard runtime, a default 240-minute idle pause, per-machine disk size, providers, repositories, and custom CLI access. A per-machine Keep running switch bypasses idle pause; no automatic deletion is performed. Owners and admins can create a writable Environment Builder, pause or stop it, and save selected tooling/home customization as an immutable local OCI Development Environment image.
| Size | CPU | Memory | PID basis | Disk (hard quota) |
|---|---|---|---|---|
| Small | 2 cores | 4 GB | 512 | 20 GB |
| Medium | 4 cores | 8 GB | 512 | 50 GB |
| Large | 8 cores | 16 GB | 512 | 100 GB |
Per-container PID limits are derived from the profile basis; 512 is not one aggregate machine-wide process limit. Per-machine workspace-volume limits use Docker local-volume project quotas. Self-hosted Dev Machines require Docker's data root on XFS mounted with pquota or prjquota; the manager fails its startup quota probe instead of running an unbounded workspace when the host does not support this boundary.
Here's what Kuayle runs on and what each piece does:
| Layer | Tech | Role |
|---|---|---|
| API | Go + Echo | High-performance HTTP server with middleware, routing, JWT auth |
| Database | PostgreSQL 17 | Primary data store, raw SQL via sqlx/pgx, no ORM |
| Redis | Redis 7 | Required configuration; reserved for future cache and job use |
| Frontend | SvelteKit + Svelte 5 | SPA with TypeScript, runes-based reactivity, static adapter |
| UI | Tailwind CSS + shadcn-svelte | Utility-first styling with accessible component primitives |
| Editor | Tiptap v3 + Yjs | Rich text with code blocks, mentions, slash commands, task lists |
| Real-time | WebSocket (nhooyr.io) | Workspace events, issue presence, and targeted notifications |
| Storage | Local FS or S3-compatible | AWS S3, Cloudflare R2, MinIO, SeaweedFS |
| Reverse Proxy | Caddy | Production HTTPS and routing |
| Dev Machines | Docker + Caddy + Go gateway/manager + code-server + ttyd/xterm + Chrome/KasmVNC | Opt-in multi-container development environments with issue worktrees and provider abstraction |
| Infra | Docker + Docker Compose | Local development and reference self-hosting stack |
cp .env.example .env
make docker-upApp at http://localhost:5173 · API at http://localhost:8080
cp .env.example .env
make reset-dev
make seed
make dev-start-services
make devmake dev runs the Go backend on localhost:8080 and Vite on
localhost:5173 directly on the host for hot reload. The root Compose stack is
for supporting services only during local development: Postgres, Redis, the Dev
Machine gateway/manager, runtime image builds, and the Caddy wildcard TLS proxy
for https://*.machines.localhost. The dev proxy binds 127.0.0.1:80 and
127.0.0.1:443; stop the selfhosting stack first if it is using those ports.
The root gateway container is named kuayle-dev-machine-gateway to avoid the
selfhosting kuayle-machine-gateway name.
| Command | What it does |
|---|---|
make dev |
Run backend + frontend (with migrate) |
make dev-start-services |
Start Postgres, Redis, and Dev Machine infrastructure |
make dev-reset |
Reset, seed, start infrastructure, then run host dev |
make dev-backend |
Backend only |
make dev-frontend |
Frontend only |
make dev-full |
Backend + frontend + smee proxy |
make dev-smee |
Start webhook proxy (smee.io) |
make migrate-up |
Apply migrations |
make migrate-down |
Roll back migrations |
make seed |
Seed the database |
make reset-dev |
Reset dev database |
make test |
Run all tests |
make test-backend |
Backend tests only |
make test-frontend |
Frontend tests only |
make lint |
Lint everything |
make docker-up |
Start all (Docker) |
make docker-down |
Stop all (Docker) |
make scan |
Security scan backend + frontend |
Kuayle is designed to be self-hosted. The reference stack in selfhosting/ includes Caddy, PostgreSQL, Redis, the backend, the frontend, and an update script. Review secrets, backups, monitoring, and host security before production use.
- A Linux server with Docker + Docker Compose v2
- A domain pointing to your server (for HTTPS via Let's Encrypt HTTP-01)
- If enabling Dev Machines: a second registrable domain with a wildcard DNS record and either a custom Caddy DNS-01 build or an imported wildcard TLS certificate
git clone https://github.com/carbogninalberto/kuayle.git
cd kuayle/selfhosting
cp .env.example .envEdit .env with your domain and production values:
DOMAIN=kuayle.yourcompany.com
FRONTEND_URL=https://kuayle.yourcompany.com
POSTGRES_PASSWORD=<strong-random-password>
JWT_SECRET=<random-string-at-least-32-chars>FRONTEND_URL must be the exact public browser origin, including scheme and any non-default port. Dev Machine native terminal WebSockets use this value for strict Origin validation.
docker compose up --build -dThis starts 5 containers: Caddy (auto TLS), PostgreSQL, Redis, backend API, and frontend.
docker compose exec backend /app/server migrate up
docker compose exec backend /app/server seedCaddy handles HTTPS termination and proxies /api/* to the backend and all other requests to the frontend, so one public origin is enough.
Dev Machines remain disabled in the default five-service deployment. Before enabling them:
- Set
DEV_MACHINES_ENABLED=true, then configureDEV_MACHINE_DOMAINon a separate registrable domain fromDOMAIN, with wildcard DNS pointing to the host. - Replace the checked-in local
tls internalwildcard configuration with either a mounted wildcard certificate or a DNS-01-enabled Caddy build for production. Profile startup rejects public production machine domains while Caddy internal TLS remains active. The wildcard route already proxies machine HTTP and WebSocket upgrades to the gateway. - Keep
FRONTEND_URLset to the exact public Kuayle origin; the gateway uses it for native terminal WebSocketOriginchecks. - Set
DEV_MACHINE_ENCRYPTION_KEYto an independent random value of at least 32 characters and setDEV_MACHINE_INGEST_URLto the public HTTPS API URL. - Set an independent
DEV_MACHINE_GATEWAY_DB_PASSWORD; Compose provisions a restrictedkuayle_gatewaylogin and the gateway rejects the application database credential in production. - On Linux, set
DEV_MACHINE_DOCKER_GIDto the numeric group owner reported bystat -c '%g' /var/run/docker.sock; Docker Desktop commonly uses the default0. - Build the runtime images, migrate, provision gateway grants, and start the optional control plane:
docker compose --profile dev-machine-images build
docker compose exec backend /app/server migrate up
docker compose --profile dev-machines run --rm machine-gateway-db-provision
docker compose --profile dev-machines up -dThe API rejects a production machine domain that shares the main application's registrable domain. machines.localhost is supported only for local development. The gateway rejects parent-domain and reserved session cookies from workloads. Browser-cookie launches for code-server and browser require exact machine origins for mutations and service WebSockets; native terminal WebSockets use a separate one-use ticket bound to exact FRONTEND_URL, host, user/service, tmux session, and working directory.
For DNS-01, replace the stock caddy:2-alpine image with a trusted custom Caddy build containing your provider module, replace tls internal with a tls block using that DNS provider, and pass its scoped API credential through a Compose override. For operator certificates, bind-mount a host directory read-only at /etc/caddy/certs through a Compose override and use tls /etc/caddy/certs/machines.pem /etc/caddy/certs/machines.key in the wildcard block. Keep private keys outside the repository and container image.
Environment snapshot bytes are local OCI images stored in the host Docker image store, while their image references and lifecycle metadata are PostgreSQL rows. Snapshots exclude the repository workspace named volume and tmpfs secret mounts; plan backup, migration, pruning, and host disk monitoring accordingly.
The manager process runs as UID/GID 1000 with only the Docker socket's host group added. Access to that socket remains host-root-equivalent despite the non-root container UID, so never expose it to machine workloads or general application services.
cd kuayle
bash selfhosting/update.shThe update script pulls the latest code, rebuilds images, applies pending migrations, and recreates containers. If Dev Machines services are running, it rebuilds their control-plane and runtime images first, stops the existing gateway and manager only while migrations and restricted gateway-role grants are applied, then starts the updated control plane. A failure after the stop clears the upgrade marker, preserves logs, restarts the same previous containers, and returns the original nonzero status. Disabled optional profiles remain disabled.
Instance sysadmins can also enable one-click updates from Settings → Version:
- Copy your user ID from Settings → Profile.
- Add it to
SYSADMINSinselfhosting/.env. - Set a strong
SYSTEM_UPDATER_TOKENand keepSYSTEM_UPDATER_URL=http://updater:8081. - Apply the env and start the internal updater sidecar with
docker compose --profile updater up -d backend updaterfromselfhosting/.
The updater sidecar is internal-only and runs the same selfhosting/update.sh flow. If it is not configured, the Version page shows the manual update command instead.
By default, uploads use a local Docker volume (STORAGE_TYPE=local). The alternative S3 backend accepts a configurable endpoint and path-style requests:
STORAGE_TYPE=s3
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_BUCKET=kuayle-uploads
S3_REGION=us-east-1
S3_ACCESS_KEY=your-key
S3_SECRET_KEY=your-secretThe backend is intended for AWS S3 and compatible APIs such as R2, MinIO, and SeaweedFS. Test bucket creation, permissions, public URL behavior, and signed downloads with your chosen provider.
Kuayle connects to GitHub through a GitHub App. By default, each workspace uses GitHub's App Manifest flow and stores the returned credentials encrypted. Deployments with shared App credentials can let workspaces install a preconfigured App instead (see .env.example for the GITHUB_APP_* variables).
- Links development activity — mention
ENG-123in a branch name, PR title/body, or commit message (case-insensitive) - Auto-transitions — branch created → In Progress, PR opened → In Review, PR merged → Done (configurable)
- Activity feed — linked PRs, branches, and commits on the matching issue detail page
If GitHub can reach your Kuayle instance (for example https://kuayle.yourcompany.com), the manifest flow can populate the callback and webhook URLs:
- Go to Settings → GitHub in your workspace
- Click Set up GitHub App — redirects to GitHub with a pre-filled form
- Click Create GitHub App on GitHub
- Redirected back — credentials saved automatically, webhook URL configured
- Click Install on GitHub to grant access to your repos
- Select which repositories to link
GitHub events appear after the App is installed on repositories, webhook delivery succeeds, and an issue identifier is present in supported activity.
If your instance runs on a private network (for example behind a VPN), GitHub cannot send webhooks directly. Use a relay or expose only the webhook endpoint through a controlled tunnel.
Option A: Webhook proxy with smee.io
smee.io is a free webhook relay that forwards GitHub events to your private instance.
- Go to smee.io/new and copy your channel URL (e.g.
https://smee.io/abc123) - Run the proxy on your server:
npx smee-client --url https://smee.io/abc123 --target http://localhost:8080/api/github/webhook
- Set up the GitHub App from Settings → GitHub (same steps as above)
- After the app is created, go to its settings on GitHub: GitHub → Settings → Developer settings → GitHub Apps → your app → General
- Set Webhook URL to your smee channel URL (
https://smee.io/abc123) - Check Active and save
The smee client must stay running to receive events. You can run it as a systemd service:
# /etc/systemd/system/smee-kuayle.service
[Unit]
Description=Smee webhook proxy for Kuayle
After=network.target
[Service]
ExecStart=/usr/bin/npx smee-client --url https://smee.io/abc123 --target http://localhost:8080/api/github/webhook
Restart=always
User=kuayle
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now smee-kuayleOption B: Reverse tunnel
Use a reverse tunnel to expose just the webhook endpoint. With cloudflared:
cloudflared tunnel --url http://localhost:8080Or with ngrok:
ngrok http 8080Then set the generated public URL as the webhook URL in your GitHub App settings (append /api/github/webhook).
For local development, the GitHub App is created without a webhook URL (since localhost isn't reachable). To receive webhook events during development:
- Set up the GitHub App from Settings → GitHub (works on localhost)
- Start a smee proxy:
npx smee-client --url https://smee.io/your-channel --target http://localhost:8080/api/github/webhook
- Update the webhook URL in your GitHub App settings to your smee channel URL
- Events will now flow through to your local instance
kuayle/
├── BE/ # Backend (Go)
│ ├── cmd/server/ # Entrypoint (server, migrate, seed)
│ └── internal/
│ ├── config/ # Configuration
│ ├── domain/ # Domain models
│ ├── dto/ # Data transfer objects
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access (raw SQL)
│ ├── middleware/ # Auth and request middleware
│ └── realtime/ # WebSocket support
├── UI/ # Frontend (SvelteKit)
│ └── src/
│ ├── routes/ # Pages
│ └── lib/
│ ├── api/ # API client
│ ├── components/ # UI components
│ ├── features/ # Feature modules
│ ├── types/ # TypeScript types
│ └── utils/ # Utilities
├── WEB/ # Public marketing site (SvelteKit, statically generated)
├── selfhosting/ # Production Docker Compose + Caddy config
│ ├── docker-compose.yml
│ ├── Caddyfile
│ ├── update.sh
│ └── .env.example
├── scripts/ # Dev and maintenance scripts
├── docker-compose.yml # Dev Docker Compose
├── Makefile
├── TECHNICAL.md # Dev Machines specification
└── .env.example
Pull requests are welcome. Keep changes focused, explain the behavior being changed, and include relevant validation.
- Fork the repo
- Create your branch (
git checkout -b feature/cool-thing) - Commit your changes
- Push and open a PR
Apache 2.0, see LICENSE.
Alberto Carbognin, @carbogninalberto
Project note: Kuayle has been developed with AI-assisted tooling under human direction and review. Published releases remain MVPs; evaluate and test them against your requirements before production use.
