feat: Add multi-stage Docker build for production deployment - #59
feat: Add multi-stage Docker build for production deployment#59VAIBHAVSING wants to merge 6 commits into
Conversation
- Add push_to_dockerhub input for workflow_dispatch - Set force_rebuild default to true for manual triggers - Use fallback DockerHub username (vaibhavsing) if secret not set - Add conditional push logic based on input - Fix all image build/push steps to use dynamic username - Remove unused dockerhub_username output from setup job
- Create Dockerfile.production with all 4 layers as multi-stage build - Update docker-cd-production.yml workflow to build and push only final image - Workflow builds: supervisor, base, languages, vscode, and ai-tools layers - Only final workspace image (vaibhavsing/dev8-workspace) pushed to Docker Hub - Added comprehensive testing for all components - Added security scanning with Trivy - Local development: cd docker && make build-all && docker compose up -d
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The sha tag with branch prefix was generating invalid tags like
'vaibhavsing/dev8-workspace:-7f4a660' which caused build to fail.
Removed type=sha,prefix={{branch}}- from metadata tags.
Changes: - Removed Dockerfile.production (multi-stage approach) - Updated workflow to use 'make build-all' for layered builds - Builds: base → languages → vscode → ai-tools (separately) - Only pushes final dev8-workspace container to Docker Hub - Container is ready for ACI deployment with volume support - Local dev: cd docker && make build-all && docker compose up -d
Docker compose validates environment variables even during build. Set a dummy token to allow builds to proceed in CI.
Issue: docker compose build tries to pull from Docker Hub after building base layer, but images are only available locally. Solution: Use direct 'docker build' commands for each layer to ensure they use locally built images as base.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
.github/workflows/docker-cd-production.yml (4)
1-6: Semantic clarification: Separate layer builds vs. true multi-stage Docker buildThe PR title and description indicate a "multi-stage Docker build," but the workflow builds four separate Docker images sequentially (lines 77–98), not a true multi-stage build using
FROMstatements within a single Dockerfile. The distinction matters:
- Current approach: Builds
dev8-base,dev8-languages,dev8-vscode, anddev8-workspaceas independent images—useful for layer caching and independent testing, but increases storage and build complexity.- True multi-stage: Uses a single Dockerfile with multiple
FROMstatements, producing only the final image by default.Clarify whether the separate-image approach is intentional for your use case (e.g., reusing base layers in other workflows) or if a single Dockerfile with true multi-stage build would be preferable. This affects documentation and expectations.
If you'd like to refactor to a true multi-stage Dockerfile, I can help generate that. Otherwise, consider updating the PR title and description to reflect "separate layered Docker builds" for accuracy.
77-98: Add set -e or explicit error handling for layer buildsEach build step (lines 83, 87, 91, 95) lacks explicit failure detection. If any layer build fails, the workflow continues to subsequent steps (tagging, testing, pushing) with a stale or missing image, causing confusing failures downstream.
Add
set -eat the top of the script to exit on any error:- name: Build all Docker layers run: | + set -e echo "🏗️ Building all Docker layers (base → languages → vscode → ai-tools)..." # Build Layer 1: Base echo "Building base layer..." docker build -t dev8-base:latest -f ./docker/images/00-base/Dockerfile .Alternatively, chain with explicit error messages:
docker build -t dev8-base:latest -f ./docker/images/00-base/Dockerfile . || { echo "❌ Base layer build failed"; exit 1; }
112-142: Test container: simplify multi-line command or use dedicated test scriptThe single-line docker run command with multiple chained
&&operators (lines 115–141) is difficult to debug if a component fails. The error output will not clearly identify which test step failed.Consider moving the test commands to a dedicated shell script in the repository (e.g.,
docker/test-workspace.sh), then invoke it:- name: Test final workspace container run: | - echo "=== Testing Final Workspace Container ===" - docker run --rm dev8-workspace:latest bash -c " - echo '--- Testing Workspace Supervisor ---' && - workspace-supervisor --version && - ... - " + echo "=== Testing Final Workspace Container ===" + docker run --rm dev8-workspace:latest bash ./docker/test-workspace.shThis improves:
- Readability and maintainability
- Error attribution (clearer which test fails)
- Reusability (same script for local and CI testing)
- Easier iteration during development
99-111: Minor: consolidate conditional tagging logicThe conditional tagging logic is correct but somewhat verbose. Consider consolidating:
- name: Tag final workspace image for Docker Hub run: | echo "🏷️ Tagging final workspace image..." - docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.version }} - docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:latest - if [ "${{ github.ref }}" = "refs/heads/production" ]; then - docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:production - fi - if [ "${{ github.event_name }}" = "pull_request" ]; then - docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:pr-${{ github.event.pull_request.number }} - fi + # Always tag with version and latest + docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.version }} + docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:latest + + # Production branch gets a production tag + [ "${{ github.ref }}" = "refs/heads/production" ] && \ + docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:production + + # PRs get a pr-<number> tag + [ "${{ github.event_name }}" = "pull_request" ] && \ + docker tag dev8-workspace:latest ${{ env.DOCKERHUB_IMAGE }}:pr-${{ github.event.pull_request.number }} + echo "✅ Images tagged"This reduces repetition and improves clarity of the tagging strategy.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.github/workflows/docker-cd-production.yml(1 hunks)
🔇 Additional comments (3)
.github/workflows/docker-cd-production.yml (3)
160-172: Potential issue:numfmtcommand availabilityLine 167 uses
numfmt --to=iec-i --suffix=Bto format the image size. Whilenumfmtis part of GNU coreutils, it may not be available or in thePATHon allubuntu-latestrunners.Verify that
numfmtis available onubuntu-latest. If unavailable, consider a portable alternative:# Using awk and printf (portable) SIZE=$(docker inspect dev8-workspace:latest --format='{{.Size}}') # Convert bytes to human-readable (rough implementation) echo $SIZE | awk '{ for (i=1; i<=log($1)/log(1024); i++) {} printf "%.2f %s\n", $1/1024^i, ["B", "KiB", "MiB", "GiB", "TiB"][i+1] }'Or use
pythonfor more robust formatting (available on ubuntu-latest):python3 -c "import os; s=$(docker inspect dev8-workspace:latest --format='{{.Size}}'); print(f'{s / (1024**3):.2f} GiB' if s > 1024**3 else f'{s / (1024**2):.2f} MiB')"
173-190: Verify push condition logic for workflow_dispatchLine 177 conditions the Docker Hub push on:
- NOT a pull_request event
- AND (main branch OR production branch OR workflow_dispatch with
push_to_dockerhub=true)This is correct, but ensure the
workflow_dispatchinput parsing is reliable. The YAML input'true'is a string, and the comparison on line 177 is a string comparison—confirm this works as expected in practice.Consider adding explicit logging for the push decision:
- name: Push final workspace container to Docker Hub if: | github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/production' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_to_dockerhub == 'true')) run: | + echo "Event: ${{ github.event_name }}" + echo "Ref: ${{ github.ref }}" + echo "Push to Docker Hub: yes" echo "🚀 Pushing final workspace container to Docker Hub..." docker push ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.version }} docker push ${{ env.DOCKERHUB_IMAGE }}:latestThis helps with debugging if pushes are unexpectedly skipped.
272-327: Approve PR comment generationThe
Comment PR with container infostep correctly:
- Uses
github-script@v7to post a comment- Provides clear instructions for local development and production deployment
- Only runs on pull_request events
- Includes build approach and test status
No issues identified. This improves reviewer experience by providing context directly in the PR.
| - name: Deployment summary | ||
| if: github.event_name != 'pull_request' | ||
| run: | | ||
| { | ||
| echo "# 🚀 Docker Production Deployment Summary" | ||
| echo "" | ||
| echo "## 📦 Deployment Information" | ||
| echo "- **Version**: \`${{ needs.setup.outputs.version }}\`" | ||
| echo "- **Version**: \`${{ steps.version.outputs.version }}\`" | ||
| echo "- **Commit**: \`${{ github.sha }}\`" | ||
| echo "- **Branch**: \`${{ github.ref_name }}\`" | ||
| echo "- **Timestamp**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" | ||
| echo "" | ||
| echo "## 🏗️ Build Status" | ||
| echo "## 🏗️ Build Approach" | ||
| echo "" | ||
| echo "Built using **layered Docker images**:" | ||
| echo "1. � dev8-base (Ubuntu + SSH + Supervisor)" | ||
| echo "2. 🔹 dev8-languages (Node.js, Python, Go, Rust, Bun)" | ||
| echo "3. 🔹 dev8-vscode (VS Code Server)" | ||
| echo "4. 🔹 **dev8-workspace** (AI Tools - Final Container)" | ||
| echo "" | ||
| echo "| Image | Status | Pushed to Docker Hub |" | ||
| echo "|-------|--------|----------------------|" | ||
| echo "| dev8-base | ${{ needs.build-base.result }} | ${{ needs.build-base.result == 'success' && '✅' || '❌' }} |" | ||
| echo "| dev8-languages | ${{ needs.build-languages.result }} | ${{ needs.build-languages.result == 'success' && '✅' || '❌' }} |" | ||
| echo "| dev8-vscode | ${{ needs.build-vscode.result }} | ${{ needs.build-vscode.result == 'success' && '✅' || '❌' }} |" | ||
| echo "| dev8-workspace | ${{ needs.build-ai-tools.result }} | ${{ needs.build-ai-tools.result == 'success' && '✅' || '❌' }} |" | ||
| echo "## �🐳 Docker Hub - Final Container Only" | ||
| echo "" | ||
| echo "## 🔗 Docker Hub Images" | ||
| echo "**Image**: \`${{ env.DOCKERHUB_IMAGE }}\`" | ||
| echo "" | ||
| echo "Pull commands:" | ||
| echo "### Pull Commands:" | ||
| echo "\`\`\`bash" | ||
| echo "# Base image" | ||
| echo "docker pull ${{ needs.setup.outputs.dockerhub_username }}/dev8-base:${{ needs.setup.outputs.version }}" | ||
| echo "# Latest version" | ||
| echo "docker pull ${{ env.DOCKERHUB_IMAGE }}:latest" | ||
| echo "" | ||
| echo "# Languages image" | ||
| echo "docker pull ${{ needs.setup.outputs.dockerhub_username }}/dev8-languages:${{ needs.setup.outputs.version }}" | ||
| echo "# Specific version" | ||
| echo "docker pull ${{ env.DOCKERHUB_IMAGE }}:${{ steps.version.outputs.version }}" | ||
| echo "\`\`\`" | ||
| echo "" | ||
| echo "# VS Code image" | ||
| echo "docker pull ${{ needs.setup.outputs.dockerhub_username }}/dev8-vscode:${{ needs.setup.outputs.version }}" | ||
| echo "## 🏃 Run Container (Production)" | ||
| echo "" | ||
| echo "# Workspace image (recommended)" | ||
| echo "docker pull ${{ needs.setup.outputs.dockerhub_username }}/dev8-workspace:${{ needs.setup.outputs.version }}" | ||
| echo "docker pull ${{ needs.setup.outputs.dockerhub_username }}/dev8-workspace:latest" | ||
| echo "docker pull ${{ needs.setup.outputs.dockerhub_username }}/dev8-workspace:production" | ||
| echo "\`\`\`bash" | ||
| echo "# Run workspace container with volume support" | ||
| echo "docker run -d \\" | ||
| echo " --name dev8-workspace \\" | ||
| echo " -p 8080:8080 \\" | ||
| echo " -p 2222:2222 \\" | ||
| echo " -p 9000:9000 \\" | ||
| echo " -e GITHUB_TOKEN=\${GITHUB_TOKEN} \\" | ||
| echo " -e ENVIRONMENT_ID=dev8-prod-001 \\" | ||
| echo " -v dev8-home:/home/dev8 \\" | ||
| echo " -v dev8-workspace:/workspace \\" | ||
| echo " ${{ env.DOCKERHUB_IMAGE }}:latest" | ||
| echo "\`\`\`" | ||
| echo "" | ||
| echo "## 🔐 Security Scans" | ||
| echo "## 🧪 Test Results" | ||
| echo "" | ||
| echo "Trivy vulnerability scans completed. Check GitHub Security tab for detailed results." | ||
| echo "✅ All component tests passed:" | ||
| echo "- Workspace Supervisor" | ||
| echo "- Node.js (with npm, pnpm)" | ||
| echo "- Python (with pip, poetry)" | ||
| echo "- Go" | ||
| echo "- Rust" | ||
| echo "- Bun" | ||
| echo "- VS Code Server" | ||
| echo "- GitHub CLI" | ||
| echo "- Azure CLI" | ||
| echo "- yq" | ||
| echo "" | ||
| echo "## 📝 Next Steps" | ||
| echo "## 🔐 Security Scan" | ||
| echo "" | ||
| echo "1. Review security scan results in GitHub Security" | ||
| echo "2. Update production deployments to use version \`${{ needs.setup.outputs.version }}\`" | ||
| echo "3. Test in staging environment before full rollout" | ||
| echo "4. Monitor application logs after deployment" | ||
| echo "Trivy vulnerability scan completed. Check GitHub Security tab for detailed results." | ||
| echo "" | ||
| echo "---" | ||
| echo "## 📝 Local Development" | ||
| echo "" | ||
| echo "\`\`\`bash" | ||
| echo "# Build all layers locally" | ||
| echo "cd docker && make build-all" | ||
| echo "" | ||
| echo "# Run with docker-compose" | ||
| echo "docker compose up -d" | ||
| echo "\`\`\`" | ||
| echo "" | ||
| echo "---" | ||
| echo "**Deployment completed at**: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" | ||
| } >> "$GITHUB_STEP_SUMMARY" |
There was a problem hiding this comment.
Character encoding issue in deployment summary output
Lines 206 and 211 contain visually corrupted emoji characters:
- Line 206:
"1. � dev8-base (Ubuntu + SSH + Supervisor)"– appears to be a mojibake or corrupted emoji - Line 211:
"## 🐳 Docker Hub - Final Container Only"– the 🐳 may not render consistently
Replace with safe, ASCII-compatible alternatives or test emoji rendering:
- echo "1. � dev8-base (Ubuntu + SSH + Supervisor)"
- echo "2. 🔹 dev8-languages (Node.js, Python, Go, Rust, Bun)"
- echo "3. 🔹 dev8-vscode (VS Code Server)"
- echo "4. 🔹 **dev8-workspace** (AI Tools - Final Container)"
+ echo "1. 🐳 dev8-base (Ubuntu + SSH + Supervisor)"
+ echo "2. 🔹 dev8-languages (Node.js, Python, Go, Rust, Bun)"
+ echo "3. 🔹 dev8-vscode (VS Code Server)"
+ echo "4. 🔹 **dev8-workspace** (AI Tools - Final Container)"And:
- echo "## 🐳 Docker Hub - Final Container Only"
+ echo "## Docker Hub - Final Container Only 🐳"|
mirror #60 |
Summary
This PR introduces a multi-stage Docker build system for production deployment to Docker Hub.
Changes
1. New Multi-Stage Dockerfile (
docker/Dockerfile.production)2. Updated GitHub Actions Workflow (
.github/workflows/docker-cd-production.yml)vaibhavsing/dev8-workspacemainorproductionbranches, and PRsLocal Development
Docker Hub Deployment
When merged to
mainorproductionbranch, the workflow will:vaibhavsing/dev8-workspace:latestTesting Checklist
Images Produced
vaibhavsing/dev8-workspace:latestvaibhavsing/dev8-workspace:<version>vaibhavsing/dev8-workspace:<branch>-<sha>Summary by CodeRabbit
pr-<number>for easier identification and management.