420 lines
11 KiB
Markdown
420 lines
11 KiB
Markdown
# 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)
|