feat(ci): Add deployment workflow for staging and production - #95
Conversation
- Add deploy.yml workflow with SSH-based deployment - Staging: Auto-deploys on push to main (after tests pass) - Production: Deploys on GitHub releases or manual trigger - Quality gates: Requires test.yml to pass before deployment - Security: Uses ssh-agent for secure key handling - Add workflow_call trigger to test.yml for reusability - Include pre-deployment backups for production - Add health check verification post-deployment Triggers: - Push to main → staging - GitHub Release → production - Manual workflow_dispatch → either Required secrets: HOST, USER, SSH_KEY, PROJECT_PATH
|
Caution Review failedThe pull request is closed. WalkthroughAdds a GitHub Actions "Deploy" workflow for staging and production SSH-based deployments (build, PM2/service restart, health checks, backups for production). Makes the test workflow callable by other workflows. Replaces SESSION.md with a CI/CD deployment session guide and required secrets/instructions. Changes
Sequence Diagram(s)sequenceDiagram
participant GH as GitHub Actions
participant Test as Test Workflow
participant SSH as Remote Server
participant Health as Health Endpoint
GH->>Test: workflow_call -> run tests
Test-->>GH: tests pass
alt Deploy → Staging (push main or manual env=staging)
GH->>SSH: open SSH, copy env (.env.staging)
GH->>SSH: checkout code (main)
SSH->>SSH: backend install & build (dev)
SSH->>SSH: frontend npm ci & build (staging)
SSH->>SSH: pm2 start/reload (ecosystem.staging.config.js)
SSH->>Health: GET /health
Health-->>SSH: 200 OK
SSH-->>GH: deployment summary
end
alt Deploy → Production (release or manual env=production)
GH->>SSH: open SSH, create backups (state.db, commits, logs)
GH->>SSH: checkout tag or origin/main
SSH->>SSH: backend install & production build
SSH->>SSH: frontend npm ci & production build
SSH->>SSH: optional migrations
SSH->>SSH: pm2 start/reload (ecosystem.production.config.js)
SSH->>Health: GET /health
Health-->>SSH: 200 OK
SSH-->>GH: deployment summary
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (6)
.github/workflows/deploy.yml (5)
54-57: Consider pre-configuring known hosts for stronger MITM protection.Using
ssh-keyscanat runtime trusts whatever host key is returned at that moment. For stronger security, you could store the server's host key fingerprint as a secret and verify it, or commit a known_hosts file with pre-verified keys.That said, this approach is common and acceptable for most use cases.
110-114: Health check failure should fail the workflow.Currently, if the health check fails, the workflow still succeeds due to
|| echo. For staging, consider failing the workflow when health checks fail to catch broken deployments early.- name: Verify deployment run: | echo "🔍 Verifying deployment..." sleep 10 - ssh ${{ secrets.USER }}@${{ secrets.HOST }} "curl -sf http://localhost:8080/health" || echo "⚠️ Health check failed or endpoint not available" + ssh ${{ secrets.USER }}@${{ secrets.HOST }} "curl -sf http://localhost:8080/health" || { echo "❌ Health check failed"; exit 1; }
160-172: Consider a persistent backup location instead of/tmp.Backups stored in
/tmpmay be cleared by the OS before you need them for rollback (e.g., after a reboot). Consider storing backups in a dedicated persistent directory like~/codeframe-backupsor$PROJECT_PATH/backups.- BACKUP_DIR="/tmp/codeframe-backup-\$(date +%Y%m%d-%H%M%S)" + BACKUP_DIR="\$HOME/codeframe-backups/\$(date +%Y%m%d-%H%M%S)" mkdir -p \$BACKUP_DIR
238-242: Health check failure should fail the production deployment workflow.Same issue as staging—the
|| echopattern masks deployment failures. For production, it's especially important to fail the workflow if the health check fails so you're alerted to broken deployments.- name: Verify deployment run: | echo "🔍 Verifying production deployment..." sleep 15 - ssh ${{ secrets.USER }}@${{ secrets.HOST }} "curl -sf http://localhost:8080/health" || echo "⚠️ Health check failed or endpoint not available" + ssh ${{ secrets.USER }}@${{ secrets.HOST }} "curl -sf http://localhost:8080/health" || { echo "❌ Production health check failed"; exit 1; }
215-217: Migrations placeholder should be addressed before production use.The placeholder comment for database migrations should be either implemented or documented as a known limitation. If migrations are needed later, forgetting to add them here could cause production issues.
Would you like me to open an issue to track implementing database migration support?
claudedocs/SESSION.md (1)
60-65: Add blank lines around the table for proper Markdown rendering.Per markdownlint MD058, tables should be surrounded by blank lines.
### Deployment Triggers + | Trigger | Environment | Condition | |---------|-------------|-----------| | Push to `main` | Staging | Automatic after tests pass | | GitHub Release | Production | Automatic after tests pass | | Manual dispatch | Either | Select environment in UI | + ### Required GitHub Setup
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.github/workflows/deploy.yml(1 hunks).github/workflows/test.yml(1 hunks)claudedocs/SESSION.md(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.md
📄 CodeRabbit inference engine (AGENTS.md)
Documentation files must be sized to fit in a single agent context window (spec.md ~200-400 lines, plan.md ~300-600 lines, tasks.md ~400-800 lines)
Files:
claudedocs/SESSION.md
🧠 Learnings (1)
📚 Learning: 2025-12-11T17:15:19.102Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-11T17:15:19.102Z
Learning: Applies to codeframe/agents/worker_agent.py : Implement quality gates with 4 stages (tests → type checking → coverage → review) before task completion
Applied to files:
.github/workflows/deploy.yml
🪛 markdownlint-cli2 (0.18.1)
claudedocs/SESSION.md
61-61: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Backend Unit Tests
- GitHub Check: Frontend Unit Tests
- GitHub Check: E2E Smoke Tests (Chromium)
🔇 Additional comments (4)
.github/workflows/test.yml (1)
8-8: LGTM!Adding
workflow_callenables this workflow to be used as a reusable workflow, which is the correct approach for implementing a quality gate in the deployment workflow..github/workflows/deploy.yml (3)
1-22: LGTM!The trigger configuration appropriately maps deployment scenarios: automatic staging on main push, automatic production on release, and manual dispatch for flexibility.
23-29: LGTM!Using the test workflow as a quality gate via
uses:is a clean approach that avoids code duplication.
59-108: LGTM!The staging deployment script is well-structured with:
set -efor fail-fast behavior- Conditional venv creation
- Conditional service restarts with warnings
One consideration: long-running operations like
npm cicould cause SSH timeouts on slow networks. If you encounter issues, consider addingServerAliveIntervalto the SSH config.
- Replace systemctl commands with PM2 restart/start - Update health check port from 8080 to 14200 (staging backend port) - Add logs directory creation before PM2 start - Support optional ecosystem.production.config.js for production - Add PM2 save after restart to persist process list
- Add environment file creation step for staging and production - Create .env.staging/.env.production from GitHub secrets - Set proper file permissions (600) for security - Document new required secrets: ANTHROPIC_API_KEY, OPENAI_API_KEY, CORS_ORIGINS, API_URL, WS_URL New secrets needed in staging environment: - ANTHROPIC_API_KEY - OPENAI_API_KEY (optional) - CORS_ORIGINS - API_URL - WS_URL
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
.github/workflows/deploy.yml (2)
19-21: Unused environment variables.The
PYTHON_VERSIONandNODE_VERSIONenvironment variables are defined but never referenced in the workflow. Either use them in the deployment scripts (e.g.,python${{ env.PYTHON_VERSION }}) or remove them to avoid confusion.
219-221: Database migrations are commented out.The placeholder comment suggests migrations may be needed, but there's no actual migration command. If your application requires database schema migrations, this step must be implemented before production deployment.
Do you need database migrations for this project? If yes, I can help you implement the appropriate migration command (e.g., using Alembic for Python).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/deploy.yml(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-11T17:15:19.102Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-11T17:15:19.102Z
Learning: Applies to codeframe/agents/worker_agent.py : Implement quality gates with 4 stages (tests → type checking → coverage → review) before task completion
Applied to files:
.github/workflows/deploy.yml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Frontend Unit Tests
- GitHub Check: Backend Unit Tests
- GitHub Check: E2E Smoke Tests (Chromium)
🔇 Additional comments (1)
.github/workflows/deploy.yml (1)
27-29: LGTM! Clean quality gate implementation.Delegating to the reusable test workflow is the right approach for enforcing quality gates before deployment.
| git fetch origin main | ||
| git reset --hard origin/main |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Ensure git reset works from any branch state.
The git reset --hard origin/main assumes the working directory is already on the main branch. If the server is on a different branch or in a detached HEAD state, this might not work as expected.
Apply this approach for more robust git operations:
echo "📥 Pulling latest code..."
+git fetch origin main
+git checkout main
-git fetch origin main
git reset --hard origin/main📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| git fetch origin main | |
| git reset --hard origin/main | |
| git fetch origin main | |
| git checkout main | |
| git reset --hard origin/main |
🤖 Prompt for AI Agents
.github/workflows/deploy.yml around lines 70 to 71: the current sequence uses
"git reset --hard origin/main" which can fail if the repo is on a different
branch or in detached HEAD; instead fetch and then force-create/replace the
local main branch from origin so the command works from any state—use "git fetch
origin main" followed by "git checkout -B main origin/main" (or similar) to
ensure you are on a local main that exactly matches origin/main before any
resets or deployments.
| # Pull the release tag or main | ||
| echo "📥 Pulling latest code..." | ||
| git fetch origin --tags | ||
| TAG_NAME="${{ github.event.release.tag_name }}" | ||
| if [ -n "\$TAG_NAME" ]; then | ||
| git checkout \$TAG_NAME | ||
| echo "✅ Checked out tag: \$TAG_NAME" | ||
| else | ||
| git fetch origin main | ||
| git reset --hard origin/main | ||
| echo "✅ Reset to origin/main" | ||
| fi |
There was a problem hiding this comment.
Git checkout logic unsafe with empty tag name.
When triggered via workflow_dispatch for production, github.event.release.tag_name will be empty, causing TAG_NAME to be an empty string. The condition on line 192 checks if [ -n "\$TAG_NAME" ], which correctly handles this, but the logic could be clearer.
However, there's a potential issue: if someone triggers a manual production deployment without a release, they get origin/main, which may not be what they intended for production.
Consider one of these approaches:
Option 1: Require a tag for production deployments:
TAG_NAME="${{ github.event.release.tag_name }}"
if [ -n "\$TAG_NAME" ]; then
git checkout \$TAG_NAME
echo "✅ Checked out tag: \$TAG_NAME"
else
- git fetch origin main
- git reset --hard origin/main
- echo "✅ Reset to origin/main"
+ echo "❌ Production deployments require a release tag"
+ exit 1
fiOption 2: Add tag input to workflow_dispatch:
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy to'
required: true
default: 'staging'
type: choice
options:
- staging
- production
+ tag:
+ description: 'Tag to deploy (required for production)'
+ required: false
+ type: string📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Pull the release tag or main | |
| echo "📥 Pulling latest code..." | |
| git fetch origin --tags | |
| TAG_NAME="${{ github.event.release.tag_name }}" | |
| if [ -n "\$TAG_NAME" ]; then | |
| git checkout \$TAG_NAME | |
| echo "✅ Checked out tag: \$TAG_NAME" | |
| else | |
| git fetch origin main | |
| git reset --hard origin/main | |
| echo "✅ Reset to origin/main" | |
| fi | |
| # Pull the release tag or main | |
| echo "📥 Pulling latest code..." | |
| git fetch origin --tags | |
| TAG_NAME="${{ github.event.release.tag_name }}" | |
| if [ -n "\$TAG_NAME" ]; then | |
| git checkout \$TAG_NAME | |
| echo "✅ Checked out tag: \$TAG_NAME" | |
| else | |
| echo "❌ Production deployments require a release tag" | |
| exit 1 | |
| fi |
🤖 Prompt for AI Agents
.github/workflows/deploy.yml lines 188-199: the current checkout logic uses
github.event.release.tag_name which can be empty for workflow_dispatch and may
unintentionally deploy origin/main to production; update the workflow to require
an explicit tag for production deployments or add a workflow_dispatch input for
a tag and validate it: add a required 'tag' input to the production dispatch (or
check github.event.inputs.tag), set TAG_NAME from that input (falling back to
release.tag_name only if present), and if TAG_NAME is empty fail the job with a
clear error and non-zero exit instead of defaulting to origin/main, logging the
missing tag and expected usage.
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (2)
.github/workflows/deploy.yml (2)
200-241: Critical: Same command injection vulnerability as staging.This step has the same security issue as the staging environment file creation (lines 59-101). The heredoc with unsanitized environment variables could allow command injection.
Apply the same fix as suggested for staging - create the file locally and use SCP to transfer it securely.
338-342: Critical: Health check always succeeds (same issue as staging).Line 342 has the same problem as the staging health check - the
|| echomakes it always succeed even if the health check fails. This is especially critical for production deployments.Apply the same fix as suggested for staging, with the retry loop being even more important for production to ensure the service is truly healthy before marking deployment as successful.
🧹 Nitpick comments (2)
.github/workflows/deploy.yml (2)
102-155: Consider adding rollback mechanism for failed deployments.If any deployment step fails after pulling code (line 113), the server will be left in a potentially broken state. Consider implementing a rollback mechanism that reverts to the previous commit if deployment fails.
You could add a pre-deployment commit capture and post-deployment verification with rollback:
# Before deployment (after line 109) PREVIOUS_COMMIT=$(git rev-parse HEAD) # After all deployment steps, if any fail, add a trap: trap 'echo "❌ Deployment failed, rolling back..."; git reset --hard $PREVIOUS_COMMIT; exit 1' ERR
305-307: Database migrations are not implemented.The placeholder comment indicates that database migrations should be run, but there's no actual implementation. This could lead to schema mismatches after deployment if the codebase expects database changes.
Do you want me to generate a database migration implementation based on the project structure, or would you prefer to open an issue to track this task?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/deploy.yml(1 hunks)claudedocs/SESSION.md(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.md
📄 CodeRabbit inference engine (AGENTS.md)
Documentation files must be sized to fit in a single agent context window (spec.md ~200-400 lines, plan.md ~300-600 lines, tasks.md ~400-800 lines)
Files:
claudedocs/SESSION.md
🧠 Learnings (1)
📚 Learning: 2025-12-11T17:15:19.102Z
Learnt from: CR
Repo: frankbria/codeframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-11T17:15:19.102Z
Learning: Applies to codeframe/agents/worker_agent.py : Implement quality gates with 4 stages (tests → type checking → coverage → review) before task completion
Applied to files:
.github/workflows/deploy.yml
🪛 markdownlint-cli2 (0.18.1)
claudedocs/SESSION.md
70-70: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Backend Unit Tests
- GitHub Check: E2E Smoke Tests (Chromium)
🔇 Additional comments (8)
.github/workflows/deploy.yml (8)
27-29: LGTM! Quality gate properly configured.Reusing the existing test workflow as a quality gate before deployment follows best practices and prevents deploying broken code.
46-57: LGTM! SSH setup follows security best practices.Using
webfactory/ssh-agentensures the SSH key is never exposed in logs, andssh-keyscanadds the host to known_hosts to prevent MITM attacks.
163-171: LGTM! Deployment summary provides good visibility.The deployment summary includes relevant information (branch, commit, actor, timestamp) that will help track deployments in the Actions UI.
182-184: Verify production environment URL.The production URL
https://codeframe.example.comappears to be a placeholder. Ensure this is updated to the actual production domain before deploying to production.
274-285: LGTM! Tag-based deployment logic is well-implemented.The logic correctly handles both release-triggered deployments (using the tag) and manual dispatches (falling back to main). This provides flexibility for production deployments.
287-296: Good: Production-optimized dependency installation.Using
uv sync --no-dev(line 296) appropriately excludes development dependencies from the production environment, reducing the attack surface and deployment size.
344-351: LGTM! Production summary appropriately shows version information.Using
github.event.release.tag_name || github.ref_nameensures the summary shows the tag for release deployments or branch for manual deployments.
19-21: Python 3.11 matches the project requirement (requires-python = ">=3.11"in pyproject.toml). Node 20 is acceptable; the project does not specify a Node engine version requirement in package.json or version files.
All environment variables now come from GitHub secrets so they can be configured per-environment (staging vs production) in GitHub settings. Secrets required per environment: - ANTHROPIC_API_KEY, OPENAI_API_KEY - DATABASE_PATH, API_HOST, API_PORT - CORS_ORIGINS, API_URL, WS_URL - LOG_LEVEL, LOG_FILE - ENVIRONMENT, DEBUG, HOT_RELOAD
- Staging: ecosystem.staging.config.js, check for codeframe-staging-backend - Production: ecosystem.production.config.js, check for codeframe-production-backend - Use pm2 describe instead of grep for reliable process detection
- Move backups from /tmp to PROJECT_PATH/backups for persistence - Create timestamped compressed tar.gz archives - Include: database, .codeframe configs, .env files, ecosystem configs, logs - Record git commit info in archive - Implement retention policy (keep last 10 backups) - Use atomic operations (create in /tmp, mv to final location) - Set proper permissions (700 dir, 600 archives) - Abort deployment if backup fails - Report backup size and count after completion
- Replace one-liner health check with retry loop (12 attempts, 5s apart) - Exit 0 immediately on success - Exit 1 after all attempts fail (fails the workflow) - Show progress on each attempt - Applied to both staging and production deployments
- Use pm2 jlist with grep for robust process existence check - Restart individual processes by name when they exist - Start with --only flag when processes don't exist - Handle backend and frontend processes separately - Applied to both staging and production deployments
- Use printf with %s to build env content (no shell interpretation) - Base64 encode content before transfer to prevent injection - Decode safely on remote server - Write to .tmp file first, then atomic mv to final location - Verify file was created and is non-empty before proceeding - Exit with error if creation fails This prevents command injection if secrets contain shell metacharacters like backticks, $(), or other special characters.
Summary
Deployment Triggers
mainChanges
.github/workflows/deploy.yml- New deployment workflow.github/workflows/test.yml- Addedworkflow_calltrigger for reusabilityclaudedocs/SESSION.md- Implementation documentationSecurity Features
webfactory/ssh-agentfor secure SSH key handlingTest plan
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.