docs(06): create phase plan
Phase 06: Deployment - 2 plans in 2 waves - Wave 1: Docker configuration (adapter-node, Dockerfile, docker-compose) - Wave 2: Runtime configuration (health endpoint, env docs, backup script) - Ready for execution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
384
.planning/phases/06-deployment/06-02-PLAN.md
Normal file
384
.planning/phases/06-deployment/06-02-PLAN.md
Normal file
@@ -0,0 +1,384 @@
|
||||
---
|
||||
phase: 06-deployment
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["06-01"]
|
||||
files_modified:
|
||||
- src/routes/health/+server.ts
|
||||
- .env.example
|
||||
- backup.sh
|
||||
- README.md
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Health endpoint returns 200 when database is accessible"
|
||||
- "Health endpoint returns 503 when database fails"
|
||||
- "Environment variables are documented with examples"
|
||||
- "Backup script creates timestamped archive of data volume"
|
||||
artifacts:
|
||||
- path: "src/routes/health/+server.ts"
|
||||
provides: "Health check endpoint for Docker"
|
||||
exports: ["GET"]
|
||||
- path: ".env.example"
|
||||
provides: "Environment variable documentation"
|
||||
contains: "TASKPLANER_DATA_DIR"
|
||||
- path: "backup.sh"
|
||||
provides: "Volume backup script"
|
||||
contains: "tar czf"
|
||||
key_links:
|
||||
- from: "Dockerfile"
|
||||
to: "src/routes/health/+server.ts"
|
||||
via: "HEALTHCHECK wget command"
|
||||
pattern: "/health"
|
||||
- from: "docker-compose.yml"
|
||||
to: ".env.example"
|
||||
via: "environment variable reference"
|
||||
pattern: "ORIGIN"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Runtime configuration with health checks, environment documentation, and backup tooling
|
||||
|
||||
Purpose: Complete the production deployment setup with health monitoring for Docker, clear documentation of configuration options, and a backup script for data preservation.
|
||||
|
||||
Output: /health endpoint, .env.example template, backup.sh script, updated README with Docker instructions
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/tho/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/tho/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/06-deployment/06-RESEARCH.md
|
||||
@.planning/phases/06-deployment/06-01-SUMMARY.md
|
||||
|
||||
Key from Plan 01:
|
||||
- Dockerfile has HEALTHCHECK pointing to /health
|
||||
- Data paths use TASKPLANER_DATA_DIR env var
|
||||
- docker-compose.yml uses named volume taskplaner_data
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create health check endpoint</name>
|
||||
<files>src/routes/health/+server.ts</files>
|
||||
<action>
|
||||
Create src/routes/health/+server.ts:
|
||||
|
||||
```typescript
|
||||
import type { RequestHandler } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { entries } from '$lib/server/db/schema';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
try {
|
||||
// Verify database connectivity with a simple query
|
||||
db.select().from(entries).limit(1).all();
|
||||
|
||||
return new Response('ok', {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Health check failed:', error);
|
||||
|
||||
return new Response('unhealthy', {
|
||||
status: 503,
|
||||
headers: { 'Content-Type': 'text/plain' }
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The endpoint:
|
||||
- Returns 200 "ok" when database is accessible
|
||||
- Returns 503 "unhealthy" when database query fails
|
||||
- Logs errors for debugging but doesn't expose details
|
||||
- Uses simple text response (Docker just needs status code)
|
||||
</action>
|
||||
<verify>
|
||||
- `npm run dev` and visit http://localhost:5173/health returns "ok"
|
||||
- Health endpoint returns 200 status code
|
||||
- Database query is executed (visible in dev server logs on first request)
|
||||
</verify>
|
||||
<done>/health endpoint returns 200 with database connectivity check</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Create environment documentation and backup script</name>
|
||||
<files>.env.example, backup.sh</files>
|
||||
<action>
|
||||
1. Create .env.example:
|
||||
```bash
|
||||
# TaskPlaner Environment Configuration
|
||||
# Copy to .env and customize for your deployment
|
||||
|
||||
# ============================================
|
||||
# Server Configuration
|
||||
# ============================================
|
||||
|
||||
# Port the server listens on (inside container)
|
||||
# Map to host port via docker-compose ports setting
|
||||
PORT=3000
|
||||
|
||||
# ============================================
|
||||
# Data Storage
|
||||
# ============================================
|
||||
|
||||
# Directory for database and uploads
|
||||
# Docker: /data (must match volume mount)
|
||||
# Local development: ./data
|
||||
TASKPLANER_DATA_DIR=/data
|
||||
|
||||
# Optional: Direct database path override
|
||||
# DATABASE_PATH=/data/db/taskplaner.db
|
||||
|
||||
# ============================================
|
||||
# Production URL (REQUIRED for production)
|
||||
# ============================================
|
||||
|
||||
# The full URL where users access the app
|
||||
# Used for CSRF validation and generating absolute URLs
|
||||
# Example: https://tasks.example.com
|
||||
ORIGIN=http://localhost:3000
|
||||
|
||||
# ============================================
|
||||
# Request Limits
|
||||
# ============================================
|
||||
|
||||
# Maximum request body size
|
||||
# Supports K, M, G suffixes
|
||||
# Default: 512kb, recommended for images: 10M
|
||||
BODY_SIZE_LIMIT=10M
|
||||
|
||||
# ============================================
|
||||
# Reverse Proxy Configuration
|
||||
# Uncomment when running behind nginx/traefik/etc
|
||||
# ============================================
|
||||
|
||||
# Header containing original protocol (http/https)
|
||||
# PROTOCOL_HEADER=x-forwarded-proto
|
||||
|
||||
# Header containing original host
|
||||
# HOST_HEADER=x-forwarded-host
|
||||
|
||||
# Header containing original client IP
|
||||
# ADDRESS_HEADER=x-forwarded-for
|
||||
|
||||
# Number of trusted proxies in front of app
|
||||
# XFF_DEPTH=1
|
||||
```
|
||||
|
||||
2. Create backup.sh:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# TaskPlaner Data Backup Script
|
||||
# Creates a timestamped backup of the Docker volume
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration (override via environment)
|
||||
BACKUP_DIR="${BACKUP_DIR:-./backups}"
|
||||
VOLUME_NAME="${VOLUME_NAME:-taskplaner_taskplaner_data}"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="${BACKUP_DIR}/taskplaner_backup_${TIMESTAMP}.tar.gz"
|
||||
|
||||
# Create backup directory if needed
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
echo "========================================="
|
||||
echo "TaskPlaner Backup"
|
||||
echo "========================================="
|
||||
echo "Volume: $VOLUME_NAME"
|
||||
echo "Output: $BACKUP_FILE"
|
||||
echo ""
|
||||
|
||||
# Check if volume exists
|
||||
if ! docker volume inspect "$VOLUME_NAME" > /dev/null 2>&1; then
|
||||
echo "Error: Volume '$VOLUME_NAME' not found"
|
||||
echo ""
|
||||
echo "Available volumes:"
|
||||
docker volume ls --format ' - {{.Name}}' | grep -i taskplaner || echo " (none with 'taskplaner' in name)"
|
||||
echo ""
|
||||
echo "Tip: Set VOLUME_NAME environment variable to use a different volume"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create backup using temporary Alpine container
|
||||
echo "Creating backup..."
|
||||
docker run --rm \
|
||||
-v "${VOLUME_NAME}:/data:ro" \
|
||||
-v "$(cd "$BACKUP_DIR" && pwd):/backup" \
|
||||
alpine:latest \
|
||||
tar czf "/backup/taskplaner_backup_${TIMESTAMP}.tar.gz" -C /data .
|
||||
|
||||
echo ""
|
||||
echo "Backup complete!"
|
||||
echo "File: $BACKUP_FILE"
|
||||
echo "Size: $(du -h "$BACKUP_FILE" | cut -f1)"
|
||||
echo ""
|
||||
echo "To restore: docker run --rm -v ${VOLUME_NAME}:/data -v \$(pwd)/${BACKUP_DIR}:/backup alpine tar xzf /backup/taskplaner_backup_${TIMESTAMP}.tar.gz -C /data"
|
||||
```
|
||||
|
||||
3. Make backup.sh executable:
|
||||
After creating the file, run: chmod +x backup.sh
|
||||
</action>
|
||||
<verify>
|
||||
- .env.example exists with all documented variables
|
||||
- backup.sh exists and is executable: `ls -la backup.sh`
|
||||
- backup.sh syntax is valid: `bash -n backup.sh`
|
||||
</verify>
|
||||
<done>.env.example documents all configuration options, backup.sh creates timestamped archive</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Add Docker deployment section to README</name>
|
||||
<files>README.md</files>
|
||||
<action>
|
||||
Check if README.md exists. If it does, add a Docker Deployment section. If not, create a minimal README with the Docker section.
|
||||
|
||||
Add this section (after any existing content, or as the main content):
|
||||
|
||||
```markdown
|
||||
## Docker Deployment
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Build and start the container
|
||||
docker-compose up -d
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop the container
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
The application will be available at http://localhost:3000
|
||||
|
||||
### Configuration
|
||||
|
||||
Copy `.env.example` to `.env` and customize:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Key settings:
|
||||
- `ORIGIN` - Required for production. Set to your public URL (e.g., `https://tasks.example.com`)
|
||||
- `BODY_SIZE_LIMIT` - Max upload size. Default: `512kb`, recommended: `10M`
|
||||
- `PORT` - Server port inside container. Default: `3000`
|
||||
|
||||
### Behind a Reverse Proxy
|
||||
|
||||
When running behind nginx, traefik, or similar, uncomment these in `.env`:
|
||||
|
||||
```bash
|
||||
PROTOCOL_HEADER=x-forwarded-proto
|
||||
HOST_HEADER=x-forwarded-host
|
||||
ADDRESS_HEADER=x-forwarded-for
|
||||
XFF_DEPTH=1
|
||||
```
|
||||
|
||||
### Data Persistence
|
||||
|
||||
Data is stored in a Docker named volume (`taskplaner_data`). This includes:
|
||||
- SQLite database (`/data/db/taskplaner.db`)
|
||||
- Uploaded images (`/data/uploads/`)
|
||||
|
||||
The volume persists across container restarts and updates.
|
||||
|
||||
### Backup & Restore
|
||||
|
||||
Create a backup:
|
||||
```bash
|
||||
./backup.sh
|
||||
```
|
||||
|
||||
Backups are saved to `./backups/` with timestamps.
|
||||
|
||||
Restore from backup:
|
||||
```bash
|
||||
# Stop the container first
|
||||
docker-compose down
|
||||
|
||||
# Restore (replace TIMESTAMP with actual backup filename)
|
||||
docker run --rm \
|
||||
-v taskplaner_taskplaner_data:/data \
|
||||
-v $(pwd)/backups:/backup \
|
||||
alpine tar xzf /backup/taskplaner_backup_TIMESTAMP.tar.gz -C /data
|
||||
|
||||
# Start the container
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Health Check
|
||||
|
||||
The container includes a health check at `/health`. View status:
|
||||
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
A healthy container shows `(healthy)` in the status column.
|
||||
```
|
||||
|
||||
If README.md exists, preserve existing content and add this as a new section.
|
||||
If README.md doesn't exist, create it with a title "# TaskPlaner" followed by this Docker section.
|
||||
</action>
|
||||
<verify>
|
||||
- README.md exists with Docker Deployment section
|
||||
- Section includes Quick Start, Configuration, Backup instructions
|
||||
- Commands are correct and copy-pasteable
|
||||
</verify>
|
||||
<done>README.md documents Docker deployment with quick start, configuration, and backup instructions</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. Health endpoint works: `curl http://localhost:5173/health` returns "ok" (dev mode)
|
||||
2. Environment documented: `.env.example` has all configuration options
|
||||
3. Backup script works: `./backup.sh` (requires running container with volume)
|
||||
4. README complete: Docker section with quick start and backup instructions
|
||||
|
||||
Full Docker verification (after both plans):
|
||||
```bash
|
||||
# Build and start
|
||||
docker-compose up -d
|
||||
|
||||
# Check health
|
||||
docker-compose ps # Should show (healthy)
|
||||
curl http://localhost:3000/health # Should return "ok"
|
||||
|
||||
# Verify data persistence
|
||||
# Add an entry via the UI, then:
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
# Entry should still exist
|
||||
|
||||
# Test backup
|
||||
./backup.sh
|
||||
ls -la backups/ # Should show timestamped tar.gz
|
||||
```
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- /health endpoint returns 200 when database accessible, 503 on failure
|
||||
- .env.example documents PORT, TASKPLANER_DATA_DIR, ORIGIN, BODY_SIZE_LIMIT, proxy headers
|
||||
- backup.sh creates timestamped tar.gz of data volume
|
||||
- README.md has Docker deployment section with quick start and backup instructions
|
||||
- Container shows "healthy" status in docker-compose ps
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/06-deployment/06-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user