Add deploy-{name} CI steps to all component templates (app-astro,
app-react, service, worker) so each component deploys independently
via kubectl set image on merge to main. Replace the skeleton's
generic deploy step with a verify step that confirms deployments.
Add GET /templates/components endpoint for listing available component
templates with optional type filter. Simplify component API by merging
type+template into a single type field (e.g., "app-react" instead of
type="app" template="app-react").
Include ESLint configs and pnpm-workspace.yaml in app templates.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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-react",
"name": "dashboard"
}'
Response:
{
"data": {
"type": "app-react",
"name": "dashboard",
"path": "apps/dashboard",
"port": 3001
}
}
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: Deployment (CI-Driven)
Each component's CI step includes a deploy-{name} phase that runs kubectl set image on merge to main. Pushing code triggers the full build-and-deploy pipeline automatically.
To manually deploy a single component outside of CI:
# Example: deploy the api service with a specific image tag
kubectl set image deployment/taskapp-api \
api=registry.threesix.ai/taskapp/api:<tag> \
-n projects
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 | Stack | Default Port |
|---|---|---|---|
| service | services/ |
go | 8001+ |
| worker | workers/ |
go | N/A |
| app-astro | apps/ |
astro | 3001+ |
| app-react | apps/ |
react | 3001+ |
| cli | cli/ |
go | 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) │
│ ├──► deploy-api (kubectl set image) │
│ ├──► build-dashboard (Kaniko → registry) │
│ ├──► deploy-dashboard (kubectl set image) │
│ └──► verify (confirm deployments) │
│ │
│ 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 component templates
curl -s "$RDEV_API_URL/templates/components" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data.components'
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