rdev/cookbooks/composable-app.md

420 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 /project
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
Git push triggers Woodpecker CI
CI builds and deploys all components to K8s
```
**Composable. Template-driven. CI auto-configured.**
---
## Prerequisites
### API Access
```bash
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)
```bash
curl -X POST "$RDEV_API_URL/project" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "taskapp",
"description": "Task management application"
}'
```
**Response:**
```json
{
"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
```bash
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:**
```json
{
"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` - adds `build-api` step
- `Procfile` - adds `api: make -C services/api run`
- `go.work` - adds `use ./services/api`
---
## Step 3: Add Frontend App
```bash
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:**
```json
{
"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:
```bash
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:
```bash
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:
```bash
# 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
```bash
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
```bash
# 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
```bash
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
```bash
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
```bash
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
```bash
curl -X DELETE "$RDEV_API_URL/project/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:
```bash
./cookbooks/scripts/composable-test.sh run my-test-app
```
Check status:
```bash
./cookbooks/scripts/composable-test.sh status my-test-app
```
Cleanup:
```bash
./cookbooks/scripts/composable-test.sh teardown my-test-app
```
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────────┐
│ Composable Full-Stack Deployment │
│ │
│ POST /project │
│ │ │
│ └──► 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
```bash
# Check project exists
curl -s "$RDEV_API_URL/project/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
```bash
# Check worker status
curl -s "$RDEV_API_URL/workers" -H "X-API-Key: $RDEV_API_KEY" | jq '.data.summary'
```
### Pipeline fails
```bash
# 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
```bash
# 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](./landing-page.md) - Simple single-component site
- [Composable Monorepo Guide](../.claude/guides/services/composable-monorepo.md)
- [Component Templates](../.claude/guides/services/templates.md)