rdev/cookbooks/composable-app.md

11 KiB
Raw Blame History

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

export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
export RDEV_API_KEY="<your-api-key>"

Infrastructure Required


Step 1: Create Project (Monorepo Skeleton)

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:

{
  "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 - adds build-api step
  • Procfile - adds api: make -C services/api run
  • go.work - adds use ./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/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:

./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 /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

# 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

# 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