From f2ede91fd9af04c188f4bec67614d2a25f353c60 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 19:06:02 +0000 Subject: [PATCH 1/6] docs: add comprehensive CI/CD deployment plan - Define multi-environment strategy (dev/prod) with separate Supabase projects - Document GitHub Actions workflows for CI, dev deployment, and prod deployment - Include Netlify configuration with security headers and SPA redirects - Outline testing strategy with Vitest and React Testing Library - Plan future Docker containerization for self-hosted client deployments - Add implementation roadmap and rollback procedures --- docs/CICD_DEPLOYMENT_PLAN.md | 708 +++++++++++++++++++++++++++++++++++ 1 file changed, 708 insertions(+) create mode 100644 docs/CICD_DEPLOYMENT_PLAN.md diff --git a/docs/CICD_DEPLOYMENT_PLAN.md b/docs/CICD_DEPLOYMENT_PLAN.md new file mode 100644 index 00000000..cb1cb411 --- /dev/null +++ b/docs/CICD_DEPLOYMENT_PLAN.md @@ -0,0 +1,708 @@ +# CI/CD Deployment Plan - Eryxon Flow + +## Overview + +This document outlines the CI/CD strategy for migrating from Lovable to a professional GitHub-based deployment pipeline with separate development and production environments. + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ GitHub Repository │ +│ (SheetMetalConnect/eryxon-flow) │ +└─────────────────────────────────────────────────────────────────────────────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────────┐ + │ Feature │ │ Dev │ │ Main │ + │ Branches │ │ Branch │ │ Branch │ + └───────────┘ └───────────┘ └───────────────┘ + │ │ │ + ▼ ▼ ▼ + ┌───────────┐ ┌───────────┐ ┌───────────────┐ + │ Lint │ │ Deploy │ │ Deploy │ + │ Build │ │ to Dev │ │ to Prod │ + │ Test │ │ Env │ │ Env │ + └───────────┘ └───────────┘ └───────────────┘ + │ │ + ▼ ▼ + ┌───────────┐ ┌───────────────┐ + │ Supabase │ │ Supabase │ + │ DEV │ │ PROD │ + └───────────┘ └───────────────┘ + │ │ + ▼ ▼ + ┌───────────┐ ┌───────────────┐ + │ Netlify │ │ Netlify │ + │ Preview │ │ Production │ + └───────────┘ │ OR Hetzner │ + └───────────────┘ +``` + +--- + +## Environment Strategy + +### 1. Development Environment (DEV) + +**Purpose**: Testing, QA, feature acceptance before production + +| Component | Service | Details | +|-----------|---------|---------| +| Frontend | Netlify (Preview) | Auto-deploy on `dev` branch pushes | +| Database | Supabase DEV Project | Separate project with seed data | +| Edge Functions | Supabase DEV | Deployed via CLI | +| URL | `dev.eryxon-flow.netlify.app` | Or custom subdomain | + +**Characteristics**: +- Uses seed scripts for test data (no prod data sync needed) +- Runs full test suite before deployment +- Accessible for internal QA/acceptance testing +- Can be reset/reseeded at any time + +### 2. Production Environment (PROD) + +**Purpose**: Live customer-facing application + +| Component | Service | Details | +|-----------|---------|---------| +| Frontend | Netlify OR Hetzner | Production domain | +| Database | Supabase PROD Project | Current production instance | +| Edge Functions | Supabase PROD | Deployed after acceptance | +| URL | `app.eryxon-flow.com` | Production domain | + +**Deployment Options**: + +#### Option A: Netlify (Recommended for SaaS) +- Simple, managed hosting +- Global CDN +- Easy rollbacks +- Built-in analytics + +#### Option B: Hetzner (Self-hosted) +- Full control +- Lower cost at scale +- Docker-based deployment +- Required for future client self-hosting + +--- + +## Branch Strategy + +``` +main (production) + │ + └── dev (staging/acceptance) + │ + ├── feature/xxx (feature branches) + ├── fix/xxx (bug fixes) + └── claude/xxx (AI-assisted development) +``` + +| Branch | Purpose | Deploys To | Trigger | +|--------|---------|------------|---------| +| `main` | Production releases | Production | Manual merge from `dev` | +| `dev` | Staging/acceptance | Dev environment | Auto on push | +| `feature/*` | Feature development | PR preview (optional) | PR creation | + +--- + +## GitHub Actions Workflows + +### Workflow 1: CI Pipeline (`ci.yml`) + +**Triggers**: All pull requests, pushes to `dev` and `main` + +```yaml +# .github/workflows/ci.yml +name: CI Pipeline + +on: + push: + branches: [main, dev] + pull_request: + branches: [main, dev] + +jobs: + lint: + name: Lint & Type Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Type check + run: npx tsc --noEmit + + build: + name: Build Application + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build for production + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 + + test: + name: Run Tests + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run test + # Note: Tests need to be set up first (see Testing Strategy section) +``` + +### Workflow 2: Deploy to Dev (`deploy-dev.yml`) + +**Triggers**: Push to `dev` branch (after CI passes) + +```yaml +# .github/workflows/deploy-dev.yml +name: Deploy to Development + +on: + push: + branches: [dev] + +jobs: + deploy-frontend: + name: Deploy Frontend to Dev + runs-on: ubuntu-latest + environment: development + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build for dev environment + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_DEV }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_DEV }} + + - name: Deploy to Netlify (Dev) + uses: nwtgck/actions-netlify@v3 + with: + publish-dir: './dist' + production-deploy: true + github-token: ${{ secrets.GITHUB_TOKEN }} + deploy-message: "Deploy from GitHub Actions - Dev" + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }} + + deploy-edge-functions: + name: Deploy Edge Functions to Dev + runs-on: ubuntu-latest + environment: development + steps: + - uses: actions/checkout@v4 + + - name: Setup Supabase CLI + uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Deploy Edge Functions + run: | + supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_DEV }} + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} +``` + +### Workflow 3: Deploy to Production (`deploy-prod.yml`) + +**Triggers**: Push to `main` branch (manual merge from dev) + +```yaml +# .github/workflows/deploy-prod.yml +name: Deploy to Production + +on: + push: + branches: [main] + +jobs: + deploy-frontend: + name: Deploy Frontend to Production + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build for production + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + + - name: Deploy to Netlify (Production) + uses: nwtgck/actions-netlify@v3 + with: + publish-dir: './dist' + production-deploy: true + github-token: ${{ secrets.GITHUB_TOKEN }} + deploy-message: "Deploy from GitHub Actions - Production" + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PROD }} + + deploy-edge-functions: + name: Deploy Edge Functions to Production + runs-on: ubuntu-latest + environment: production + needs: deploy-frontend + steps: + - uses: actions/checkout@v4 + + - name: Setup Supabase CLI + uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Deploy Edge Functions + run: | + supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_PROD }} + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + + create-release: + name: Create Release Tag + runs-on: ubuntu-latest + needs: [deploy-frontend, deploy-edge-functions] + steps: + - uses: actions/checkout@v4 + + - name: Get version from package.json + id: version + run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + tag_name: v${{ steps.version.outputs.VERSION }} + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +--- + +## GitHub Secrets Configuration + +### Required Secrets + +| Secret Name | Environment | Description | +|-------------|-------------|-------------| +| `VITE_SUPABASE_URL_DEV` | Development | Dev Supabase project URL | +| `VITE_SUPABASE_ANON_KEY_DEV` | Development | Dev Supabase anon key | +| `SUPABASE_PROJECT_REF_DEV` | Development | Dev Supabase project ref | +| `NETLIFY_SITE_ID_DEV` | Development | Dev Netlify site ID | +| `VITE_SUPABASE_URL_PROD` | Production | Prod Supabase project URL | +| `VITE_SUPABASE_ANON_KEY_PROD` | Production | Prod Supabase anon key | +| `SUPABASE_PROJECT_REF_PROD` | Production | Prod Supabase project ref | +| `NETLIFY_SITE_ID_PROD` | Production | Prod Netlify site ID | +| `SUPABASE_ACCESS_TOKEN` | All | Supabase CLI access token | +| `NETLIFY_AUTH_TOKEN` | All | Netlify personal access token | + +### Setting Up Secrets + +1. Go to GitHub repo → Settings → Secrets and variables → Actions +2. Add each secret for both environments +3. Use GitHub Environments for `development` and `production` with protection rules + +--- + +## Testing Strategy + +### Phase 1: Basic Testing Setup + +Add Vitest for unit/integration tests: + +```bash +npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom +``` + +Update `package.json`: +```json +{ + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" + } +} +``` + +Create `vitest.config.ts`: +```typescript +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react-swc' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + globals: true, + setupFiles: ['./src/test/setup.ts'], + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) +``` + +### Phase 2: Test Categories + +| Category | Tools | Purpose | +|----------|-------|---------| +| Unit Tests | Vitest | Test utility functions, hooks | +| Component Tests | React Testing Library | Test UI components | +| Integration Tests | Vitest + MSW | Test API integrations | +| E2E Tests (Future) | Playwright | Full user flow testing | + +### Recommended Test Files + +``` +src/ +├── test/ +│ ├── setup.ts # Test setup (jsdom, mocks) +│ └── mocks/ +│ └── handlers.ts # MSW API handlers +├── lib/ +│ ├── scheduler.ts +│ └── scheduler.test.ts # Unit tests +├── hooks/ +│ ├── useQRMMetrics.ts +│ └── useQRMMetrics.test.ts # Hook tests +└── components/ + └── ui/ + └── Button.test.tsx # Component tests +``` + +--- + +## Netlify Configuration + +Create `netlify.toml` in project root: + +```toml +[build] + publish = "dist" + command = "npm run build" + +[build.environment] + NODE_VERSION = "20" + +# SPA redirect - send all routes to index.html +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 + +# Security headers +[[headers]] + for = "/*" + [headers.values] + X-Frame-Options = "DENY" + X-XSS-Protection = "1; mode=block" + X-Content-Type-Options = "nosniff" + Referrer-Policy = "strict-origin-when-cross-origin" + Content-Security-Policy = "frame-ancestors 'none'" + +# Cache static assets +[[headers]] + for = "/assets/*" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" +``` + +--- + +## Future: Docker Containerization + +For self-hosted client deployments with local Supabase: + +### Dockerfile (Frontend) + +```dockerfile +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm ci + +COPY . . + +ARG VITE_SUPABASE_URL +ARG VITE_SUPABASE_PUBLISHABLE_KEY + +RUN npm run build + +# Production stage +FROM nginx:alpine + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +### Docker Compose (Full Stack Self-Hosted) + +```yaml +# docker-compose.yml +version: '3.8' + +services: + # Frontend + eryxon-flow: + build: + context: . + args: + VITE_SUPABASE_URL: http://localhost:8000 + VITE_SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_ANON_KEY} + ports: + - "80:80" + depends_on: + - supabase-kong + + # Supabase Services (simplified) + supabase-db: + image: supabase/postgres:15.1.0.147 + ports: + - "5432:5432" + environment: + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - supabase-db:/var/lib/postgresql/data + + supabase-kong: + image: kong:2.8.1 + ports: + - "8000:8000" + environment: + KONG_DATABASE: "off" + depends_on: + - supabase-db + + # Add more Supabase services as needed: + # - supabase-auth + # - supabase-rest + # - supabase-realtime + # - supabase-storage + +volumes: + supabase-db: +``` + +### GitHub Workflow for Docker Builds + +```yaml +# .github/workflows/docker-build.yml +name: Build Docker Image + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ github.ref_name }} + ghcr.io/${{ github.repository }}:latest + build-args: | + VITE_SUPABASE_URL=__SUPABASE_URL__ + VITE_SUPABASE_PUBLISHABLE_KEY=__SUPABASE_ANON_KEY__ +``` + +--- + +## Implementation Roadmap + +### Phase 1: Foundation (Immediate) +- [ ] Create `dev` branch from `main` +- [ ] Set up second Supabase project for DEV environment +- [ ] Create Netlify account and sites (dev + prod) +- [ ] Configure GitHub secrets +- [ ] Implement CI workflow (`ci.yml`) +- [ ] Create `netlify.toml` + +### Phase 2: Deployment Automation +- [ ] Implement dev deployment workflow (`deploy-dev.yml`) +- [ ] Implement prod deployment workflow (`deploy-prod.yml`) +- [ ] Configure GitHub Environments with protection rules +- [ ] Test full pipeline: feature → dev → prod + +### Phase 3: Testing Infrastructure +- [ ] Add Vitest and React Testing Library +- [ ] Create test setup and configuration +- [ ] Write initial unit tests for critical utilities +- [ ] Add tests to CI pipeline + +### Phase 4: Docker & Self-Hosting (Future) +- [ ] Create Dockerfile for frontend +- [ ] Create docker-compose for full stack +- [ ] Set up GitHub Container Registry +- [ ] Document self-hosted deployment process +- [ ] Create client deployment guide + +--- + +## Workflow Summary + +``` +Developer Flow: +1. Create feature branch from `dev` +2. Make changes, commit, push +3. Create PR to `dev` → CI runs (lint, build, test) +4. Merge to `dev` → Auto-deploy to DEV environment +5. QA/Acceptance testing on DEV +6. Create PR from `dev` to `main` +7. Merge to `main` → Auto-deploy to PRODUCTION +8. Release tag created automatically +``` + +--- + +## Rollback Procedure + +### Netlify Rollback +1. Go to Netlify dashboard → Deploys +2. Click on previous successful deploy +3. Click "Publish deploy" + +### Git Rollback +```bash +# Revert to previous commit +git revert HEAD +git push origin main + +# Or reset to specific commit (use with caution) +git reset --hard +git push --force-with-lease origin main +``` + +--- + +## Monitoring & Alerts + +### Recommended Tools +- **Netlify Analytics**: Built-in traffic monitoring +- **Supabase Dashboard**: Database monitoring, function logs +- **GitHub Actions**: Build/deploy status +- **Sentry** (optional): Error tracking + +### Health Checks +Add a health endpoint or monitor the main app URL for uptime. + +--- + +## Cost Considerations + +| Service | Free Tier | Estimated Monthly (Growth) | +|---------|-----------|---------------------------| +| GitHub Actions | 2,000 min/month | Free for most projects | +| Netlify | 100GB bandwidth | $19/month (Pro) | +| Supabase (DEV) | 500MB DB, 1GB storage | Free tier sufficient | +| Supabase (PROD) | Current plan | Current pricing | +| Hetzner (if used) | N/A | ~$5-20/month | + +--- + +## Next Steps + +1. **Review this plan** and confirm the approach +2. **Create the Supabase DEV project** with seed data +3. **Set up Netlify** accounts and sites +4. **I can create the GitHub Actions workflows** once the plan is approved + +Questions to confirm: +- Preferred production hosting: Netlify or Hetzner? +- Do you need preview deployments for PRs? +- Any specific test scenarios to prioritize? From 5f3828ec68d847f4b7dff0698453bedd1ba6f930 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 19:11:13 +0000 Subject: [PATCH 2/6] feat: add CI/CD workflows and Netlify configuration - Add CI workflow for PRs (lint, type-check, build, test) - Add deploy-dev workflow for dev branch deployments - Add deploy-prod workflow for main branch deployments - Add netlify.toml with SPA routing and security headers - Simplify deployment plan documentation --- .github/workflows/ci.yml | 68 +++ .github/workflows/deploy-dev.yml | 54 +++ .github/workflows/deploy-prod.yml | 74 +++ docs/CICD_DEPLOYMENT_PLAN.md | 749 +++--------------------------- netlify.toml | 27 ++ 5 files changed, 299 insertions(+), 673 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 .github/workflows/deploy-prod.yml create mode 100644 netlify.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..50925777 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: CI + +on: + pull_request: + branches: [main, dev] + +jobs: + lint-and-typecheck: + name: Lint & Type Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Type check + run: npx tsc --noEmit + + build: + name: Build + runs-on: ubuntu-latest + needs: lint-and-typecheck + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_DEV }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_DEV }} + + test: + name: Test + runs-on: ubuntu-latest + needs: lint-and-typecheck + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm run test --if-present diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 00000000..7d936e74 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,54 @@ +name: Deploy to Development + +on: + push: + branches: [dev] + +jobs: + deploy-frontend: + name: Deploy Frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_DEV }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_DEV }} + + - name: Deploy to Netlify + uses: nwtgck/actions-netlify@v3 + with: + publish-dir: './dist' + production-deploy: true + github-token: ${{ secrets.GITHUB_TOKEN }} + deploy-message: "Dev deploy - ${{ github.sha }}" + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }} + + deploy-edge-functions: + name: Deploy Edge Functions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Supabase CLI + uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Deploy functions + run: supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_DEV }} + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 00000000..eb05ed12 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,74 @@ +name: Deploy to Production + +on: + push: + branches: [main] + +jobs: + deploy-frontend: + name: Deploy Frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + + - name: Deploy to Netlify + uses: nwtgck/actions-netlify@v3 + with: + publish-dir: './dist' + production-deploy: true + github-token: ${{ secrets.GITHUB_TOKEN }} + deploy-message: "Prod deploy - ${{ github.sha }}" + env: + NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} + NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PROD }} + + deploy-edge-functions: + name: Deploy Edge Functions + runs-on: ubuntu-latest + needs: deploy-frontend + steps: + - uses: actions/checkout@v4 + + - name: Setup Supabase CLI + uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Deploy functions + run: supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_PROD }} + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + + create-release: + name: Create Release + runs-on: ubuntu-latest + needs: [deploy-frontend, deploy-edge-functions] + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Get version + id: version + run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.VERSION }}-${{ github.run_number }} + generate_release_notes: true diff --git a/docs/CICD_DEPLOYMENT_PLAN.md b/docs/CICD_DEPLOYMENT_PLAN.md index cb1cb411..fc78d03c 100644 --- a/docs/CICD_DEPLOYMENT_PLAN.md +++ b/docs/CICD_DEPLOYMENT_PLAN.md @@ -1,708 +1,111 @@ # CI/CD Deployment Plan - Eryxon Flow -## Overview - -This document outlines the CI/CD strategy for migrating from Lovable to a professional GitHub-based deployment pipeline with separate development and production environments. - -## Architecture Overview +## Architecture ``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ GitHub Repository │ -│ (SheetMetalConnect/eryxon-flow) │ -└─────────────────────────────────────────────────────────────────────────────┘ - │ - ┌───────────────┼───────────────┐ - │ │ │ - ▼ ▼ ▼ - ┌───────────┐ ┌───────────┐ ┌───────────────┐ - │ Feature │ │ Dev │ │ Main │ - │ Branches │ │ Branch │ │ Branch │ - └───────────┘ └───────────┘ └───────────────┘ - │ │ │ - ▼ ▼ ▼ - ┌───────────┐ ┌───────────┐ ┌───────────────┐ - │ Lint │ │ Deploy │ │ Deploy │ - │ Build │ │ to Dev │ │ to Prod │ - │ Test │ │ Env │ │ Env │ - └───────────┘ └───────────┘ └───────────────┘ - │ │ - ▼ ▼ - ┌───────────┐ ┌───────────────┐ - │ Supabase │ │ Supabase │ - │ DEV │ │ PROD │ - └───────────┘ └───────────────┘ - │ │ - ▼ ▼ - ┌───────────┐ ┌───────────────┐ - │ Netlify │ │ Netlify │ - │ Preview │ │ Production │ - └───────────┘ │ OR Hetzner │ - └───────────────┘ +Feature Branches → dev branch → main branch + ↓ ↓ ↓ + CI Tests Deploy DEV Deploy PROD + ↓ ↓ + Current Supabase New Supabase + Netlify Dev Netlify Prod / Hetzner ``` ---- - -## Environment Strategy - -### 1. Development Environment (DEV) - -**Purpose**: Testing, QA, feature acceptance before production - -| Component | Service | Details | -|-----------|---------|---------| -| Frontend | Netlify (Preview) | Auto-deploy on `dev` branch pushes | -| Database | Supabase DEV Project | Separate project with seed data | -| Edge Functions | Supabase DEV | Deployed via CLI | -| URL | `dev.eryxon-flow.netlify.app` | Or custom subdomain | - -**Characteristics**: -- Uses seed scripts for test data (no prod data sync needed) -- Runs full test suite before deployment -- Accessible for internal QA/acceptance testing -- Can be reset/reseeded at any time - -### 2. Production Environment (PROD) - -**Purpose**: Live customer-facing application +## Environments -| Component | Service | Details | -|-----------|---------|---------| -| Frontend | Netlify OR Hetzner | Production domain | -| Database | Supabase PROD Project | Current production instance | -| Edge Functions | Supabase PROD | Deployed after acceptance | -| URL | `app.eryxon-flow.com` | Production domain | - -**Deployment Options**: - -#### Option A: Netlify (Recommended for SaaS) -- Simple, managed hosting -- Global CDN -- Easy rollbacks -- Built-in analytics - -#### Option B: Hetzner (Self-hosted) -- Full control -- Lower cost at scale -- Docker-based deployment -- Required for future client self-hosting - ---- +| Environment | Frontend | Database | Trigger | +|-------------|----------|----------|---------| +| **DEV** | Netlify | Current Supabase project | Push to `dev` | +| **PROD** | Netlify or Hetzner | New Supabase project (TBD) | Push to `main` | ## Branch Strategy -``` -main (production) - │ - └── dev (staging/acceptance) - │ - ├── feature/xxx (feature branches) - ├── fix/xxx (bug fixes) - └── claude/xxx (AI-assisted development) -``` - -| Branch | Purpose | Deploys To | Trigger | -|--------|---------|------------|---------| -| `main` | Production releases | Production | Manual merge from `dev` | -| `dev` | Staging/acceptance | Dev environment | Auto on push | -| `feature/*` | Feature development | PR preview (optional) | PR creation | - ---- - -## GitHub Actions Workflows - -### Workflow 1: CI Pipeline (`ci.yml`) - -**Triggers**: All pull requests, pushes to `dev` and `main` - -```yaml -# .github/workflows/ci.yml -name: CI Pipeline - -on: - push: - branches: [main, dev] - pull_request: - branches: [main, dev] - -jobs: - lint: - name: Lint & Type Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Run ESLint - run: npm run lint - - - name: Type check - run: npx tsc --noEmit - - build: - name: Build Application - runs-on: ubuntu-latest - needs: lint - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build for production - run: npm run build - env: - VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} - VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - retention-days: 7 - - test: - name: Run Tests - runs-on: ubuntu-latest - needs: lint - steps: - - uses: actions/checkout@v4 +- `main` - Production releases (protected) +- `dev` - Development/staging environment +- `feature/*`, `fix/*`, `claude/*` - Working branches → PR to `dev` - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' +## Workflows - - name: Install dependencies - run: npm ci +| Workflow | Trigger | Actions | +|----------|---------|---------| +| `ci.yml` | All PRs | Lint, type-check, build, test | +| `deploy-dev.yml` | Push to `dev` | Build + deploy to Netlify DEV + Supabase Edge Functions | +| `deploy-prod.yml` | Push to `main` | Build + deploy to Netlify PROD + Supabase Edge Functions | - - name: Run tests - run: npm run test - # Note: Tests need to be set up first (see Testing Strategy section) -``` - -### Workflow 2: Deploy to Dev (`deploy-dev.yml`) - -**Triggers**: Push to `dev` branch (after CI passes) - -```yaml -# .github/workflows/deploy-dev.yml -name: Deploy to Development - -on: - push: - branches: [dev] - -jobs: - deploy-frontend: - name: Deploy Frontend to Dev - runs-on: ubuntu-latest - environment: development - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build for dev environment - run: npm run build - env: - VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_DEV }} - VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_DEV }} - - - name: Deploy to Netlify (Dev) - uses: nwtgck/actions-netlify@v3 - with: - publish-dir: './dist' - production-deploy: true - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: "Deploy from GitHub Actions - Dev" - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }} - - deploy-edge-functions: - name: Deploy Edge Functions to Dev - runs-on: ubuntu-latest - environment: development - steps: - - uses: actions/checkout@v4 - - - name: Setup Supabase CLI - uses: supabase/setup-cli@v1 - with: - version: latest - - - name: Deploy Edge Functions - run: | - supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_DEV }} - env: - SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} -``` - -### Workflow 3: Deploy to Production (`deploy-prod.yml`) - -**Triggers**: Push to `main` branch (manual merge from dev) - -```yaml -# .github/workflows/deploy-prod.yml -name: Deploy to Production +## Required GitHub Secrets -on: - push: - branches: [main] +### Development Environment +- `VITE_SUPABASE_URL_DEV` - Current Supabase URL +- `VITE_SUPABASE_ANON_KEY_DEV` - Current Supabase anon key +- `SUPABASE_PROJECT_REF_DEV` - Current project ref (`vatgianzotsurljznsry`) +- `NETLIFY_SITE_ID_DEV` - Netlify dev site ID -jobs: - deploy-frontend: - name: Deploy Frontend to Production - runs-on: ubuntu-latest - environment: production - steps: - - uses: actions/checkout@v4 +### Production Environment +- `VITE_SUPABASE_URL_PROD` - New Supabase URL (configure when ready) +- `VITE_SUPABASE_ANON_KEY_PROD` - New Supabase anon key +- `SUPABASE_PROJECT_REF_PROD` - New project ref +- `NETLIFY_SITE_ID_PROD` - Netlify prod site ID - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' +### Shared +- `SUPABASE_ACCESS_TOKEN` - Supabase CLI token (from supabase.com/dashboard/account/tokens) +- `NETLIFY_AUTH_TOKEN` - Netlify personal access token - - name: Install dependencies - run: npm ci +## Setup Steps - - name: Build for production - run: npm run build - env: - VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} - VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} +1. **Create Netlify sites** + - Create dev site (e.g., `eryxon-flow-dev`) + - Create prod site (e.g., `eryxon-flow` or custom domain) - - name: Deploy to Netlify (Production) - uses: nwtgck/actions-netlify@v3 - with: - publish-dir: './dist' - production-deploy: true - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: "Deploy from GitHub Actions - Production" - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PROD }} +2. **Get Netlify credentials** + - Go to User Settings → Applications → Personal access tokens + - Copy site IDs from Site Settings → General - deploy-edge-functions: - name: Deploy Edge Functions to Production - runs-on: ubuntu-latest - environment: production - needs: deploy-frontend - steps: - - uses: actions/checkout@v4 +3. **Get Supabase access token** + - Go to supabase.com/dashboard/account/tokens + - Generate new token for CLI access - - name: Setup Supabase CLI - uses: supabase/setup-cli@v1 - with: - version: latest +4. **Configure GitHub secrets** + - Go to repo Settings → Secrets and variables → Actions + - Add all secrets listed above - - name: Deploy Edge Functions - run: | - supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_PROD }} - env: - SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} +5. **Create `dev` branch** + ```bash + git checkout main + git checkout -b dev + git push -u origin dev + ``` - create-release: - name: Create Release Tag - runs-on: ubuntu-latest - needs: [deploy-frontend, deploy-edge-functions] - steps: - - uses: actions/checkout@v4 +6. **Set branch protection** (optional) + - Require PR reviews for `main` + - Require status checks to pass - - name: Get version from package.json - id: version - run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - - - name: Create Release - uses: softprops/action-gh-release@v1 - with: - tag_name: v${{ steps.version.outputs.VERSION }} - generate_release_notes: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - ---- - -## GitHub Secrets Configuration - -### Required Secrets - -| Secret Name | Environment | Description | -|-------------|-------------|-------------| -| `VITE_SUPABASE_URL_DEV` | Development | Dev Supabase project URL | -| `VITE_SUPABASE_ANON_KEY_DEV` | Development | Dev Supabase anon key | -| `SUPABASE_PROJECT_REF_DEV` | Development | Dev Supabase project ref | -| `NETLIFY_SITE_ID_DEV` | Development | Dev Netlify site ID | -| `VITE_SUPABASE_URL_PROD` | Production | Prod Supabase project URL | -| `VITE_SUPABASE_ANON_KEY_PROD` | Production | Prod Supabase anon key | -| `SUPABASE_PROJECT_REF_PROD` | Production | Prod Supabase project ref | -| `NETLIFY_SITE_ID_PROD` | Production | Prod Netlify site ID | -| `SUPABASE_ACCESS_TOKEN` | All | Supabase CLI access token | -| `NETLIFY_AUTH_TOKEN` | All | Netlify personal access token | - -### Setting Up Secrets - -1. Go to GitHub repo → Settings → Secrets and variables → Actions -2. Add each secret for both environments -3. Use GitHub Environments for `development` and `production` with protection rules - ---- - -## Testing Strategy - -### Phase 1: Basic Testing Setup - -Add Vitest for unit/integration tests: +## Developer Workflow ```bash -npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom -``` - -Update `package.json`: -```json -{ - "scripts": { - "test": "vitest run", - "test:watch": "vitest", - "test:coverage": "vitest run --coverage" - } -} -``` - -Create `vitest.config.ts`: -```typescript -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react-swc' -import path from 'path' +# Start new feature +git checkout dev +git pull origin dev +git checkout -b feature/my-feature -export default defineConfig({ - plugins: [react()], - test: { - environment: 'jsdom', - globals: true, - setupFiles: ['./src/test/setup.ts'], - }, - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), - }, - }, -}) -``` - -### Phase 2: Test Categories - -| Category | Tools | Purpose | -|----------|-------|---------| -| Unit Tests | Vitest | Test utility functions, hooks | -| Component Tests | React Testing Library | Test UI components | -| Integration Tests | Vitest + MSW | Test API integrations | -| E2E Tests (Future) | Playwright | Full user flow testing | - -### Recommended Test Files - -``` -src/ -├── test/ -│ ├── setup.ts # Test setup (jsdom, mocks) -│ └── mocks/ -│ └── handlers.ts # MSW API handlers -├── lib/ -│ ├── scheduler.ts -│ └── scheduler.test.ts # Unit tests -├── hooks/ -│ ├── useQRMMetrics.ts -│ └── useQRMMetrics.test.ts # Hook tests -└── components/ - └── ui/ - └── Button.test.tsx # Component tests -``` - ---- - -## Netlify Configuration - -Create `netlify.toml` in project root: - -```toml -[build] - publish = "dist" - command = "npm run build" - -[build.environment] - NODE_VERSION = "20" - -# SPA redirect - send all routes to index.html -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - -# Security headers -[[headers]] - for = "/*" - [headers.values] - X-Frame-Options = "DENY" - X-XSS-Protection = "1; mode=block" - X-Content-Type-Options = "nosniff" - Referrer-Policy = "strict-origin-when-cross-origin" - Content-Security-Policy = "frame-ancestors 'none'" - -# Cache static assets -[[headers]] - for = "/assets/*" - [headers.values] - Cache-Control = "public, max-age=31536000, immutable" -``` - ---- - -## Future: Docker Containerization - -For self-hosted client deployments with local Supabase: - -### Dockerfile (Frontend) - -```dockerfile -# Build stage -FROM node:20-alpine AS builder - -WORKDIR /app -COPY package*.json ./ -RUN npm ci - -COPY . . - -ARG VITE_SUPABASE_URL -ARG VITE_SUPABASE_PUBLISHABLE_KEY - -RUN npm run build - -# Production stage -FROM nginx:alpine - -COPY --from=builder /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] -``` - -### Docker Compose (Full Stack Self-Hosted) - -```yaml -# docker-compose.yml -version: '3.8' - -services: - # Frontend - eryxon-flow: - build: - context: . - args: - VITE_SUPABASE_URL: http://localhost:8000 - VITE_SUPABASE_PUBLISHABLE_KEY: ${SUPABASE_ANON_KEY} - ports: - - "80:80" - depends_on: - - supabase-kong - - # Supabase Services (simplified) - supabase-db: - image: supabase/postgres:15.1.0.147 - ports: - - "5432:5432" - environment: - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - volumes: - - supabase-db:/var/lib/postgresql/data +# Work on feature... +git add . +git commit -m "feat: add feature" +git push -u origin feature/my-feature - supabase-kong: - image: kong:2.8.1 - ports: - - "8000:8000" - environment: - KONG_DATABASE: "off" - depends_on: - - supabase-db - - # Add more Supabase services as needed: - # - supabase-auth - # - supabase-rest - # - supabase-realtime - # - supabase-storage - -volumes: - supabase-db: +# Create PR to dev → CI runs automatically +# Merge to dev → Deploys to DEV environment +# Test on DEV environment +# Create PR from dev to main → Deploys to PROD ``` -### GitHub Workflow for Docker Builds - -```yaml -# .github/workflows/docker-build.yml -name: Build Docker Image - -on: - release: - types: [published] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: | - ghcr.io/${{ github.repository }}:${{ github.ref_name }} - ghcr.io/${{ github.repository }}:latest - build-args: | - VITE_SUPABASE_URL=__SUPABASE_URL__ - VITE_SUPABASE_PUBLISHABLE_KEY=__SUPABASE_ANON_KEY__ -``` +## Future: Docker Self-Hosted ---- +For client self-hosted deployments, build Docker image: -## Implementation Roadmap - -### Phase 1: Foundation (Immediate) -- [ ] Create `dev` branch from `main` -- [ ] Set up second Supabase project for DEV environment -- [ ] Create Netlify account and sites (dev + prod) -- [ ] Configure GitHub secrets -- [ ] Implement CI workflow (`ci.yml`) -- [ ] Create `netlify.toml` - -### Phase 2: Deployment Automation -- [ ] Implement dev deployment workflow (`deploy-dev.yml`) -- [ ] Implement prod deployment workflow (`deploy-prod.yml`) -- [ ] Configure GitHub Environments with protection rules -- [ ] Test full pipeline: feature → dev → prod - -### Phase 3: Testing Infrastructure -- [ ] Add Vitest and React Testing Library -- [ ] Create test setup and configuration -- [ ] Write initial unit tests for critical utilities -- [ ] Add tests to CI pipeline - -### Phase 4: Docker & Self-Hosting (Future) -- [ ] Create Dockerfile for frontend -- [ ] Create docker-compose for full stack -- [ ] Set up GitHub Container Registry -- [ ] Document self-hosted deployment process -- [ ] Create client deployment guide - ---- - -## Workflow Summary - -``` -Developer Flow: -1. Create feature branch from `dev` -2. Make changes, commit, push -3. Create PR to `dev` → CI runs (lint, build, test) -4. Merge to `dev` → Auto-deploy to DEV environment -5. QA/Acceptance testing on DEV -6. Create PR from `dev` to `main` -7. Merge to `main` → Auto-deploy to PRODUCTION -8. Release tag created automatically -``` - ---- - -## Rollback Procedure - -### Netlify Rollback -1. Go to Netlify dashboard → Deploys -2. Click on previous successful deploy -3. Click "Publish deploy" - -### Git Rollback ```bash -# Revert to previous commit -git revert HEAD -git push origin main - -# Or reset to specific commit (use with caution) -git reset --hard -git push --force-with-lease origin main +docker build -t eryxon-flow \ + --build-arg VITE_SUPABASE_URL=http://localhost:8000 \ + --build-arg VITE_SUPABASE_PUBLISHABLE_KEY=your-key . ``` ---- - -## Monitoring & Alerts - -### Recommended Tools -- **Netlify Analytics**: Built-in traffic monitoring -- **Supabase Dashboard**: Database monitoring, function logs -- **GitHub Actions**: Build/deploy status -- **Sentry** (optional): Error tracking - -### Health Checks -Add a health endpoint or monitor the main app URL for uptime. - ---- - -## Cost Considerations - -| Service | Free Tier | Estimated Monthly (Growth) | -|---------|-----------|---------------------------| -| GitHub Actions | 2,000 min/month | Free for most projects | -| Netlify | 100GB bandwidth | $19/month (Pro) | -| Supabase (DEV) | 500MB DB, 1GB storage | Free tier sufficient | -| Supabase (PROD) | Current plan | Current pricing | -| Hetzner (if used) | N/A | ~$5-20/month | - ---- - -## Next Steps - -1. **Review this plan** and confirm the approach -2. **Create the Supabase DEV project** with seed data -3. **Set up Netlify** accounts and sites -4. **I can create the GitHub Actions workflows** once the plan is approved - -Questions to confirm: -- Preferred production hosting: Netlify or Hetzner? -- Do you need preview deployments for PRs? -- Any specific test scenarios to prioritize? +See `Dockerfile` and `docker-compose.yml` (to be created when needed). diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..4542550b --- /dev/null +++ b/netlify.toml @@ -0,0 +1,27 @@ +[build] + publish = "dist" + command = "npm run build" + +[build.environment] + NODE_VERSION = "20" + +# SPA routing - all routes to index.html +[[redirects]] + from = "/*" + to = "/index.html" + status = 200 + +# Security headers +[[headers]] + for = "/*" + [headers.values] + X-Frame-Options = "DENY" + X-XSS-Protection = "1; mode=block" + X-Content-Type-Options = "nosniff" + Referrer-Policy = "strict-origin-when-cross-origin" + +# Cache static assets (1 year) +[[headers]] + for = "/assets/*" + [headers.values] + Cache-Control = "public, max-age=31536000, immutable" From 8e8de64e5a902114fea4a005f2a4e2d051ba78eb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 19:27:41 +0000 Subject: [PATCH 3/6] feat: add Docker deployment for Hetzner production MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Dockerfile with multi-stage build (node + nginx) - Add nginx.conf with SPA routing and security headers - Add docker-compose.yml for simple deployment - Add docker-compose.prod.yml with Caddy for automatic SSL - Add Caddyfile for reverse proxy configuration - Update deploy-prod workflow: build Docker → push to GHCR → deploy to Hetzner via SSH - Update deployment plan with Hetzner setup instructions Production architecture: - Docker image built and pushed to GitHub Container Registry - Automatic deployment to Hetzner server via SSH - Caddy handles SSL certificates (Let's Encrypt) - Supabase Cloud (EU) for database --- .github/workflows/deploy-prod.yml | 97 +++++++---- Caddyfile | 12 ++ Dockerfile | 38 +++++ docker-compose.prod.yml | 33 ++++ docker-compose.yml | 37 +++++ docs/CICD_DEPLOYMENT_PLAN.md | 264 +++++++++++++++++++++++------- nginx.conf | 36 ++++ 7 files changed, 431 insertions(+), 86 deletions(-) create mode 100644 Caddyfile create mode 100644 Dockerfile create mode 100644 docker-compose.prod.yml create mode 100644 docker-compose.yml create mode 100644 nginx.conf diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index eb05ed12..aaafd96f 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -4,43 +4,62 @@ on: push: branches: [main] +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + jobs: - deploy-frontend: - name: Deploy Frontend + build-and-push: + name: Build & Push Docker Image runs-on: ubuntu-latest + permissions: + contents: read + packages: write + outputs: + version: ${{ steps.version.outputs.VERSION }} steps: - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' + - name: Get version + id: version + run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - - name: Install dependencies - run: npm ci + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Build - run: npm run build - env: - VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} - VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v3 + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 with: - publish-dir: './dist' - production-deploy: true - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: "Prod deploy - ${{ github.sha }}" - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_PROD }} + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest + type=raw,value=${{ steps.version.outputs.VERSION }} + type=sha,prefix= + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + cache-from: type=gha + cache-to: type=gha,mode=max deploy-edge-functions: name: Deploy Edge Functions runs-on: ubuntu-latest - needs: deploy-frontend steps: - uses: actions/checkout@v4 @@ -54,21 +73,39 @@ jobs: env: SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + deploy-to-hetzner: + name: Deploy to Hetzner + runs-on: ubuntu-latest + needs: [build-and-push] + steps: + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.HETZNER_HOST }} + username: ${{ secrets.HETZNER_USERNAME }} + key: ${{ secrets.HETZNER_SSH_KEY }} + script: | + cd /opt/eryxon-flow + docker compose pull + docker compose up -d --remove-orphans + docker image prune -f + create-release: name: Create Release runs-on: ubuntu-latest - needs: [deploy-frontend, deploy-edge-functions] + needs: [build-and-push, deploy-edge-functions, deploy-to-hetzner] permissions: contents: write steps: - uses: actions/checkout@v4 - - name: Get version - id: version - run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - - name: Create Release uses: softprops/action-gh-release@v2 with: - tag_name: v${{ steps.version.outputs.VERSION }}-${{ github.run_number }} + tag_name: v${{ needs.build-and-push.outputs.version }}-${{ github.run_number }} generate_release_notes: true + body: | + ## Docker Image + ``` + docker pull ghcr.io/${{ github.repository }}:${{ needs.build-and-push.outputs.version }} + ``` diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 00000000..fc14f1e9 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,12 @@ +# Replace app.eryxon-flow.com with your domain +app.eryxon-flow.com { + reverse_proxy app:80 + + # Security headers (Caddy adds many by default) + header { + X-Frame-Options "DENY" + X-Content-Type-Options "nosniff" + X-XSS-Protection "1; mode=block" + Referrer-Policy "strict-origin-when-cross-origin" + } +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0ea53868 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Build arguments for Supabase config (passed at build time) +ARG VITE_SUPABASE_URL +ARG VITE_SUPABASE_PUBLISHABLE_KEY + +# Build the app +RUN npm run build + +# Production stage +FROM nginx:alpine + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built assets from builder +COPY --from=builder /app/dist /usr/share/nginx/html + +# Expose port 80 +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..25bdc140 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,33 @@ +# Eryxon Flow - Production with HTTPS +# Use this for production with automatic SSL via Caddy + +services: + app: + image: ghcr.io/sheetmetalconnect/eryxon-flow:latest + container_name: eryxon-flow + restart: unless-stopped + expose: + - "80" + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + + caddy: + image: caddy:alpine + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + depends_on: + - app + +volumes: + caddy_data: + caddy_config: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..1a15c421 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,37 @@ +# Eryxon Flow - Production Deployment +# Deploy on Hetzner with Docker + +services: + eryxon-flow: + image: ghcr.io/sheetmetalconnect/eryxon-flow:latest + container_name: eryxon-flow + restart: unless-stopped + ports: + - "80:80" + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + + # Optional: Caddy reverse proxy for HTTPS (recommended) + # Uncomment below to enable automatic SSL with Let's Encrypt + # + # caddy: + # image: caddy:alpine + # container_name: caddy + # restart: unless-stopped + # ports: + # - "80:80" + # - "443:443" + # volumes: + # - ./Caddyfile:/etc/caddy/Caddyfile + # - caddy_data:/data + # - caddy_config:/config + # depends_on: + # - eryxon-flow + +# volumes: +# caddy_data: +# caddy_config: diff --git a/docs/CICD_DEPLOYMENT_PLAN.md b/docs/CICD_DEPLOYMENT_PLAN.md index fc78d03c..1fd4cb48 100644 --- a/docs/CICD_DEPLOYMENT_PLAN.md +++ b/docs/CICD_DEPLOYMENT_PLAN.md @@ -5,23 +5,25 @@ ``` Feature Branches → dev branch → main branch ↓ ↓ ↓ - CI Tests Deploy DEV Deploy PROD - ↓ ↓ - Current Supabase New Supabase - Netlify Dev Netlify Prod / Hetzner + CI Tests Deploy DEV Build Docker Image + ↓ ↓ + Netlify Dev Push to GHCR + Current Supabase ↓ + Deploy to Hetzner + New Supabase (EU) ``` ## Environments -| Environment | Frontend | Database | Trigger | -|-------------|----------|----------|---------| -| **DEV** | Netlify | Current Supabase project | Push to `dev` | -| **PROD** | Netlify or Hetzner | New Supabase project (TBD) | Push to `main` | +| Environment | Frontend | Database | Region | Trigger | +|-------------|----------|----------|--------|---------| +| **DEV** | Netlify | Current Supabase | US/EU | Push to `dev` | +| **PROD** | Hetzner (Docker) | New Supabase | EU | Push to `main` | ## Branch Strategy - `main` - Production releases (protected) -- `dev` - Development/staging environment +- `dev` - Development/staging - `feature/*`, `fix/*`, `claude/*` - Working branches → PR to `dev` ## Workflows @@ -29,55 +31,116 @@ Feature Branches → dev branch → main branch | Workflow | Trigger | Actions | |----------|---------|---------| | `ci.yml` | All PRs | Lint, type-check, build, test | -| `deploy-dev.yml` | Push to `dev` | Build + deploy to Netlify DEV + Supabase Edge Functions | -| `deploy-prod.yml` | Push to `main` | Build + deploy to Netlify PROD + Supabase Edge Functions | +| `deploy-dev.yml` | Push to `dev` | Build → Netlify + Supabase Edge Functions | +| `deploy-prod.yml` | Push to `main` | Build Docker → GHCR → Hetzner + Supabase Edge Functions | -## Required GitHub Secrets +--- -### Development Environment -- `VITE_SUPABASE_URL_DEV` - Current Supabase URL -- `VITE_SUPABASE_ANON_KEY_DEV` - Current Supabase anon key -- `SUPABASE_PROJECT_REF_DEV` - Current project ref (`vatgianzotsurljznsry`) -- `NETLIFY_SITE_ID_DEV` - Netlify dev site ID +## GitHub Secrets -### Production Environment -- `VITE_SUPABASE_URL_PROD` - New Supabase URL (configure when ready) -- `VITE_SUPABASE_ANON_KEY_PROD` - New Supabase anon key -- `SUPABASE_PROJECT_REF_PROD` - New project ref -- `NETLIFY_SITE_ID_PROD` - Netlify prod site ID +### Development +| Secret | Value | +|--------|-------| +| `VITE_SUPABASE_URL_DEV` | Current Supabase URL | +| `VITE_SUPABASE_ANON_KEY_DEV` | Current Supabase anon key | +| `SUPABASE_PROJECT_REF_DEV` | `vatgianzotsurljznsry` | +| `NETLIFY_SITE_ID_DEV` | Netlify dev site ID | +| `NETLIFY_AUTH_TOKEN` | Netlify personal access token | -### Shared -- `SUPABASE_ACCESS_TOKEN` - Supabase CLI token (from supabase.com/dashboard/account/tokens) -- `NETLIFY_AUTH_TOKEN` - Netlify personal access token +### Production +| Secret | Value | +|--------|-------| +| `VITE_SUPABASE_URL_PROD` | New Supabase URL (EU) | +| `VITE_SUPABASE_ANON_KEY_PROD` | New Supabase anon key | +| `SUPABASE_PROJECT_REF_PROD` | New project ref | +| `SUPABASE_ACCESS_TOKEN` | Supabase CLI token | +| `HETZNER_HOST` | Hetzner server IP | +| `HETZNER_USERNAME` | SSH username (e.g., `root`) | +| `HETZNER_SSH_KEY` | SSH private key | -## Setup Steps +--- -1. **Create Netlify sites** - - Create dev site (e.g., `eryxon-flow-dev`) - - Create prod site (e.g., `eryxon-flow` or custom domain) +## Hetzner Server Setup -2. **Get Netlify credentials** - - Go to User Settings → Applications → Personal access tokens - - Copy site IDs from Site Settings → General +### 1. Create Server -3. **Get Supabase access token** - - Go to supabase.com/dashboard/account/tokens - - Generate new token for CLI access +1. Go to [Hetzner Cloud Console](https://console.hetzner.cloud) +2. Create new project or use existing +3. Add server: + - **Location**: EU (Falkenstein, Nuremberg, or Helsinki) + - **Image**: Ubuntu 24.04 + - **Type**: CX22 (~€4/month) or CX32 for more resources + - **SSH Key**: Add your public key -4. **Configure GitHub secrets** - - Go to repo Settings → Secrets and variables → Actions - - Add all secrets listed above +### 2. Initial Server Setup -5. **Create `dev` branch** - ```bash - git checkout main - git checkout -b dev - git push -u origin dev - ``` +```bash +# SSH into server +ssh root@YOUR_SERVER_IP + +# Update system +apt update && apt upgrade -y + +# Install Docker +curl -fsSL https://get.docker.com | sh + +# Create app directory +mkdir -p /opt/eryxon-flow +cd /opt/eryxon-flow + +# Login to GitHub Container Registry +docker login ghcr.io -u YOUR_GITHUB_USERNAME + +# Create docker-compose.yml +cat > docker-compose.yml << 'EOF' +services: + app: + image: ghcr.io/sheetmetalconnect/eryxon-flow:latest + container_name: eryxon-flow + restart: unless-stopped + expose: + - "80" + + caddy: + image: caddy:alpine + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + - caddy_config:/config + depends_on: + - app + +volumes: + caddy_data: + caddy_config: +EOF + +# Create Caddyfile (replace domain) +cat > Caddyfile << 'EOF' +app.yourdomain.com { + reverse_proxy app:80 +} +EOF + +# Start services +docker compose up -d +``` -6. **Set branch protection** (optional) - - Require PR reviews for `main` - - Require status checks to pass +### 3. DNS Configuration + +Point your domain to the Hetzner server IP: +``` +A app.yourdomain.com YOUR_SERVER_IP +``` + +Caddy will automatically obtain SSL certificates from Let's Encrypt. + +--- ## Developer Workflow @@ -92,20 +155,109 @@ git add . git commit -m "feat: add feature" git push -u origin feature/my-feature -# Create PR to dev → CI runs automatically -# Merge to dev → Deploys to DEV environment -# Test on DEV environment -# Create PR from dev to main → Deploys to PROD +# Create PR to dev → CI runs +# Merge to dev → Deploys to DEV (Netlify) +# Test on DEV +# Create PR from dev to main +# Merge to main → Builds Docker → Deploys to PROD (Hetzner) ``` -## Future: Docker Self-Hosted +--- -For client self-hosted deployments, build Docker image: +## Docker Commands +### Local Development ```bash +# Build locally docker build -t eryxon-flow \ - --build-arg VITE_SUPABASE_URL=http://localhost:8000 \ - --build-arg VITE_SUPABASE_PUBLISHABLE_KEY=your-key . + --build-arg VITE_SUPABASE_URL=https://xxx.supabase.co \ + --build-arg VITE_SUPABASE_PUBLISHABLE_KEY=xxx . + +# Run locally +docker run -p 8080:80 eryxon-flow +``` + +### Production +```bash +# Pull latest image +docker pull ghcr.io/sheetmetalconnect/eryxon-flow:latest + +# View logs +docker logs -f eryxon-flow + +# Restart +docker compose restart + +# Full redeploy +docker compose pull && docker compose up -d --remove-orphans +``` + +--- + +## File Structure + +``` +.github/ + workflows/ + ci.yml # PR checks + deploy-dev.yml # Dev deployment (Netlify) + deploy-prod.yml # Prod deployment (Docker → Hetzner) +Dockerfile # Multi-stage build +nginx.conf # SPA routing config +docker-compose.yml # Simple deployment +docker-compose.prod.yml # Production with Caddy/SSL +Caddyfile # Caddy reverse proxy config +netlify.toml # Netlify config (dev) +``` + +--- + +## Future: Client Self-Hosted Deployments + +For clients wanting fully self-hosted (including Supabase): + +```yaml +# docker-compose.selfhosted.yml (future) +services: + app: + image: ghcr.io/sheetmetalconnect/eryxon-flow:latest + # ... app config + + # Supabase stack + postgres: + image: supabase/postgres:15.1.0.147 + kong: + image: kong:2.8.1 + auth: + image: supabase/gotrue:latest + rest: + image: postgrest/postgrest:latest + realtime: + image: supabase/realtime:latest + storage: + image: supabase/storage-api:latest + + # Redis (when PR merged) + redis: + image: redis:alpine +``` + +--- + +## Monitoring + +### Health Check +```bash +curl https://app.yourdomain.com/health +``` + +### Docker Status +```bash +docker ps +docker stats +docker logs eryxon-flow --tail 100 ``` -See `Dockerfile` and `docker-compose.yml` (to be created when needed). +### Supabase +- Monitor via Supabase Dashboard +- Edge Function logs in Dashboard → Functions → Logs diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..35ceaebf --- /dev/null +++ b/nginx.conf @@ -0,0 +1,36 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml; + + # Security headers + add_header X-Frame-Options "DENY" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Cache static assets + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA routing - serve index.html for all routes + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "OK"; + add_header Content-Type text/plain; + } +} From c10c9ad6133023ce9e74ae872d0b5317b69481d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 19:31:02 +0000 Subject: [PATCH 4/6] feat: add manual release workflow with controlled migrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add release.yml for manual releases with test → migrate → deploy sequence - Migrations require GitHub Environment approval for safety - Simplify deploy-prod.yml to just build and push Docker image - Update documentation with release process instructions Release workflow options: - run_migrations: Run database migrations (requires approval) - deploy_functions: Deploy Supabase Edge Functions --- .github/workflows/deploy-prod.yml | 76 +------- .github/workflows/release.yml | 174 +++++++++++++++++ docs/CICD_DEPLOYMENT_PLAN.md | 300 +++++++++++------------------- 3 files changed, 291 insertions(+), 259 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index aaafd96f..ab055069 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -1,5 +1,6 @@ -name: Deploy to Production +name: Build Production Image +# Auto-build on push to main (no deploy, just build) on: push: branches: [main] @@ -15,8 +16,6 @@ jobs: permissions: contents: read packages: write - outputs: - version: ${{ steps.version.outputs.VERSION }} steps: - uses: actions/checkout@v4 @@ -27,85 +26,24 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to GitHub Container Registry + - name: Login to GHCR uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=latest - type=raw,value=${{ steps.version.outputs.VERSION }} - type=sha,prefix= - - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} build-args: | VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL_PROD }} VITE_SUPABASE_PUBLISHABLE_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} cache-from: type=gha cache-to: type=gha,mode=max - - deploy-edge-functions: - name: Deploy Edge Functions - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Supabase CLI - uses: supabase/setup-cli@v1 - with: - version: latest - - - name: Deploy functions - run: supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_PROD }} - env: - SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} - - deploy-to-hetzner: - name: Deploy to Hetzner - runs-on: ubuntu-latest - needs: [build-and-push] - steps: - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ secrets.HETZNER_HOST }} - username: ${{ secrets.HETZNER_USERNAME }} - key: ${{ secrets.HETZNER_SSH_KEY }} - script: | - cd /opt/eryxon-flow - docker compose pull - docker compose up -d --remove-orphans - docker image prune -f - - create-release: - name: Create Release - runs-on: ubuntu-latest - needs: [build-and-push, deploy-edge-functions, deploy-to-hetzner] - permissions: - contents: write - steps: - - uses: actions/checkout@v4 - - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ needs.build-and-push.outputs.version }}-${{ github.run_number }} - generate_release_notes: true - body: | - ## Docker Image - ``` - docker pull ghcr.io/${{ github.repository }}:${{ needs.build-and-push.outputs.version }} - ``` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..34b95c90 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,174 @@ +name: Release + +# Manual trigger for controlled releases +on: + workflow_dispatch: + inputs: + run_migrations: + description: 'Run database migrations' + required: true + default: 'false' + type: boolean + deploy_functions: + description: 'Deploy Edge Functions' + required: true + default: 'true' + type: boolean + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + # Step 1: Run all tests + test: + name: 1. Run Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Type check + run: npx tsc --noEmit + + - name: Build (verify) + run: npm run build + env: + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + + - name: Run tests + run: npm run test --if-present + + # Step 2: Run migrations (if enabled, requires approval) + migrate: + name: 2. Run Migrations + runs-on: ubuntu-latest + needs: test + if: ${{ inputs.run_migrations == true }} + environment: production # Requires manual approval + steps: + - uses: actions/checkout@v4 + + - name: Setup Supabase CLI + uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Link to production project + run: supabase link --project-ref ${{ secrets.SUPABASE_PROJECT_REF_PROD }} + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + + - name: Run migrations + run: supabase db push --linked + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + + # Step 3: Deploy Edge Functions (if enabled) + deploy-functions: + name: 3. Deploy Edge Functions + runs-on: ubuntu-latest + needs: [test, migrate] + if: | + always() && + needs.test.result == 'success' && + (needs.migrate.result == 'success' || needs.migrate.result == 'skipped') && + inputs.deploy_functions == true + steps: + - uses: actions/checkout@v4 + + - name: Setup Supabase CLI + uses: supabase/setup-cli@v1 + with: + version: latest + + - name: Deploy Edge Functions + run: supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_PROD }} + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + + # Step 4: Build and push Docker image + build-and-push: + name: 4. Build & Push Docker Image + runs-on: ubuntu-latest + needs: [test, migrate, deploy-functions] + if: | + always() && + needs.test.result == 'success' && + (needs.migrate.result == 'success' || needs.migrate.result == 'skipped') && + (needs.deploy-functions.result == 'success' || needs.deploy-functions.result == 'skipped') + permissions: + contents: read + packages: write + outputs: + version: ${{ steps.version.outputs.VERSION }} + steps: + - uses: actions/checkout@v4 + + - name: Get version + id: version + run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} + build-args: | + VITE_SUPABASE_URL=${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY=${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Step 5: Create GitHub release + create-release: + name: 5. Create Release + runs-on: ubuntu-latest + needs: build-and-push + if: needs.build-and-push.result == 'success' + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ needs.build-and-push.outputs.version }} + generate_release_notes: true + body: | + ## Release v${{ needs.build-and-push.outputs.version }} + + **What was deployed:** + - Migrations: ${{ inputs.run_migrations }} + - Edge Functions: ${{ inputs.deploy_functions }} + + **Docker Image:** + ``` + docker pull ghcr.io/${{ github.repository }}:${{ needs.build-and-push.outputs.version }} + ``` diff --git a/docs/CICD_DEPLOYMENT_PLAN.md b/docs/CICD_DEPLOYMENT_PLAN.md index 1fd4cb48..23519d1b 100644 --- a/docs/CICD_DEPLOYMENT_PLAN.md +++ b/docs/CICD_DEPLOYMENT_PLAN.md @@ -6,33 +6,66 @@ Feature Branches → dev branch → main branch ↓ ↓ ↓ CI Tests Deploy DEV Build Docker Image - ↓ ↓ - Netlify Dev Push to GHCR - Current Supabase ↓ - Deploy to Hetzner - New Supabase (EU) + (Netlify) (auto push to GHCR) + ↓ + Current Supabase + + ┌─────────────────────────────┐ + │ Manual Release Workflow │ + └─────────────────────────────┘ + ↓ + 1. Run Tests + ↓ + 2. Run Migrations (optional) + ↓ + 3. Deploy Edge Functions + ↓ + 4. Build & Push Docker Image + ↓ + 5. Create GitHub Release ``` ## Environments -| Environment | Frontend | Database | Region | Trigger | -|-------------|----------|----------|--------|---------| -| **DEV** | Netlify | Current Supabase | US/EU | Push to `dev` | -| **PROD** | Hetzner (Docker) | New Supabase | EU | Push to `main` | - -## Branch Strategy - -- `main` - Production releases (protected) -- `dev` - Development/staging -- `feature/*`, `fix/*`, `claude/*` - Working branches → PR to `dev` +| Environment | Frontend | Database | Trigger | +|-------------|----------|----------|---------| +| **DEV** | Netlify | Current Supabase | Push to `dev` | +| **PROD** | Docker Image (GHCR) | New Supabase (EU) | Manual release workflow | ## Workflows -| Workflow | Trigger | Actions | +| Workflow | Trigger | Purpose | |----------|---------|---------| -| `ci.yml` | All PRs | Lint, type-check, build, test | -| `deploy-dev.yml` | Push to `dev` | Build → Netlify + Supabase Edge Functions | -| `deploy-prod.yml` | Push to `main` | Build Docker → GHCR → Hetzner + Supabase Edge Functions | +| `ci.yml` | PRs to dev/main | Lint, type-check, build, test | +| `deploy-dev.yml` | Push to `dev` | Auto-deploy frontend to Netlify | +| `deploy-prod.yml` | Push to `main` | Auto-build Docker image to GHCR | +| `release.yml` | **Manual** | Full controlled release with migrations | + +--- + +## Release Process + +### Quick Release (no migrations) +1. Go to **Actions → Release → Run workflow** +2. Set `run_migrations: false` +3. Set `deploy_functions: true` +4. Click **Run workflow** + +### Full Release (with migrations) +1. Go to **Actions → Release → Run workflow** +2. Set `run_migrations: true` +3. Set `deploy_functions: true` +4. Click **Run workflow** +5. **Approve** the migration step when prompted (GitHub Environment protection) + +### Release Steps +``` +1. Run Tests → Lint, type-check, build, unit tests +2. Run Migrations → (if enabled) supabase db push to PROD +3. Deploy Functions → (if enabled) Deploy Edge Functions to PROD +4. Build Image → Build Docker with PROD config → push to GHCR +5. Create Release → Tag version, generate changelog +``` --- @@ -50,214 +83,101 @@ Feature Branches → dev branch → main branch ### Production | Secret | Value | |--------|-------| -| `VITE_SUPABASE_URL_PROD` | New Supabase URL (EU) | +| `VITE_SUPABASE_URL_PROD` | New Supabase URL (EU region) | | `VITE_SUPABASE_ANON_KEY_PROD` | New Supabase anon key | -| `SUPABASE_PROJECT_REF_PROD` | New project ref | +| `SUPABASE_PROJECT_REF_PROD` | New Supabase project ref | | `SUPABASE_ACCESS_TOKEN` | Supabase CLI token | -| `HETZNER_HOST` | Hetzner server IP | -| `HETZNER_USERNAME` | SSH username (e.g., `root`) | -| `HETZNER_SSH_KEY` | SSH private key | --- -## Hetzner Server Setup +## Setup Steps -### 1. Create Server +### 1. Create Supabase Production Project (EU) +1. Go to [supabase.com](https://supabase.com) +2. Create new project → Select **EU region** (Frankfurt) +3. Note: project URL, anon key, project ref -1. Go to [Hetzner Cloud Console](https://console.hetzner.cloud) -2. Create new project or use existing -3. Add server: - - **Location**: EU (Falkenstein, Nuremberg, or Helsinki) - - **Image**: Ubuntu 24.04 - - **Type**: CX22 (~€4/month) or CX32 for more resources - - **SSH Key**: Add your public key +### 2. Create Netlify Dev Site +1. Go to [netlify.com](https://netlify.com) +2. Create site for dev environment +3. Get site ID and personal access token -### 2. Initial Server Setup +### 3. Configure GitHub +1. Add secrets: Settings → Secrets → Actions +2. Create Environment "production" with required reviewers +3. Create `dev` branch from `main` +### 4. Initial Production Migration ```bash -# SSH into server -ssh root@YOUR_SERVER_IP - -# Update system -apt update && apt upgrade -y - -# Install Docker -curl -fsSL https://get.docker.com | sh - -# Create app directory -mkdir -p /opt/eryxon-flow -cd /opt/eryxon-flow - -# Login to GitHub Container Registry -docker login ghcr.io -u YOUR_GITHUB_USERNAME - -# Create docker-compose.yml -cat > docker-compose.yml << 'EOF' -services: - app: - image: ghcr.io/sheetmetalconnect/eryxon-flow:latest - container_name: eryxon-flow - restart: unless-stopped - expose: - - "80" - - caddy: - image: caddy:alpine - container_name: caddy - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./Caddyfile:/etc/caddy/Caddyfile:ro - - caddy_data:/data - - caddy_config:/config - depends_on: - - app - -volumes: - caddy_data: - caddy_config: -EOF - -# Create Caddyfile (replace domain) -cat > Caddyfile << 'EOF' -app.yourdomain.com { - reverse_proxy app:80 -} -EOF - -# Start services -docker compose up -d +supabase link --project-ref YOUR_PROD_PROJECT_REF +supabase db push ``` -### 3. DNS Configuration - -Point your domain to the Hetzner server IP: -``` -A app.yourdomain.com YOUR_SERVER_IP -``` - -Caddy will automatically obtain SSL certificates from Let's Encrypt. - --- -## Developer Workflow +## Docker Image +### Available at GHCR ```bash -# Start new feature -git checkout dev -git pull origin dev -git checkout -b feature/my-feature +# Latest +docker pull ghcr.io/sheetmetalconnect/eryxon-flow:latest -# Work on feature... -git add . -git commit -m "feat: add feature" -git push -u origin feature/my-feature +# Specific version +docker pull ghcr.io/sheetmetalconnect/eryxon-flow:1.0.0 -# Create PR to dev → CI runs -# Merge to dev → Deploys to DEV (Netlify) -# Test on DEV -# Create PR from dev to main -# Merge to main → Builds Docker → Deploys to PROD (Hetzner) +# By commit SHA +docker pull ghcr.io/sheetmetalconnect/eryxon-flow:abc1234 ``` ---- - -## Docker Commands - -### Local Development +### Run Anywhere ```bash -# Build locally -docker build -t eryxon-flow \ - --build-arg VITE_SUPABASE_URL=https://xxx.supabase.co \ - --build-arg VITE_SUPABASE_PUBLISHABLE_KEY=xxx . - -# Run locally -docker run -p 8080:80 eryxon-flow +docker run -d -p 80:80 ghcr.io/sheetmetalconnect/eryxon-flow:latest ``` -### Production -```bash -# Pull latest image -docker pull ghcr.io/sheetmetalconnect/eryxon-flow:latest +--- -# View logs -docker logs -f eryxon-flow +## Future: Hetzner Deployment -# Restart -docker compose restart +When ready, add deploy step to `release.yml`: -# Full redeploy -docker compose pull && docker compose up -d --remove-orphans -``` +1. Create Hetzner server (CX22, ~€4/mo, EU) +2. Install Docker on server +3. Add secrets: `HETZNER_HOST`, `HETZNER_USERNAME`, `HETZNER_SSH_KEY` +4. Use prepared files: + - `docker-compose.prod.yml` - With Caddy SSL + - `Caddyfile` - Reverse proxy --- ## File Structure ``` -.github/ - workflows/ - ci.yml # PR checks - deploy-dev.yml # Dev deployment (Netlify) - deploy-prod.yml # Prod deployment (Docker → Hetzner) -Dockerfile # Multi-stage build -nginx.conf # SPA routing config -docker-compose.yml # Simple deployment -docker-compose.prod.yml # Production with Caddy/SSL -Caddyfile # Caddy reverse proxy config -netlify.toml # Netlify config (dev) +.github/workflows/ + ci.yml # PR checks + deploy-dev.yml # Dev → Netlify (auto) + deploy-prod.yml # Main → Docker image (auto) + release.yml # Manual release with migrations +Dockerfile # Multi-stage build +nginx.conf # SPA routing +docker-compose.yml # Simple deployment +docker-compose.prod.yml # With Caddy SSL +Caddyfile # Caddy config +netlify.toml # Netlify config ``` --- -## Future: Client Self-Hosted Deployments - -For clients wanting fully self-hosted (including Supabase): - -```yaml -# docker-compose.selfhosted.yml (future) -services: - app: - image: ghcr.io/sheetmetalconnect/eryxon-flow:latest - # ... app config - - # Supabase stack - postgres: - image: supabase/postgres:15.1.0.147 - kong: - image: kong:2.8.1 - auth: - image: supabase/gotrue:latest - rest: - image: postgrest/postgrest:latest - realtime: - image: supabase/realtime:latest - storage: - image: supabase/storage-api:latest - - # Redis (when PR merged) - redis: - image: redis:alpine -``` - ---- - -## Monitoring +## Developer Workflow -### Health Check ```bash -curl https://app.yourdomain.com/health -``` +# Daily development +git checkout dev && git pull +git checkout -b feature/my-feature +# ... work ... +git push -u origin feature/my-feature +# Create PR → CI runs → Merge → Auto-deploys to Netlify DEV -### Docker Status -```bash -docker ps -docker stats -docker logs eryxon-flow --tail 100 +# Ready for production +# Create PR: dev → main → Merge → Auto-builds Docker image +# Then: Actions → Release → Run workflow (choose options) ``` - -### Supabase -- Monitor via Supabase Dashboard -- Edge Function logs in Dashboard → Functions → Logs From bccaecefa8e55aec625b82dea787bde22104eadb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 19:33:03 +0000 Subject: [PATCH 5/6] refactor: simplify CI/CD - remove Netlify, keep Lovable for dev - Remove deploy-dev.yml (Netlify workflow) - Remove netlify.toml - Update docs: Lovable for dev, Docker for prod/local/on-premise - Add customer on-premise deployment instructions --- .github/workflows/deploy-dev.yml | 54 -------- docs/CICD_DEPLOYMENT_PLAN.md | 215 +++++++++++++++++++------------ netlify.toml | 27 ---- 3 files changed, 134 insertions(+), 162 deletions(-) delete mode 100644 .github/workflows/deploy-dev.yml delete mode 100644 netlify.toml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml deleted file mode 100644 index 7d936e74..00000000 --- a/.github/workflows/deploy-dev.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Deploy to Development - -on: - push: - branches: [dev] - -jobs: - deploy-frontend: - name: Deploy Frontend - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - env: - VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_DEV }} - VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_DEV }} - - - name: Deploy to Netlify - uses: nwtgck/actions-netlify@v3 - with: - publish-dir: './dist' - production-deploy: true - github-token: ${{ secrets.GITHUB_TOKEN }} - deploy-message: "Dev deploy - ${{ github.sha }}" - env: - NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} - NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_DEV }} - - deploy-edge-functions: - name: Deploy Edge Functions - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Supabase CLI - uses: supabase/setup-cli@v1 - with: - version: latest - - - name: Deploy functions - run: supabase functions deploy --project-ref ${{ secrets.SUPABASE_PROJECT_REF_DEV }} - env: - SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} diff --git a/docs/CICD_DEPLOYMENT_PLAN.md b/docs/CICD_DEPLOYMENT_PLAN.md index 23519d1b..1b02efdc 100644 --- a/docs/CICD_DEPLOYMENT_PLAN.md +++ b/docs/CICD_DEPLOYMENT_PLAN.md @@ -3,41 +3,40 @@ ## Architecture ``` -Feature Branches → dev branch → main branch - ↓ ↓ ↓ - CI Tests Deploy DEV Build Docker Image - (Netlify) (auto push to GHCR) - ↓ - Current Supabase - - ┌─────────────────────────────┐ - │ Manual Release Workflow │ - └─────────────────────────────┘ - ↓ - 1. Run Tests - ↓ - 2. Run Migrations (optional) - ↓ - 3. Deploy Edge Functions - ↓ - 4. Build & Push Docker Image - ↓ - 5. Create GitHub Release +Feature Branches → main branch + ↓ ↓ + CI Tests Build Docker Image + (push to GHCR) + + ┌─────────────────────────────────────────────┐ + │ Manual Release Workflow │ + └─────────────────────────────────────────────┘ + ↓ + 1. Run Tests + ↓ + 2. Run Migrations (optional) + ↓ + 3. Deploy Edge Functions + ↓ + 4. Build & Push Docker Image + ↓ + 5. Create GitHub Release ``` ## Environments -| Environment | Frontend | Database | Trigger | +| Environment | Frontend | Database | Purpose | |-------------|----------|----------|---------| -| **DEV** | Netlify | Current Supabase | Push to `dev` | -| **PROD** | Docker Image (GHCR) | New Supabase (EU) | Manual release workflow | +| **DEV** | Lovable | Current Supabase | Development & testing | +| **PROD** | Docker (Hetzner) | New Supabase (EU) | Production | +| **Local** | Docker | DEV or PROD Supabase | Local testing | +| **On-Premise** | Docker | Customer Supabase | Customer deployments | ## Workflows | Workflow | Trigger | Purpose | |----------|---------|---------| -| `ci.yml` | PRs to dev/main | Lint, type-check, build, test | -| `deploy-dev.yml` | Push to `dev` | Auto-deploy frontend to Netlify | +| `ci.yml` | PRs | Lint, type-check, build, test | | `deploy-prod.yml` | Push to `main` | Auto-build Docker image to GHCR | | `release.yml` | **Manual** | Full controlled release with migrations | @@ -56,36 +55,17 @@ Feature Branches → dev branch → main branch 2. Set `run_migrations: true` 3. Set `deploy_functions: true` 4. Click **Run workflow** -5. **Approve** the migration step when prompted (GitHub Environment protection) - -### Release Steps -``` -1. Run Tests → Lint, type-check, build, unit tests -2. Run Migrations → (if enabled) supabase db push to PROD -3. Deploy Functions → (if enabled) Deploy Edge Functions to PROD -4. Build Image → Build Docker with PROD config → push to GHCR -5. Create Release → Tag version, generate changelog -``` +5. **Approve** the migration step (GitHub Environment protection) --- ## GitHub Secrets -### Development | Secret | Value | |--------|-------| -| `VITE_SUPABASE_URL_DEV` | Current Supabase URL | -| `VITE_SUPABASE_ANON_KEY_DEV` | Current Supabase anon key | -| `SUPABASE_PROJECT_REF_DEV` | `vatgianzotsurljznsry` | -| `NETLIFY_SITE_ID_DEV` | Netlify dev site ID | -| `NETLIFY_AUTH_TOKEN` | Netlify personal access token | - -### Production -| Secret | Value | -|--------|-------| -| `VITE_SUPABASE_URL_PROD` | New Supabase URL (EU region) | -| `VITE_SUPABASE_ANON_KEY_PROD` | New Supabase anon key | -| `SUPABASE_PROJECT_REF_PROD` | New Supabase project ref | +| `VITE_SUPABASE_URL_PROD` | Supabase URL (EU region) | +| `VITE_SUPABASE_ANON_KEY_PROD` | Supabase anon key | +| `SUPABASE_PROJECT_REF_PROD` | Supabase project ref | | `SUPABASE_ACCESS_TOKEN` | Supabase CLI token | --- @@ -97,17 +77,11 @@ Feature Branches → dev branch → main branch 2. Create new project → Select **EU region** (Frankfurt) 3. Note: project URL, anon key, project ref -### 2. Create Netlify Dev Site -1. Go to [netlify.com](https://netlify.com) -2. Create site for dev environment -3. Get site ID and personal access token - -### 3. Configure GitHub +### 2. Configure GitHub 1. Add secrets: Settings → Secrets → Actions 2. Create Environment "production" with required reviewers -3. Create `dev` branch from `main` -### 4. Initial Production Migration +### 3. Initial Production Migration ```bash supabase link --project-ref YOUR_PROD_PROJECT_REF supabase db push @@ -117,35 +91,117 @@ supabase db push ## Docker Image -### Available at GHCR +### Pull from GHCR ```bash # Latest docker pull ghcr.io/sheetmetalconnect/eryxon-flow:latest # Specific version docker pull ghcr.io/sheetmetalconnect/eryxon-flow:1.0.0 +``` -# By commit SHA -docker pull ghcr.io/sheetmetalconnect/eryxon-flow:abc1234 +### Run Locally (for testing) +```bash +docker run -p 8080:80 ghcr.io/sheetmetalconnect/eryxon-flow:latest +# Open http://localhost:8080 ``` -### Run Anywhere +### Build Locally (custom Supabase) ```bash -docker run -d -p 80:80 ghcr.io/sheetmetalconnect/eryxon-flow:latest +docker build -t eryxon-flow \ + --build-arg VITE_SUPABASE_URL=https://your-project.supabase.co \ + --build-arg VITE_SUPABASE_PUBLISHABLE_KEY=your-anon-key . + +docker run -p 8080:80 eryxon-flow ``` --- -## Future: Hetzner Deployment +## Hetzner Production Deployment -When ready, add deploy step to `release.yml`: +### 1. Create Server +1. [Hetzner Cloud Console](https://console.hetzner.cloud) +2. Create server: Ubuntu 24.04, CX22 (~€4/mo), EU region +3. Add SSH key + +### 2. Server Setup +```bash +ssh root@YOUR_SERVER_IP + +# Install Docker +curl -fsSL https://get.docker.com | sh + +# Create app directory +mkdir -p /opt/eryxon-flow +cd /opt/eryxon-flow + +# Login to GHCR +docker login ghcr.io -u YOUR_GITHUB_USERNAME + +# Create docker-compose.yml +cat > docker-compose.yml << 'EOF' +services: + app: + image: ghcr.io/sheetmetalconnect/eryxon-flow:latest + container_name: eryxon-flow + restart: unless-stopped + expose: + - "80" + + caddy: + image: caddy:alpine + container_name: caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy_data:/data + +volumes: + caddy_data: +EOF + +# Create Caddyfile +cat > Caddyfile << 'EOF' +app.yourdomain.com { + reverse_proxy app:80 +} +EOF + +# Start +docker compose up -d +``` -1. Create Hetzner server (CX22, ~€4/mo, EU) -2. Install Docker on server -3. Add secrets: `HETZNER_HOST`, `HETZNER_USERNAME`, `HETZNER_SSH_KEY` -4. Use prepared files: - - `docker-compose.prod.yml` - With Caddy SSL - - `Caddyfile` - Reverse proxy +### 3. DNS +Point `app.yourdomain.com` → Server IP. Caddy handles SSL automatically. + +### 4. Update Production +```bash +cd /opt/eryxon-flow +docker compose pull +docker compose up -d --remove-orphans +``` + +--- + +## Customer On-Premise Deployment + +Same Docker image, customer provides their own Supabase: + +```bash +# Build with customer's Supabase +docker build -t eryxon-flow-customer \ + --build-arg VITE_SUPABASE_URL=https://customer-project.supabase.co \ + --build-arg VITE_SUPABASE_PUBLISHABLE_KEY=customer-anon-key . + +# Or use docker-compose with env vars +cat > .env << EOF +SUPABASE_URL=https://customer-project.supabase.co +SUPABASE_ANON_KEY=customer-anon-key +EOF +``` --- @@ -154,7 +210,6 @@ When ready, add deploy step to `release.yml`: ``` .github/workflows/ ci.yml # PR checks - deploy-dev.yml # Dev → Netlify (auto) deploy-prod.yml # Main → Docker image (auto) release.yml # Manual release with migrations Dockerfile # Multi-stage build @@ -162,7 +217,6 @@ nginx.conf # SPA routing docker-compose.yml # Simple deployment docker-compose.prod.yml # With Caddy SSL Caddyfile # Caddy config -netlify.toml # Netlify config ``` --- @@ -170,14 +224,13 @@ netlify.toml # Netlify config ## Developer Workflow ```bash -# Daily development -git checkout dev && git pull -git checkout -b feature/my-feature -# ... work ... -git push -u origin feature/my-feature -# Create PR → CI runs → Merge → Auto-deploys to Netlify DEV - -# Ready for production -# Create PR: dev → main → Merge → Auto-builds Docker image -# Then: Actions → Release → Run workflow (choose options) +# Development (use Lovable) +# Push to GitHub → Lovable auto-syncs + +# Ready for production release +# Actions → Release → Run workflow +# Docker image built and pushed to GHCR + +# Deploy to Hetzner +ssh root@server "cd /opt/eryxon-flow && docker compose pull && docker compose up -d" ``` diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 4542550b..00000000 --- a/netlify.toml +++ /dev/null @@ -1,27 +0,0 @@ -[build] - publish = "dist" - command = "npm run build" - -[build.environment] - NODE_VERSION = "20" - -# SPA routing - all routes to index.html -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - -# Security headers -[[headers]] - for = "/*" - [headers.values] - X-Frame-Options = "DENY" - X-XSS-Protection = "1; mode=block" - X-Content-Type-Options = "nosniff" - Referrer-Policy = "strict-origin-when-cross-origin" - -# Cache static assets (1 year) -[[headers]] - for = "/assets/*" - [headers.values] - Cache-Control = "public, max-age=31536000, immutable" From c6d31d1e01b3adebf30130a94edecdebb3ba7799 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 09:53:22 +0000 Subject: [PATCH 6/6] fix: review and improve CI/CD setup - ci.yml: Remove dev branch reference (using Lovable for dev) - ci.yml: Use PROD secrets for build verification - Dockerfile: Add ENV declarations so Vite picks up build args - README: Add CI/CD section with deployment environments table - README: Link to CICD_DEPLOYMENT_PLAN.md in documentation list --- .github/workflows/ci.yml | 6 +++--- Dockerfile | 4 ++++ README.md | 26 +++++++++++++++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50925777..47d9ba5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: pull_request: - branches: [main, dev] + branches: [main] jobs: lint-and-typecheck: @@ -45,8 +45,8 @@ jobs: - name: Build run: npm run build env: - VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_DEV }} - VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_DEV }} + VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL_PROD }} + VITE_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY_PROD }} test: name: Test diff --git a/Dockerfile b/Dockerfile index 0ea53868..f4a5e9a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,10 @@ COPY . . ARG VITE_SUPABASE_URL ARG VITE_SUPABASE_PUBLISHABLE_KEY +# Set as environment variables for Vite build +ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL +ENV VITE_SUPABASE_PUBLISHABLE_KEY=$VITE_SUPABASE_PUBLISHABLE_KEY + # Build the app RUN npm run build diff --git a/README.md b/README.md index 0580e9e9..13dda229 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ Comprehensive documentation is available in the [`/docs`](./docs) folder: - **[API_DOCUMENTATION.md](docs/API_DOCUMENTATION.md)** - REST API reference - **[DESIGN_SYSTEM.md](docs/DESIGN_SYSTEM.md)** - Design tokens and styling - **[EDGE_FUNCTIONS_SETUP.md](docs/EDGE_FUNCTIONS_SETUP.md)** - Edge Functions guide +- **[CICD_DEPLOYMENT_PLAN.md](docs/CICD_DEPLOYMENT_PLAN.md)** - CI/CD pipeline and Docker deployment - **[CLAUDE.md](CLAUDE.md)** - AI assistant guide for contributors Additional documentation: @@ -117,11 +118,26 @@ Additional documentation: ## 📦 Deployment -Deployed via [Lovable Platform](https://lovable.dev/projects/aaa3208a-70fb-4eb6-a5eb-5823f025e734) +| Environment | Platform | Details | +|-------------|----------|---------| +| **Development** | [Lovable](https://lovable.dev/projects/aaa3208a-70fb-4eb6-a5eb-5823f025e734) | Auto-syncs with GitHub | +| **Production** | Docker on Hetzner | EU-hosted, fixed costs | +| **Local/On-Premise** | Docker | Same image, custom Supabase | -To deploy updates: **Share → Publish** +### CI/CD Pipeline -For custom domains, see [Lovable docs](https://docs.lovable.dev/features/custom-domain) +- **PRs**: Automated lint, type-check, build, test +- **Releases**: Manual workflow with migrations and Edge Functions deployment +- **Docker Images**: Published to GitHub Container Registry (GHCR) + +See **[docs/CICD_DEPLOYMENT_PLAN.md](docs/CICD_DEPLOYMENT_PLAN.md)** for full setup instructions. + +### Quick Docker Run + +```bash +docker pull ghcr.io/sheetmetalconnect/eryxon-flow:latest +docker run -p 8080:80 ghcr.io/sheetmetalconnect/eryxon-flow:latest +``` ## 📄 License @@ -133,6 +149,6 @@ This software is for internal use only and may not be distributed, copied, or mo --- -**Built with** React 18 + TypeScript + Supabase -**Status**: Production +**Built with** React 18 + TypeScript + Supabase +**Status**: Production **Version**: 1.2