RippleCore
Infrastructure

CI/CD Pipeline

Complete CI/CD implementation using GitHub Actions + Dokploy

CI/CD Pipeline Setup Guide

Complete CI/CD implementation using GitHub Actions + Dokploy

Pipeline: GitHub Actions (CI) → GitHub Container Registry (Artifacts) → Dokploy (CD) Deployment Time: 5-10 minutes (test → build → deploy) Zero-Downtime: Yes (rolling deployments with health checks)


Table of Contents


Pipeline Architecture

CI/CD Flow Diagram

┌──────────────────────────────────────────────────────┐
│ 1. DEVELOPER WORKFLOW                                        │
├──────────────────────────────────────────────────────┤
│ git commit -m "feat: add feature"                           │
│ git push origin feature/new-feature                          │
│                                                              │
│ → Creates PR to staging branch                              │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│ 2. GITHUB ACTIONS (CI PHASE)                                │
├──────────────────────────────────────────────────────┤
│ ✓ Checkout code                                              │
│ ✓ Install dependencies (pnpm)                                │
│ ✓ Lint & Type Check (Biome)                                  │
│ ✓ Run Tests (Vitest + E2E)                                   │
│ ✓ Build Docker images (multi-stage)                          │
│ ✓ Security scan (Trivy)                                      │
│ ✓ Push to GitHub Container Registry                          │
│                                                              │
│ Duration: 5-8 minutes                                        │
│ Parallelization: Build matrix (app, api, web)               │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│ 3. PREVIEW DEPLOYMENT (for PRs)                              │
├──────────────────────────────────────────────────────┤
│ Dokploy creates ephemeral environment                        │
│ URL: https://pr-123.staging.your-domain.com                  │
│                                                              │
│ Auto-cleanup: 7 days or on PR merge                          │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│ 4. STAGING DEPLOYMENT (on merge to staging)                 │
├──────────────────────────────────────────────────────┤
│ Dokploy webhook triggered                                    │
│ → Pull latest images from registry                           │
│ → Run database migrations (if needed)                        │
│ → Deploy with zero downtime                                  │
│ → Health check validation                                    │
│                                                              │
│ URL: https://staging.your-domain.com                         │
│ Duration: 2-3 minutes                                        │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│ 5. PRODUCTION DEPLOYMENT (on merge to main)                 │
├──────────────────────────────────────────────────────┤
│ Manual approval required (GitHub Environment)                │
│ → Dokploy webhook triggered                                  │
│ → Rolling deployment (2 replicas)                            │
│ → Health checks (30s interval, 3 retries)                    │
│ → Automatic rollback on failure                              │
│ → Slack notification on completion                           │
│                                                              │
│ URL: https://app.your-domain.com                             │
│ Duration: 2-3 minutes                                        │
└──────────────────────────────────────────────────────┘

GitHub Actions Setup

Prerequisites

  1. GitHub Repository Secrets

Navigate to: Settings → Secrets and variables → Actions

Add these secrets:

DOKPLOY_API_KEY          # Dokploy API key (generate in Dokploy UI)
SLACK_WEBHOOK_URL        # Slack incoming webhook URL
GHCR_PAT                 # GitHub Personal Access Token (packages:write)
  1. GitHub Environments

Create these environments in: Settings → Environments

  • staging (no approval required)
  • production (required reviewers: 1+)

GitHub Actions Workflow

File: .github/workflows/deploy.yml

name: CI/CD Pipeline

on:
  push:
    branches: [main, staging, develop]
  pull_request:
    branches: [main, staging]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ═════════════════════════════════════════════════════
  # CI PHASE: Testing & Quality Checks
  # ═════════════════════════════════════════════════════

  test:
    name: Test & Lint
    runs-on: ubuntu-latest
    timeout-minutes: 15

    services:
      postgres:
        image: postgres:18-alpine
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: ripplecore_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

      redis:
        image: redis:7-alpine
        ports:
          - 6379:6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 9

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: Install Dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint & Type Check
        run: pnpm check

      - name: Run Unit Tests
        run: pnpm test
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/ripplecore_test
          REDIS_URL: redis://localhost:6379

      - name: Run E2E Tests
        run: pnpm test:e2e
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/ripplecore_test
          REDIS_URL: redis://localhost:6379
          BETTER_AUTH_SECRET: test-secret-for-ci-only
          BETTER_AUTH_URL: http://localhost:3000

  # ═════════════════════════════════════════════════════
  # BUILD PHASE: Docker Images
  # ═════════════════════════════════════════════════════

  build:
    name: Build Docker Images
    needs: test
    runs-on: ubuntu-latest
    timeout-minutes: 20

    permissions:
      contents: read
      packages: write

    strategy:
      matrix:
        app: [app, api, web]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract Docker Metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.app }}
          tags: |
            type=ref,event=branch
            type=ref,event=pr
            type=sha,prefix={{branch}}-
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: apps/${{ matrix.app }}/Dockerfile
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            NODE_ENV=production

  # ═════════════════════════════════════════════════════
  # SECURITY PHASE: Vulnerability Scanning
  # ═════════════════════════════════════════════════════

  security:
    name: Security Scan
    needs: build
    runs-on: ubuntu-latest
    timeout-minutes: 10

    permissions:
      contents: read
      security-events: write

    strategy:
      matrix:
        app: [app, api, web]

    steps:
      - name: Run Trivy Vulnerability Scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ matrix.app }}:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results-${{ matrix.app }}.sarif'
          severity: 'CRITICAL,HIGH'

      - name: Upload Trivy Results to GitHub Security
        uses: github/codeql-action/upload-sarif@v2
        if: always()
        with:
          sarif_file: 'trivy-results-${{ matrix.app }}.sarif'
          category: 'trivy-${{ matrix.app }}'

  # ═════════════════════════════════════════════════════
  # PREVIEW DEPLOYMENT: PR Environments
  # ═════════════════════════════════════════════════════

  deploy-preview:
    name: Deploy Preview Environment
    needs: [build, security]
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Trigger Dokploy Preview Deployment
        run: |
          PR_NUMBER=${{ github.event.pull_request.number }}
          curl -X POST https://dokploy.your-domain.com/api/preview/deploy \
            -H "Authorization: Bearer ${{ secrets.DOKPLOY_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "pr": "'$PR_NUMBER'",
              "branch": "${{ github.head_ref }}",
              "imageTag": "${{ github.sha }}"
            }'

      - name: Wait for Preview Deployment
        run: |
          PR_NUMBER=${{ github.event.pull_request.number }}
          max_attempts=30
          attempt=0

          while [ $attempt -lt $max_attempts ]; do
            if curl -f https://pr-$PR_NUMBER.staging.your-domain.com/api/health; then
              echo "✅ Preview environment is healthy"
              exit 0
            fi
            attempt=$((attempt + 1))
            echo "Waiting for preview environment... ($attempt/$max_attempts)"
            sleep 10
          done

          echo "❌ Preview deployment health check failed"
          exit 1

      - name: Comment PR with Preview URL
        uses: actions/github-script@v6
        with:
          script: |
            const prNumber = context.payload.pull_request.number;
            const previewUrl = `https://pr-${prNumber}.staging.your-domain.com`;

            github.rest.issues.createComment({
              issue_number: prNumber,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `
## 🚀 Preview Deployment Ready

Your changes have been deployed to a preview environment:

**URL**: ${previewUrl}

This environment will be automatically deleted when:
- The PR is merged or closed
- 7 days have passed since last update

---
<sub>Deployment ID: \`${{ github.sha }}\` | Build: [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})</sub>
              `
            });

  # ═════════════════════════════════════════════════════
  # STAGING DEPLOYMENT
  # ═════════════════════════════════════════════════════

  deploy-staging:
    name: Deploy to Staging
    needs: [build, security]
    if: github.ref == 'refs/heads/staging' && github.event_name == 'push'
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: staging

    steps:
      - name: Trigger Dokploy Staging Deployment
        run: |
          curl -X POST https://dokploy.your-domain.com/api/deploy \
            -H "Authorization: Bearer ${{ secrets.DOKPLOY_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "project": "ripplecore-staging",
              "services": ["app", "api", "web"],
              "imageTag": "${{ github.sha }}"
            }'

      - name: Wait for Staging Health Checks
        run: |
          services=("https://staging.your-domain.com" "https://api.staging.your-domain.com" "https://www.staging.your-domain.com")
          max_attempts=30

          for service in "${services[@]}"; do
            attempt=0
            echo "Checking $service..."

            while [ $attempt -lt $max_attempts ]; do
              if curl -f $service/api/health; then
                echo "✅ $service is healthy"
                break
              fi
              attempt=$((attempt + 1))
              sleep 10
            done

            if [ $attempt -eq $max_attempts ]; then
              echo "❌ $service health check failed"
              exit 1
            fi
          done

      - name: Notify Slack (Staging)
        if: always()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: |
            Staging Deployment: ${{ job.status }}
            Branch: staging
            Commit: ${{ github.sha }}
            Author: ${{ github.actor }}
          webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
          channel: '#deployments'

  # ═════════════════════════════════════════════════════
  # PRODUCTION DEPLOYMENT
  # ═════════════════════════════════════════════════════

  deploy-production:
    name: Deploy to Production
    needs: [build, security]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    timeout-minutes: 15
    environment: production  # Requires manual approval

    steps:
      - name: Trigger Dokploy Production Deployment
        id: deploy
        run: |
          curl -X POST https://dokploy.your-domain.com/api/deploy \
            -H "Authorization: Bearer ${{ secrets.DOKPLOY_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{
              "project": "ripplecore-production",
              "services": ["app", "api", "web"],
              "imageTag": "${{ github.sha }}",
              "strategy": "rolling",
              "healthCheck": {
                "enabled": true,
                "path": "/api/health",
                "interval": 30,
                "retries": 3
              }
            }'

      - name: Wait for Production Health Checks
        run: |
          services=("https://app.your-domain.com" "https://api.your-domain.com" "https://www.your-domain.com")
          max_attempts=30

          for service in "${services[@]}"; do
            attempt=0
            echo "Checking $service..."

            while [ $attempt -lt $max_attempts ]; do
              response=$(curl -s -o /dev/null -w "%{http_code}" $service/api/health)

              if [ "$response" = "200" ]; then
                echo "✅ $service is healthy (HTTP 200)"
                break
              fi

              attempt=$((attempt + 1))
              echo "Waiting for $service... ($attempt/$max_attempts) - HTTP $response"
              sleep 10
            done

            if [ $attempt -eq $max_attempts ]; then
              echo "❌ $service health check failed after $max_attempts attempts"
              echo "Initiating automatic rollback..."

              curl -X POST https://dokploy.your-domain.com/api/rollback \
                -H "Authorization: Bearer ${{ secrets.DOKPLOY_API_KEY }}" \
                -H "Content-Type: application/json" \
                -d '{"project": "ripplecore-production"}'

              exit 1
            fi
          done

      - name: Run Smoke Tests
        run: |
          # Basic smoke tests after deployment
          echo "Running smoke tests..."

          # Test authentication endpoint
          curl -f https://app.your-domain.com/api/auth/session || exit 1

          # Test API health
          curl -f https://api.your-domain.com/api/health || exit 1

          # Test database connectivity (via health endpoint)
          response=$(curl -s https://app.your-domain.com/api/health)
          if echo "$response" | grep -q '"database":{"status":"ok"}'; then
            echo "✅ Database connectivity verified"
          else
            echo "❌ Database connectivity check failed"
            exit 1
          fi

          echo "✅ All smoke tests passed"

      - name: Notify Slack (Production Success)
        if: success()
        uses: 8398a7/action-slack@v3
        with:
          status: custom
          custom_payload: |
            {
              "text": "🚀 Production Deployment Successful",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Production Deployment Successful* 🎉"
                  }
                },
                {
                  "type": "section",
                  "fields": [
                    {
                      "type": "mrkdwn",
                      "text": "*Branch:*\nmain"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Commit:*\n${{ github.sha }}"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Author:*\n${{ github.actor }}"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Status:*\n✅ Healthy"
                    }
                  ]
                },
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "<https://app.your-domain.com|View Production>"
                  }
                }
              ]
            }
          webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
          channel: '#deployments'

      - name: Notify Slack (Production Failure)
        if: failure()
        uses: 8398a7/action-slack@v3
        with:
          status: custom
          custom_payload: |
            {
              "text": "🚨 Production Deployment Failed",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Production Deployment Failed* 🚨"
                  }
                },
                {
                  "type": "section",
                  "fields": [
                    {
                      "type": "mrkdwn",
                      "text": "*Branch:*\nmain"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Commit:*\n${{ github.sha }}"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Author:*\n${{ github.actor }}"
                    },
                    {
                      "type": "mrkdwn",
                      "text": "*Status:*\n❌ Failed (Rollback Initiated)"
                    }
                  ]
                },
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Logs>"
                  }
                }
              ]
            }
          webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
          channel: '#deployments'

Dokploy Configuration

Dokploy Installation (CI/CD Server)

# SSH into CI/CD server
ssh root@cicd-server-ip

# Install Dokploy (includes Docker, Traefik, PostgreSQL)
curl -sSL https://dokploy.com/install.sh | sh

# Access Dokploy UI
# URL: https://cicd-server-ip:3000
# Create admin account on first login

Project Configuration (Via Dokploy UI)

1. Create Production Project

Settings → New Project:

Project Name: ripplecore-production
Description: RippleCore production environment

2. Add Applications

For Each App (app, api, web):

Application Name: ripplecore-app # or api, web
Type: Application (Docker)

Source:
  Repository: https://github.com/your-org/ripplecore-forge
  Branch: main
  Build Path: apps/app # or apps/api, apps/web

Build:
  Type: Dockerfile
  Dockerfile Path: apps/app/Dockerfile
  Build Context: .
  Build Args:
    - NODE_ENV=production

Deploy:
  Replicas: 2 # Zero-downtime deployments
  Resources:
    Memory Limit: 3GB
    CPU Limit: 1.5

  Health Check:
    Enabled: true
    Path: /api/health
    Interval: 30s
    Timeout: 5s
    Retries: 3
    Start Period: 60s

Environment Variables:
  DATABASE_URL: postgresql://ripplecore:<secret>@10.0.1.3:5432/ripplecore_prod
  REDIS_URL: redis://:<secret>@10.0.1.3:6379
  BETTER_AUTH_SECRET: <generate-with-cli>
  BETTER_AUTH_URL: https://app.your-domain.com
  BETTER_AUTH_TRUST_HOST: true
  NODE_ENV: production
  NEXT_PUBLIC_APP_URL: https://app.your-domain.com

Domains:
  - Domain: app.your-domain.com
    SSL: Enabled (Let's Encrypt)
    Email: admin@your-domain.com

Deployment Strategy:
  Type: Rolling Update
  Max Surge: 1
  Max Unavailable: 0

3. Enable GitHub Webhooks

Settings → Webhooks:

Enable GitHub Webhook: true
Webhook URL: https://dokploy.your-domain.com/api/webhook/github
Secret: <generate-random-secret>
# Add webhook in GitHub:
# Repo → Settings → Webhooks → Add webhook
# Payload URL: https://dokploy.your-domain.com/api/webhook/github
# Content type: application/json
# Secret: <same-as-above>
# Events: Just the push event

Environment Strategy

Branch-to-Environment Mapping

BranchEnvironmentAuto-DeployApprovalDomain
developDevelopment (local)NoNolocalhost:3000
feature/*Preview (PR)YesNopr-123.staging.domain.com
stagingStagingYesNostaging.domain.com
mainProductionYesYes (1 reviewer)app.domain.com

Environment Variables Matrix

Shared Variables (all environments):

NODE_ENV=production
NEXT_TELEMETRY_DISABLED=1

Environment-Specific:

VariableDevelopmentStagingProduction
DATABASE_URLlocalhost:543210.0.2.2:543210.0.1.3:5432
REDIS_URLlocalhost:637910.0.2.2:637910.0.1.3:6379
BETTER_AUTH_URLhttp://localhost:3000https://staging.domain.comhttps://app.domain.com
BETTER_AUTH_TRUST_HOSTfalsetruetrue
STRICT_HEALTH_CHECKfalsefalsetrue

Deployment Workflows

Standard Deployment Flow

1. Feature Development (developer workflow):

# Create feature branch
git checkout -b feature/new-feature

# Make changes, commit
git add .
git commit -m "feat: add new feature"

# Push to GitHub
git push origin feature/new-feature

# Create PR to staging
# → GitHub Actions runs tests & builds
# → Preview environment created (pr-123.staging.domain.com)

2. Staging Deployment (merge to staging):

# After PR review and approval
git checkout staging
git merge feature/new-feature
git push origin staging

# → GitHub Actions: test + build + push images
# → Dokploy: deploy to staging
# → Health checks validate deployment
# → Slack notification sent

3. Production Deployment (merge to main):

# After QA approval on staging
git checkout main
git merge staging
git push origin main

# → GitHub Actions: test + build + push images
# → Wait for manual approval (GitHub Environment protection)
# → Dokploy: rolling deployment to production
# → Health checks + smoke tests
# → Automatic rollback on failure
# → Slack notification sent

Emergency Hotfix Flow

# Create hotfix branch from main
git checkout -b hotfix/critical-fix main

# Make minimal changes
git add .
git commit -m "hotfix: fix critical bug"

# Push and create PR directly to main
git push origin hotfix/critical-fix

# → Fast-track review (1 approver)
# → Merge to main → Auto-deploy to production
# → Backport to staging after production validation

Rollback Procedure

Automatic Rollback (health check failure):

  • Dokploy automatically reverts to previous deployment
  • Previous Docker images redeployed
  • Health checks validate rollback successful

Manual Rollback (via Dokploy UI):

1. Navigate to: Projects → ripplecore-production → app
2. Click: Deployments tab
3. Select: Previous successful deployment
4. Click: Rollback to this deployment
5. Confirm: Rollback initiated
6. Verify: Health checks pass

Manual Rollback (via Git):

# Revert to previous commit
git revert HEAD
git push origin main

# → Triggers new deployment with reverted changes

Troubleshooting

Common Issues

Issue: GitHub Actions Failing at Build Step

Symptoms:

Error: Failed to build Docker image
Context: COPY failed: file not found

Solution:

# Verify Dockerfile context is correct
# In Dockerfile:
COPY apps/app/package.json ./package.json  # ❌ Wrong (missing context)
COPY package.json ./package.json           # ✅ Correct (buildx context aware)

# Check build context in workflow
# In .github/workflows/deploy.yml:
context: .        # Root of repo
file: apps/app/Dockerfile

Issue: Dokploy Deployment Stuck at "Pulling Image"

Symptoms:

  • Deployment status: "Pulling image from registry"
  • Health checks never start

Solution:

# 1. Verify image exists in registry
docker pull ghcr.io/your-org/ripplecore-app:sha-abc123

# 2. Check Dokploy server can access registry
ssh root@cicd-server
docker login ghcr.io -u your-username -p <token>

# 3. Manually pull image to verify network
docker pull ghcr.io/your-org/ripplecore-app:latest

Issue: Health Check Failing After Deployment

Symptoms:

Health check failed: Connection refused
URL: https://app.your-domain.com/api/health

Debugging Steps:

# 1. Check container is running
docker ps | grep ripplecore-app

# 2. Check container logs
docker logs ripplecore-app --tail 100

# 3. Test health endpoint locally
docker exec ripplecore-app curl http://localhost:3000/api/health

# 4. Verify environment variables
docker exec ripplecore-app env | grep DATABASE_URL

# 5. Check Traefik routing
docker logs traefik | grep "app.your-domain.com"

Issue: Preview Environment Not Created

Symptoms:

  • PR comment not posted with preview URL
  • No preview environment visible in Dokploy

Solution:

# 1. Verify Dokploy webhook received PR event
# Dokploy UI → Webhooks → Recent Deliveries

# 2. Check GitHub Actions logs for webhook call
# Actions → deploy-preview job → "Trigger Dokploy Preview Deployment"

# 3. Verify API key is correct
curl -X POST https://dokploy.your-domain.com/api/preview/test \
  -H "Authorization: Bearer YOUR_API_KEY"
# Should return: {"status": "ok"}

Performance Optimization

Reduce Build Time (GitHub Actions)

Current: ~8-10 minutes per build

Optimizations:

# 1. Enable BuildKit caching
- name: Build Docker Image
  uses: docker/build-push-action@v5
  with:
    cache-from: type=gha
    cache-to: type=gha,mode=max  # ← Aggressive caching

# 2. Use matrix builds (parallel)
strategy:
  matrix:
    app: [app, api, web]  # ← Builds in parallel

# 3. Skip unnecessary steps on PR
if: github.event_name != 'pull_request'  # Skip security scan on PRs

Expected: ~5-6 minutes per build

Reduce Deployment Time (Dokploy)

Current: ~3-5 minutes per deployment

Optimizations:

# 1. Pre-pull images during build (parallel)
# In Dokploy config:
prePullImages: true

# 2. Reduce health check interval
healthCheck:
  interval: 10s # Down from 30s
  startPeriod: 30s # Down from 60s

# 3. Use rolling deployment (already configured)
strategy: rolling
maxSurge: 1
maxUnavailable: 0

Expected: ~2-3 minutes per deployment


Monitoring Deployments

GitHub Actions Dashboard

View Pipeline Status:

  • Navigate to: Actions tab in GitHub repository
  • Filter by workflow: CI/CD Pipeline
  • View deployment history, success rates, duration trends

Dokploy Deployment Logs

Real-Time Logs:

# Access Dokploy UI
https://dokploy.your-domain.com

# Navigate to: Projects → ripplecore-production → app → Logs
# View real-time logs during deployment

CLI Access:

# SSH into CI/CD server
ssh root@cicd-server-ip

# View live deployment logs
dokploy logs ripplecore-production app --follow

Slack Notifications

Configured Channels:

  • #deployments - All deployment notifications
  • #alerts - Critical deployment failures only

Notification Types:

  • ✅ Staging deployment successful
  • ✅ Production deployment successful
  • ❌ Deployment failed (with logs link)
  • 🔄 Rollback initiated

  • Infrastructure Overview: See ARCHITECTURE.md
  • Monitoring Setup: See MONITORING.md
  • Backup Procedures: See BACKUP_RECOVERY.md
  • Deployment Checklist: See DEPLOYMENT_CHECKLIST.md

Document Version: 1.0 Last Updated: 2025-01-23 Review Cycle: After major pipeline changes