# Release Engineering Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Implement comprehensive release engineering with semantic-release, Docker multi-registry publishing, and multi-environment deployment (production, staging, nightly). **Architecture:** semantic-release handles versioning, changelog, and GitHub releases from conventional commits. GitHub Actions workflows build and publish Docker images to Docker Hub and GHCR. Separate workflows for staging and nightly deployments. **Tech Stack:** semantic-release, commitlint, husky, Docker buildx, GitHub Actions --- ## Task 1: Install Release Engineering Dependencies **Files:** - Modify: `package.json` - Create: `.releaserc.json` - Create: `commitlint.config.js` **Step 1: Install semantic-release and plugins** ```bash npm install --save-dev semantic-release @semantic-release/changelog @semantic-release/git @semantic-release/github @commitlint/cli @commitlint/config-conventional husky ``` **Step 2: Verify installation** Run: `npm list semantic-release @commitlint/cli husky --depth=0` Expected: All packages listed with versions **Step 3: Commit** ```bash git add package.json package-lock.json git commit -m "build: add semantic-release, commitlint, and husky dependencies" ``` --- ## Task 2: Configure semantic-release **Files:** - Create: `.releaserc.json` **Step 1: Create semantic-release configuration file** Create `.releaserc.json`: ```json { "branches": ["main", { "name": "staging", "prerelease": true }], "plugins": [ [ "@semantic-release/commit-analyzer", { "preset": "conventionalcommits", "releaseRules": [ { "type": "feat", "release": "minor" }, { "type": "fix", "release": "patch" }, { "type": "perf", "release": "patch" }, { "type": "revert", "release": "patch" }, { "type": "docs", "release": false }, { "type": "style", "release": false }, { "type": "refactor", "release": false }, { "type": "test", "release": false }, { "type": "build", "release": false }, { "type": "ci", "release": false }, { "type": "chore", "release": false } ] } ], [ "@semantic-release/release-notes-generator", { "preset": "conventionalcommits", "presetConfig": { "types": [ { "type": "feat", "section": "Features", "hidden": false }, { "type": "fix", "section": "Bug Fixes", "hidden": false }, { "type": "perf", "section": "Performance Improvements", "hidden": false }, { "type": "revert", "section": "Reverts", "hidden": false }, { "type": "docs", "section": "Documentation", "hidden": false }, { "type": "style", "section": "Styles", "hidden": true }, { "type": "refactor", "section": "Code Refactoring", "hidden": true }, { "type": "test", "section": "Tests", "hidden": true }, { "type": "build", "section": "Build System", "hidden": true }, { "type": "ci", "section": "Continuous Integration", "hidden": true }, { "type": "chore", "section": "Chores", "hidden": true } ] } } ], [ "@semantic-release/changelog", { "changelogFile": "CHANGELOG.md", "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." } ], [ "@semantic-release/npm", { "npmPublish": false } ], [ "@semantic-release/git", { "assets": ["package.json", "CHANGELOG.md"], "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" } ], [ "@semantic-release/github", { "assets": [ { "path": "dist/**/*", "label": "Distribution" } ] } ] ] } ``` **Step 2: Verify JSON is valid** Run: `node -e "console.log(JSON.parse(require('fs').readFileSync('.releaserc.json', 'utf8')))"` Expected: JSON object printed without errors **Step 3: Commit** ```bash git add .releaserc.json git commit -m "chore(ci): configure semantic-release" ``` --- ## Task 3: Configure commitlint **Files:** - Create: `commitlint.config.js` **Step 1: Create commitlint configuration file** Create `commitlint.config.js`: ```javascript export default { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', [ 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert', ], ], 'scope-enum': [ 2, 'always', [ 'admin', 'api', 'ui', 'docker', 'ci', 'deps', 'release', 'auth', 'skills', 'experience', 'education', 'projects', 'personal', ], ], 'scope-empty': [1, 'never'], 'subject-case': [2, 'always', 'lower-case'], 'subject-max-length': [2, 'always', 72], 'body-max-line-length': [2, 'always', 100], }, }; ``` **Step 2: Verify configuration is valid** Run: `npx commitlint --from HEAD~1 --to HEAD --verbose` (skip if only one commit) Expected: Either passes or reports linting issues **Step 3: Commit** ```bash git add commitlint.config.js git commit -m "chore(ci): configure commitlint for conventional commits" ``` --- ## Task 4: Configure Husky Git Hooks **Files:** - Create: `.husky/commit-msg` - Create: `.husky/pre-commit` **Step 1: Initialize husky** ```bash npx husky init ``` Expected: `.husky/` directory created **Step 2: Create commit-msg hook** Replace `.husky/commit-msg` content with: ```bash npx --no -- commitlint --edit "$1" ``` **Step 3: Create pre-commit hook** Replace `.husky/pre-commit` content with: ```bash npm run lint ``` **Step 4: Make hooks executable** ```bash chmod +x .husky/pre-commit .husky/commit-msg ``` **Step 5: Verify husky is configured in package.json** Run: `cat package.json | grep -A 2 '"scripts"'` Expected: `prepare` script present with `husky` If not present, add to package.json scripts: ```json "prepare": "husky" ``` **Step 6: Commit** ```bash git add .husky package.json git commit -m "chore(ci): configure husky git hooks for commitlint" ``` --- ## Task 5: Create Docker Multi-Platform Configuration **Files:** - Modify: `Dockerfile` - Create: `docker bake.hcl` **Step 1: Update Dockerfile for multi-platform support** Read current `Dockerfile` and ensure it supports multi-platform. The existing Dockerfile should already work, but verify: - No hardcoded architecture-specific paths - Uses multi-stage build - Backend builds better-sqlite3 from source **Step 2: Create docker-bake.hcl for multi-registry publishing** Create `docker-bake.hcl`: ```hcl variable "REGISTRY_USER" { default = "" } variable "IMAGE_NAME" { default = "cv-app" } variable "TAG" { default = "latest" } variable "VERSION" { default = "" } group "default" { targets = ["cv-app"] } target "cv-app" { context = "." platforms = ["linux/amd64", "linux/arm64"] tags = [ notequal("",VERSION) ? "${REGISTRY_USER}/${IMAGE_NAME}:${VERSION}" : "", notequal("",VERSION) ? "${REGISTRY_USER}/${IMAGE_NAME}:latest" : "", notequal("",VERSION) ? "ghcr.io/${REGISTRY_USER}/${IMAGE_NAME}:${VERSION}" : "", notequal("",VERSION) ? "ghcr.io/${REGISTRY_USER}/${IMAGE_NAME}:latest" : "", ] args = { NODE_VERSION = "20" } } ``` **Step 3: Commit** ```bash git add Dockerfile docker-bake.hcl git commit -m "feat(docker): add multi-platform build configuration" ``` --- ## Task 6: Create Release Workflow **Files:** - Modify: `.github/workflows/release.yml` **Step 1: Replace existing release.yml** Replace `.github/workflows/release.yml` content with: ```yaml name: Release on: push: branches: [main] workflow_dispatch: permissions: contents: write packages: write id-token: write jobs: release: name: Release runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[skip ci]')" steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Install backend dependencies working-directory: ./backend run: npm ci - name: Lint run: npm run lint - name: Run tests run: npm run test:run - name: Run backend tests working-directory: ./backend run: npm test - name: Build run: npm run build - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npx semantic-release docker: name: Build & Push Docker Image runs-on: ubuntu-latest needs: release if: needs.release.result == 'success' steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get version id: version run: | VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "latest") echo "version=${VERSION#v}" >> $GITHUB_OUTPUT echo "tag=${VERSION}" >> $GITHUB_OUTPUT - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - 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: . platforms: linux/amd64,linux/arm64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/cv-app:latest ${{ secrets.DOCKERHUB_USERNAME }}/cv-app:${{ steps.version.outputs.version }} ghcr.io/${{ github.repository_owner }}/cv-app:latest ghcr.io/${{ github.repository_owner }}/cv-app:${{ steps.version.outputs.version }} cache-from: type=gha cache-to: type=gha,mode=max ``` **Step 2: Verify YAML syntax** Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))"` Expected: No errors **Step 3: Commit** ```bash git add .github/workflows/release.yml git commit -m "feat(ci): add semantic-release workflow with Docker publishing" ``` --- ## Task 7: Create Staging Workflow **Files:** - Create: `.github/workflows/staging.yml` **Step 1: Create staging workflow** Create `.github/workflows/staging.yml`: ```yaml name: Staging Deploy on: push: branches: [staging] workflow_dispatch: permissions: contents: read packages: write jobs: build-and-deploy: name: Build & Deploy to Staging runs-on: ubuntu-latest environment: staging steps: - name: Checkout 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: Install backend dependencies working-directory: ./backend run: npm ci - name: Lint run: npm run lint - name: Run tests run: npm run test:run - name: Build run: npm run build - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - 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: . platforms: linux/amd64,linux/arm64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/cv-app:staging ghcr.io/${{ github.repository_owner }}/cv-app:staging cache-from: type=gha cache-to: type=gha,mode=max ``` **Step 2: Verify YAML syntax** Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/staging.yml'))"` Expected: No errors **Step 3: Commit** ```bash git add .github/workflows/staging.yml git commit -m "feat(ci): add staging deployment workflow" ``` --- ## Task 8: Create Nightly Build Workflow **Files:** - Create: `.github/workflows/nightly.yml` **Step 1: Create nightly workflow** Create `.github/workflows/nightly.yml`: ```yaml name: Nightly Build on: schedule: - cron: '0 2 * * *' workflow_dispatch: permissions: contents: read packages: write jobs: nightly: name: Build Nightly Image runs-on: ubuntu-latest environment: nightly steps: - name: Checkout uses: actions/checkout@v4 with: ref: main - name: Get date id: date run: echo "date=$(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Install backend dependencies working-directory: ./backend run: npm ci - name: Build run: npm run build - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - 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: . platforms: linux/amd64,linux/arm64 push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/cv-app:nightly ${{ secrets.DOCKERHUB_USERNAME }}/cv-app:edge ${{ secrets.DOCKERHUB_USERNAME }}/cv-app:${{ steps.date.outputs.date }} ghcr.io/${{ github.repository_owner }}/cv-app:nightly ghcr.io/${{ github.repository_owner }}/cv-app:edge ghcr.io/${{ github.repository_owner }}/cv-app:${{ steps.date.outputs.date }} cache-from: type=gha cache-to: type=gha,mode=max ``` **Step 2: Verify YAML syntax** Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/nightly.yml'))"` Expected: No errors **Step 3: Commit** ```bash git add .github/workflows/nightly.yml git commit -m "feat(ci): add nightly build workflow" ``` --- ## Task 9: Enhance CI Workflow with Commitlint **Files:** - Modify: `.github/workflows/ci.yml` **Step 1: Add commitlint job to CI** Add this job to `.github/workflows/ci.yml` after the `jobs:` line: ```yaml commitlint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install dependencies run: npm ci - name: Validate current commit if: github.event_name == 'push' run: npx commitlint --from HEAD~1 --to HEAD --verbose - name: Validate PR commits if: github.event_name == 'pull_request' run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose ``` **Step 2: Update job dependencies** Add `commitlint` to the `needs` array of downstream jobs. For example, change: ```yaml integration: runs-on: ubuntu-latest needs: [frontend, backend] ``` to: ```yaml integration: runs-on: ubuntu-latest needs: [frontend, backend, commitlint] ``` Do the same for `e2e` and `regression` jobs. **Step 3: Verify YAML syntax** Run: `python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))"` Expected: No errors **Step 4: Commit** ```bash git add .github/workflows/ci.yml git commit -m "feat(ci): add commitlint validation to CI workflow" ``` --- ## Task 10: Create CHANGELOG.md **Files:** - Create: `CHANGELOG.md` **Step 1: Create initial CHANGELOG** Create `CHANGELOG.md`: ```markdown # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added - Release engineering with semantic-release - Multi-platform Docker builds (amd64, arm64) - Docker Hub and GHCR publishing - Staging and nightly deployment workflows - Commitlint for conventional commits ## [0.0.0] - 2026-02-20 ### Added - Initial CV application - React frontend with Tailwind CSS v4 - Express.js backend with SQLite - Admin panel for CV editing - Password protection for admin - Docker Compose deployment ``` **Step 2: Commit** ```bash git add CHANGELOG.md git commit -m "docs: add initial CHANGELOG.md" ``` --- ## Task 11: Update README with Release Information **Files:** - Modify: `README.md` **Step 1: Add release engineering section to README** Add this section after the "Testing" section: ```markdown ## Release Process This project uses [semantic-release](https://semantic-release.gitbook.io/) for automated versioning and releases. ### Conventional Commits All commit messages must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: ``` (): [optional body] [optional footer] ``` #### Commit Types | Type | Description | Version Bump | |------|-------------|--------------| | `feat` | New feature | Minor | | `fix` | Bug fix | Patch | | `feat!` or `BREAKING CHANGE` | Breaking change | Major | | `docs`, `style`, `refactor`, `test`, `build`, `ci`, `chore` | Non-release changes | None | #### Scopes Available scopes: `admin`, `api`, `ui`, `docker`, `ci`, `deps`, `release`, `auth`, `skills`, `experience`, `education`, `projects`, `personal` ### Release Workflow 1. Push to `main` branch 2. CI runs tests and linting 3. semantic-release analyzes commits 4. If release needed: - Version is bumped in package.json - CHANGELOG.md is updated - Git tag is created - GitHub release is published - Docker images are built and pushed ### Docker Images Images are published to both Docker Hub and GitHub Container Registry: | Tag | Description | |-----|-------------| | `latest` | Latest stable release | | `v1.2.3` | Specific version | | `staging` | Staging environment | | `nightly`, `edge` | Daily builds from main | | `YYYY-MM-DD` | Dated nightly build | ```bash # Pull from Docker Hub docker pull username/cv-app:latest # Pull from GHCR docker pull ghcr.io/owner/cv-app:latest ``` ### Environments | Environment | Branch | Trigger | |-------------|--------|---------| | Production | `main` | semantic-release | | Staging | `staging` | Push to staging | | Nightly | `main` | Daily at 02:00 UTC | ``` **Step 2: Commit** ```bash git add README.md git commit -m "docs: add release process documentation" ``` --- ## Task 12: Update package.json Version **Files:** - Modify: `package.json` **Step 1: Update version field** Change `"version": "0.0.0"` to `"version": "0.0.0-dev.1"` in package.json. This indicates the project is in development before the first release. semantic-release will set the proper version on first release. **Step 2: Commit** ```bash git add package.json git commit -m "chore: set initial development version" ``` --- ## Task 13: Verify All Configurations **Step 1: Run lint** ```bash npm run lint ``` Expected: No errors **Step 2: Run tests** ```bash npm run test:run ``` Expected: All tests pass **Step 3: Test commitlint locally** ```bash echo "feat(test): test commit message" | npx commitlint ``` Expected: No errors ```bash echo "invalid commit message" | npx commitlint ``` Expected: Error message about invalid format **Step 4: Verify husky hooks** ```bash ls -la .husky/ ``` Expected: `pre-commit` and `commit-msg` files exist and are executable --- ## Task 14: Final Commit and Summary **Step 1: Verify all files are committed** ```bash git status ``` Expected: "nothing to commit, working tree clean" or only untracked files that shouldn't be committed **Step 2: View commit history** ```bash git log --oneline -15 ``` Expected: All 12+ commits from this plan visible **Step 3: Summary** The release engineering system is now configured: - **semantic-release** handles automated versioning and releases - **commitlint** enforces conventional commits - **husky** provides git hooks for validation - **GitHub Actions** workflows for release, staging, and nightly builds - **Docker** multi-platform images published to Docker Hub and GHCR --- ## Required Secrets Before the first release, configure these secrets in GitHub repository settings: | Secret | Description | |--------|-------------| | `DOCKERHUB_USERNAME` | Docker Hub username | | `DOCKERHUB_TOKEN` | Docker Hub access token | | `GITHUB_TOKEN` | Automatic (no configuration needed) | --- ## Post-Implementation Checklist - [ ] Configure Docker Hub secrets in GitHub - [ ] Create `staging` branch - [ ] Configure GitHub environments (production, staging, nightly) - [ ] Set up branch protection rules for `main` - [ ] Test release by merging a `feat:` commit to main