Неперервна доставка (Continuous Delivery, CD) завершує інженерний цикл трансформації перевіреного вихідного коду в готовий до експлуатації, незмінний артефакт (Immutable Artifact). У розподілених хмарних середовищах та кластерах Kubernetes таким стандартом де-факто виступає контейнерний образ специфікації OCI (Open Container Initiative).
Автоматизація релізу корпоративного рівня спирається на п'ять базових інженерних стовпів:
semantic-release аналізує повідомлення комітів за специфікацією Conventional Commits на основі абстрактного синтаксичного дерева (AST) Git-історії:
fix(...): інкремент PATCH-версії (виправлення дефекту без зміни API);feat(...): інкремент MINOR-версії (додавання зворотно-сумісної функціональності);feat(...)! або футер BREAKING CHANGE:: інкремент MAJOR-версії (зворотно-несумісні зміни контрактів або архітектури).verifyConditions: перевірка прав доступу, валідності токенів GITHUB_TOKEN та облікових записів реєстрів;analyzeCommits: класифікація змін з моменту останнього тегу;generateNotes: генерація Markdown списку змін (CHANGELOG.md);prepare: фіксація оновленої версії в package.json та коміт у репозиторій із маркером [skip ci];publish: створення релізу на GitHub із бінарними артефактами та присвоєння Git-тегу (vX.Y.Z).type=gha): Збірка образів усередині ізольованих раннерів за замовчуванням позбавлена локального кешу Docker Daemon. Використання бекенду cache-from=type=gha та cache-to=type=gha,mode=max зберігає проміжні шари збірки в GitHub Actions Cache API, скорочуючи повторне створення образів з 5 хвилин до 20 секунд.ghcr.io, що автентифікується через вбудований GITHUB_TOKEN) та публічного/клієнтського реєстру (Docker Hub docker.io). Образи публікуються паралельно з детермінованою схемою тегів: незмінний Git SHA (sha-a1b2c3d), точний SemVer (v1.4.2), мажорний плаваючий тег (v1) та latest.Взаємодія аналізатора комітів, збирального двигуна Buildx та віддалених реєстрів контейнерів:
[ Merge PR у релізну гілку: main ]
|
v
+-----------------------------+
| GitHub Actions CD Job |
| (Ephemeral VM: ubuntu-latest|
+-----------------------------+
|
v
+-------------------------------------------------------------------+
| 1. Життєвий цикл Semantic Release |
| AST Parse Commits: [feat, fix, perf, BREAKING CHANGE] |
| ├── Розрахунок дельти: 1.3.4 -> 1.4.0 (Minor Bump) |
| ├── Генерація артефакту CHANGELOG.md |
| ├── Створення Git-тегу: v1.4.0 |
| └── Експорт у змінні оточення: NEW_VERSION=1.4.0 |
+-------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------+
| 2. Ініціалізація Docker Buildx & QEMU |
| Платформи: linux/amd64 + linux/arm64 |
| Відновлення кешу шарів: actions/cache (type=gha) |
+-------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------+
| 3. Автентифікація в реєстрах контейнерів |
| ├── ghcr.io (GITHUB_TOKEN) |
| └── docker.io (DOCKERHUB_USERNAME / DOCKERHUB_TOKEN) |
+-------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------+
| 4. Мультиархітектурна збірка та Пуш |
| Теги: |
| ├── ghcr.io/org/app:1.4.0 |
| ├── ghcr.io/org/app:sha-7f9e2b1 |
| ├── ghcr.io/org/app:latest |
| └── docker.io/company/app:1.4.0 |
| Генерація OCI Image Manifest List (Fat Manifest) |
+-------------------------------------------------------------------+
|
v
+-------------------------------------------------------------------+
| 5. Публікація GitHub Release & Відправка Webhook у CD-оркестратор |
| (ArgoCD / FluxCD / Kubernetes Operator) |
+-------------------------------------------------------------------+output: "standalone"), тегує образ хешем коміту та пушить у приватний GHCR. Далі GitOps-оператор ArgoCD фіксує новий образ у маніфестах K8s.Процес неперервної доставки організовується через координацію конфігураційного файлу semantic-release, оптимізованого Dockerfile та робочого процесу GitHub Actions.
Файл .releaserc.json налаштовує плагіни та правила бампу версій:
{
"branches": [
"main",
{ "name": "beta", "prerelease": true },
{ "name": "next", "prerelease": true }
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "refactor", "release": "patch" },
{ "type": "chore", "release": false },
{ "breaking": true, "release": "major" }
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}Маркер [skip ci] у коміт-повідомленні плагіна @semantic-release/git критично важливий: він запобігає нескінченному циклу (Infinite Loop) повторного виклику CI при збереженні CHANGELOG.md.
Для збірки мінімалістичного контейнера Next.js використовується чотирифазний Dockerfile із підтримкою користувачів без привілеїв (Non-root user).
# -------------------------------------------------------------
# Стейдж 1: Базовий образ і менеджер пакетів
# -------------------------------------------------------------
FROM node:22-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable && corepack prepare [email protected] --activate
# -------------------------------------------------------------
# Стейдж 2: Встановлення залежностей
# -------------------------------------------------------------
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prefer-offline
# -------------------------------------------------------------
# Стейдж 3: Компіляція застосунку
# -------------------------------------------------------------
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# Збірка Next.js у режимі output: 'standalone'
RUN pnpm build
# -------------------------------------------------------------
# Стейдж 4: Мінімалістичний Production Runner
# -------------------------------------------------------------
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Безпека: запуск від непривілейованого користувача
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Копіювання публічних статичних файлів
COPY --from=builder /app/public ./public
# Копіювання автономного сервера та скомпільованих чанків
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]Запуск semantic-release, розрахунок SemVer на базі Conventional Commits, генерація CHANGELOG.md та створення Git-тегу vX.Y.Z.
Встановлення емуляторів архітектур CPU та ініціалізація екземпляра Docker BuildKit з підтримкою мультиплатформності.
Паралельний вхід у GitHub Container Registry (ghcr.io) та Docker Hub з використанням токенів доступу.
Збірка контейнера під платформи linux/amd64 та linux/arm64 з читанням шарів із кешу GitHub Actions (type=gha).
Пуш об'єднаного маніфесту образів у всі реєстри з незмінними тегами версій та SHA коміту.
Нижче наведено повністю готовий workflow .github/workflows/release-and-publish.yml, що виконує автоматичний реліз, створення тегу та публікацію мультиплатформних образів у ghcr.io та Docker Hub.
name: Continuous Delivery & Release
on:
push:
branches:
- main
concurrency:
group: cd-release-production
cancel-in-progress: false
permissions:
contents: write
packages: write
issues: write
pull-requests: write
env:
REGISTRY_GHCR: ghcr.io
IMAGE_NAME_GHCR: ${{ github.repository }}
REGISTRY_DOCKERHUB: docker.io
IMAGE_NAME_DOCKERHUB: company/nextjs-storefront
jobs:
# -------------------------------------------------------------
# Фаза 1: Автоматичний розрахунок версії та генерація релізу
# -------------------------------------------------------------
release:
name: Semantic Release & Versioning
runs-on: ubuntu-latest
outputs:
new_release_published: ${{ steps.semantic.outputs.new_release_published }}
new_release_version: ${{ steps.semantic.outputs.new_release_version }}
new_release_git_tag: ${{ steps.semantic.outputs.new_release_git_tag }}
steps:
- name: Checkout коду з повною історією (для аналізу комітів)
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Встановлення Node.js
uses: actions/setup-node@v4
with:
node-version: 22.x
- name: Встановлення інструментів Semantic Release
run: |
npm install -g \
semantic-release \
@semantic-release/commit-analyzer \
@semantic-release/release-notes-generator \
@semantic-release/changelog \
@semantic-release/git \
@semantic-release/github \
conventional-changelog-conventionalcommits
- name: Запуск Semantic Release
id: semantic
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
# -------------------------------------------------------------
# Фаза 2: Мультиплатформна збірка та публікація образів
# -------------------------------------------------------------
publish-container:
name: Build & Push Multi-Arch Container
needs: [release]
if: needs.release.outputs.new_release_published == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout репозиторію
uses: actions/checkout@v4
- name: Встановлення емуляторів QEMU (для мультиплатформності)
uses: docker/setup-qemu-action@v3
with:
platforms: linux/amd64,linux/arm64
- name: Ініціалізація Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Авторизація в GitHub Container Registry (GHCR)
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY_GHCR }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Авторизація в Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY_DOCKERHUB }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Витягнення метаданих та генерація Docker тегів
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME_GHCR }}
${{ env.REGISTRY_DOCKERHUB }}/${{ env.IMAGE_NAME_DOCKERHUB }}
tags: |
type=raw,value=${{ needs.release.outputs.new_release_version }}
type=raw,value=latest
type=sha,format=short,prefix=sha-
- name: Збірка та відправка мультиплатформного OCI-образу
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Кешування шарів в інфраструктурі GitHub Actions Cache
cache-from: type=gha
cache-to: type=gha,mode=maxПлагін @semantic-release/git комітить оновлений CHANGELOG.md та package.json назад у гілку main. Якщо повідомлення коміту не містить інструкцію пропуску пайплайну, GitHub Actions сприймає це як новий push у гілку main і запускає workflow заново. Процес входить у нескінченну рекурсію, створюючи десятки комітів та вичерпуючи ліміти акаунту.
// НЕПРАВИЛЬНО: Коміт без пропуску CI
{
"plugins": [
["@semantic-release/git", { "message": "release: update changelog" }]
]
}
// ПРАВИЛЬНО: Додавання маркера [skip ci] у шаблон коміту
{
"plugins": [
[
"@semantic-release/git",
{
"assets": ["CHANGELOG.md", "package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}Розгортання образів з тегом :latest без фіксації SemVer (v1.2.3) або хешу Git коміту (sha-7a1b8c). При виникненні критичного інциденту в продакшені оркестратор (Kubernetes) не може виконати детермінований відкат (Rollback) на попередню версію, оскільки тег :latest уже перезаписаний пошкодженим кодом.
# НЕПРАВИЛЬНО: Публікація лише плаваючого тегу latest
tags: my-org/app:latest
# ПРАВИЛЬНО: Набір незмінних тегів + плаваючі теги
tags: |
type=raw,value=${{ needs.release.outputs.new_release_version }}
type=sha,format=short,prefix=sha-
type=raw,value=latestПри збірці під linux/arm64 на стандартному x86 раннері крок RUN pnpm build виконується через емуляцію QEMU. Емуляція інструкцій CPU сповільнює компіляцію JavaScript/Rust у 5–10 разів (білд триває 25 хвилин замість 2 хвилин). Рішення — або використовувати нативні ARM раннери GitHub Actions (runs-on: ubuntu-24.04-arm), або компілювати бандл на нативній машині хоста та передавати артефакти в Docker через стейдж COPY.
Використання ARG NPM_TOKEN і наступний RUN echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc. Навіть якщо видалити файл .npmrc на наступному кроці, значення токену назавжди залишиться в метаданих проміжного шару Docker, доступному будь-якому користувачеві, який завантажив публічний образ через команду docker history.
# НЕПРАВИЛЬНО: Секрет зберігається в метаданих шару
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
# ПРАВИЛЬНО: Використання монтування секретів BuildKit без збереження в шарах
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc pnpm install --frozen-lockfileВпровадження commitlint та Husky для примусового дотримання специфікації Conventional Commits усіма розробниками.
Налаштування Next.js output: standalone, багатостадійної збірки, запуску від користувача non-root та ізоляції шарів.
Створення конфігурації .releaserc.json з автоматичною генерацією CHANGELOG.md та створенням захищених Git-тегів.
Підключення QEMU, налаштування збірки під linux/amd64 та linux/arm64 зі збереженням кешу шарів у GitHub Actions Cache.
Авторизація в реєстрах, генерація OCI Image Manifest List та зв'язування з тригерами деплою в GitOps операторах.