--- 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" --- 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 @/home/tho/.claude/get-shit-done/workflows/execute-plan.md @/home/tho/.claude/get-shit-done/templates/summary.md @.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 Task 1: Create health check endpoint src/routes/health/+server.ts 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) - `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) /health endpoint returns 200 with database connectivity check Task 2: Create environment documentation and backup script .env.example, backup.sh 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 - .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` .env.example documents all configuration options, backup.sh creates timestamped archive Task 3: Add Docker deployment section to README README.md 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. - README.md exists with Docker Deployment section - Section includes Quick Start, Configuration, Backup instructions - Commands are correct and copy-pasteable README.md documents Docker deployment with quick start, configuration, and backup instructions 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 ``` - /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 After completion, create `.planning/phases/06-deployment/06-02-SUMMARY.md`