Adds the composable monorepo template system that generates project skeletons with pluggable components (service, worker, app-react, app-astro, cli). Key changes: - Monorepo skeleton templates with shared pkg/, scripts/, and git hooks - Component templates (service, worker, app-react, app-astro, cli) with Dockerfiles, CI steps, and component.yaml manifests - Component domain model with validation and dependency resolution - Component handler endpoints for CRUD and composition - Template provider extended with BuildComposableProject and component assembly - Deployer extended with composable project deployment support - Handler timeout constants (TimeoutFastLookup through TimeoutLongRunning) - envutil package for centralized env var reads with defaults - api.DecodeJSON helper for standardized request body decoding - Standardized response helpers (WriteBadRequest, WriteNotFound, etc.) - Replaced fullstack-app cookbook with composable-app cookbook - Hardened handler timeouts, logging, and error responses across all handlers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
11 KiB
Composable App Cookbook
Deploy a full-stack application with multiple components using composable monorepo templates.
Overview
This cookbook creates a full-stack application by composing components:
POST /projects
↓
Creates: Monorepo skeleton with shared packages
↓
POST /projects/{id}/components (service)
↓
Adds: Go API backend with CI step
↓
POST /projects/{id}/components (app)
↓
Adds: React/Astro frontend with CI step
↓
POST /projects/{id}/deploy
↓
Deploys all components to K8s
Composable. Template-driven. CI auto-configured.
Prerequisites
API Access
export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
export RDEV_API_KEY="<your-api-key>"
Infrastructure Required
- rdev-api running with embedded worker
- Gitea at https://git.threesix.ai
- Woodpecker CI at https://ci.threesix.ai
Step 1: Create Project (Monorepo Skeleton)
curl -X POST "$RDEV_API_URL/projects" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "taskapp",
"description": "Task management application"
}'
Response:
{
"data": {
"project_id": "taskapp",
"name": "taskapp",
"domain": "xyz789ab.threesix.ai",
"url": "https://xyz789ab.threesix.ai",
"git": {
"owner": "jordan",
"name": "taskapp",
"html_url": "https://git.threesix.ai/jordan/taskapp"
}
}
}
This creates:
taskapp/
├── CLAUDE.md # AI routing
├── README.md # Project docs
├── Procfile # Local dev (empty)
├── docker-compose.yml # Postgres, Redis
├── go.work # Go workspace
├── .woodpecker.yml # CI pipeline (template-provided)
├── .golangci.yml # Go linting
├── scripts/ # Discovery scripts
│ ├── discover.sh
│ ├── install.sh
│ ├── quality.sh
│ └── dev.sh
├── pkg/ # Shared Go packages
│ ├── app/ # Service bootstrapper
│ ├── middleware/ # HTTP middleware
│ ├── httpresponse/ # JSON responses
│ └── ...
├── services/ # (empty, ready for components)
├── apps/ # (empty, ready for components)
├── workers/ # (empty, ready for components)
└── cli/ # (empty, ready for components)
Step 2: Add Backend Service
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "service",
"name": "api",
"template": "service"
}'
Response:
{
"data": {
"type": "service",
"name": "api",
"path": "services/api",
"port": 8001,
"template": "service"
}
}
This adds:
services/api/
├── cmd/server/main.go # Entry point using pkg/app
├── internal/
│ ├── api/routes.go # Chi router setup
│ ├── api/handlers/health.go # Health endpoints
│ └── config/config.go # Configuration
├── migrations/ # Database migrations
├── Makefile # Build targets
├── Dockerfile # Multi-stage Go build
├── component.yaml # Port, dependencies
└── .env.example # Environment template
And updates:
.woodpecker.yml- addsbuild-apistepProcfile- addsapi: make -C services/api rungo.work- addsuse ./services/api
Step 3: Add Frontend App
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "app",
"name": "dashboard",
"template": "app-react"
}'
Response:
{
"data": {
"type": "app",
"name": "dashboard",
"path": "apps/dashboard",
"port": 3001,
"template": "app-react"
}
}
This adds:
apps/dashboard/
├── src/
│ ├── App.tsx
│ ├── main.tsx
│ └── components/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── Dockerfile # Multi-stage Node build
└── component.yaml # Port, dependencies
Step 4: Customize with Claude (Optional)
Submit a build task to customize the components:
curl -X POST "$RDEV_API_URL/projects/taskapp/builds" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Implement a task management system:\n\nBACKEND (services/api):\n- Add Task model with id, title, description, status, created_at\n- Endpoints: GET /api/tasks, POST /api/tasks, PATCH /api/tasks/{id}, DELETE /api/tasks/{id}\n- In-memory storage for now\n\nFRONTEND (apps/dashboard):\n- Task list with status badges\n- Add task form\n- Mark complete/delete buttons\n- Dark theme with Tailwind\n- Fetch from /api/tasks",
"auto_commit": true,
"auto_push": true
}'
Monitor the build:
curl -s "$RDEV_API_URL/builds/{task_id}" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data.status'
Step 5: Deploy All Components
curl -X POST "$RDEV_API_URL/projects/taskapp/deploy" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
This deploys all components to K8s, or deploy a single component:
curl -X POST "$RDEV_API_URL/projects/taskapp/deploy" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"component": "services/api"}'
Step 6: Monitor CI Pipeline
curl -s "$RDEV_API_URL/projects/taskapp/pipelines" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data[0]'
The pipeline builds both components in parallel then deploys.
Step 7: Verify Deployment
# Check site is live
curl -I https://xyz789ab.threesix.ai
# Test API
curl https://xyz789ab.threesix.ai/api/tasks | jq .
# View the app
open https://xyz789ab.threesix.ai
Adding More Components
Add a Background Worker
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "worker",
"name": "notifications",
"template": "worker"
}'
Add a CLI Tool
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "cli",
"name": "taskctl",
"template": "cli"
}'
List All Components
curl -s "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
Component Types
| Type | Directory | Templates | Default Port |
|---|---|---|---|
| service | services/ |
service | 8001+ |
| worker | workers/ |
worker | N/A |
| app | apps/ |
app-astro, app-react | 3001+ |
| cli | cli/ |
cli | N/A |
Ports auto-increment as you add components of the same type.
Teardown
curl -X DELETE "$RDEV_API_URL/projects/taskapp" \
-H "X-API-Key: $RDEV_API_KEY"
Removes: DNS records, K8s deployments, project metadata. Gitea repo preserved.
E2E Test Script
Run the full flow:
./cookbooks/scripts/composable-test.sh run my-test-app
Check status:
./cookbooks/scripts/composable-test.sh status my-test-app
Cleanup:
./cookbooks/scripts/composable-test.sh teardown my-test-app
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ Composable Full-Stack Deployment │
│ │
│ POST /projects │
│ │ │
│ └──► Creates monorepo skeleton with: │
│ - Shared pkg/ (8 packages) │
│ - .woodpecker.yml (base CI) │
│ - Discovery scripts │
│ │
│ POST /projects/{id}/components (× N) │
│ │ │
│ └──► For each component: │
│ - Renders template to services/|apps/|workers/|cli/ │
│ - Inserts CI step into .woodpecker.yml │
│ - Updates Procfile, go.work │
│ │
│ POST /projects/{id}/builds (optional) │
│ │ │
│ └──► Claude customizes existing components │
│ │
│ Git push → Woodpecker CI: │
│ ├──► build-api (Kaniko → registry) │
│ ├──► build-dashboard (Kaniko → registry) │
│ └──► deploy (kubectl set image) │
│ │
│ Components live at https://{slug}.threesix.ai │
│ ├──► /api/* → services/api │
│ └──► /* → apps/dashboard │
└─────────────────────────────────────────────────────────────────────┘
Troubleshooting
Component addition fails
# Check project exists
curl -s "$RDEV_API_URL/projects/taskapp" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
# Check available templates
curl -s "$RDEV_API_URL/templates/components" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
Build stuck in pending
# Check worker status
curl -s "$RDEV_API_URL/workers" -H "X-API-Key: $RDEV_API_KEY" | jq '.data.summary'
Pipeline fails
# Get pipeline details
curl -s "$RDEV_API_URL/projects/taskapp/pipelines/1" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
# Check Woodpecker UI
open https://ci.threesix.ai/jordan/taskapp
Deployment not updating
# Check K8s pods
kubectl get pods -n projects -l app=taskapp
# Check deployment events
kubectl describe deployment taskapp-api -n projects
Related
- Landing Page Cookbook - Simple single-component site
- Composable Monorepo Guide
- Component Templates