22 KiB
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
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
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:
{
"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
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:
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
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
npx husky init
Expected: .husky/ directory created
Step 2: Create commit-msg hook
Replace .husky/commit-msg content with:
npx --no -- commitlint --edit "$1"
Step 3: Create pre-commit hook
Replace .husky/pre-commit content with:
npm run lint
Step 4: Make hooks executable
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:
"prepare": "husky"
Step 6: Commit
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:
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
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:
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
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:
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
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:
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
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:
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:
integration:
runs-on: ubuntu-latest
needs: [frontend, backend]
to:
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
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:
# 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
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:
## 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
git add package.json
git commit -m "chore: set initial development version"
Task 13: Verify All Configurations
Step 1: Run lint
npm run lint
Expected: No errors
Step 2: Run tests
npm run test:run
Expected: All tests pass
Step 3: Test commitlint locally
echo "feat(test): test commit message" | npx commitlint
Expected: No errors
echo "invalid commit message" | npx commitlint
Expected: Error message about invalid format
Step 4: Verify husky hooks
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
git status
Expected: "nothing to commit, working tree clean" or only untracked files that shouldn't be committed
Step 2: View commit history
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
stagingbranch - Configure GitHub environments (production, staging, nightly)
- Set up branch protection rules for
main - Test release by merging a
feat:commit to main