Appearance
GitLab CI/CD + Semantic Release
⚠ Historical context. Этот гайд писался, когда
partizap-frontendиспользовал npm. С 2026-04-09 проект мигрирован на pnpm (DEV-328/329/330), см.plans/2026-04-09-pnpm-migration-design.md. Примеры команд в этом гайде (npm ci,npm run ...) остаются справочными для общей схемы semantic-release, но в актуальном CIpartizap-frontendиспользуютсяpnpm install --frozen-lockfileиpnpm exec semantic-releaseвнутриdocker run node:22-alpineс bind-mount/home/gitlab-runner/pnpm-store. Актуальный.gitlab-ci.yml— единственный источник правды.
Гайд по настройке CI/CD пайплайнов с автоматическим версионированием через semantic-release. Два варианта: с Docker (все команды в контейнерах) и без Docker (node на хосте).
Содержание
- Conventional Commits
- Semantic Release: установка и конфигурация
- GitLab CI: вариант с Docker
- GitLab CI: вариант без Docker
- Trivy: security audit gate
- Dockerfile (multi-stage)
- .npmrc для кросс-платформенных сборок
- GitLab: токены и переменные
- Подводные камни и решения
1. Conventional Commits
Все коммиты должны следовать формату type(scope): description.
Типы и их влияние на версию
| Тип | Релиз | Пример |
|---|---|---|
feat: / feature: | minor (1.0.0 -> 1.1.0) | feat: каталог с фильтрами |
fix: | patch (1.0.0 -> 1.0.1) | fix(auth): валидация email |
perf: | patch | perf: кэширование категорий |
revert: | patch | revert: откат миграции |
ci: | patch | ci: лимит памяти Docker |
refactor: | patch | refactor: extract composable |
style: | patch | style: fix spacing |
build: | patch | build: update Dockerfile |
BREAKING CHANGE: в body | major (1.0.0 -> 2.0.0) | любой тип + breaking change |
chore: / docs: / test: | нет релиза | chore: обновление зависимостей |
По умолчанию semantic-release не создаёт релиз для
ci:,refactor:,style:,build:. Добавляется черезreleaseRules.
2. Semantic Release
Установка
bash
npm install -D semantic-release \
@semantic-release/commit-analyzer \
@semantic-release/release-notes-generator \
@semantic-release/exec \
@semantic-release/gitlab \
conventional-changelog-conventionalcommitsНе использовать
@semantic-release/changelog+@semantic-release/git— они создаютchore(release)коммиты в main, засоряя историю и рассинхронизируя lockfile. Release notes хранятся в GitLab Releases.
package.json version
Установить "version": "0.0.0-development" — реальная версия определяется git-тегами и передаётся через --build-arg NUXT_PUBLIC_APP_VERSION.
.releaserc.json
json
{
"branches": ["main"],
"tagFormat": "v${version}",
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "conventionalcommits",
"releaseRules": [
{ "type": "feat", "release": "minor" },
{ "type": "feature", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "revert", "release": "patch" },
{ "type": "ci", "release": "patch" },
{ "type": "refactor", "release": "patch" },
{ "type": "style", "release": "patch" },
{ "type": "build", "release": "patch" }
]
}],
["@semantic-release/release-notes-generator", {
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{ "type": "feat", "section": "Features" },
{ "type": "feature", "section": "Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance" },
{ "type": "revert", "section": "Reverts" },
{ "type": "ci", "section": "CI/CD" },
{ "type": "refactor", "section": "Refactoring" },
{ "type": "style", "section": "Styles" },
{ "type": "build", "section": "Build" }
]
}
}],
["@semantic-release/exec", {
"prepareCmd": "echo APP_VERSION=${nextRelease.version} > release.env"
}],
"@semantic-release/gitlab"
]
}@semantic-release/exec и dotenv-артефакт
@semantic-release/exec записывает APP_VERSION=x.y.z в release.env. В CI release-джоба экспортирует этот файл как dotenv artifact — последующие джобы (build:prod, deploy:prod) получают $APP_VERSION автоматически.
### Self-hosted GitLab
Если GitLab не на gitlab.com, нужно указать URL для API:
```json
["@semantic-release/gitlab", {
"gitlabUrl": "http://127.0.0.1:8081"
}]gitlabUrl имеет наивысший приоритет (выше env vars). Это необходимо когда:
- GitLab на localhost с нестандартным портом
- Внешний URL (
https://gitlab.example.com) недоступен из Docker-контейнера (SSL, DNS) CI_SERVER_URLуказывает на внешний адрес, а не на localhost
3. GitLab CI: Docker
Все npm-команды бегут внутри docker run node:22-alpine. На хосте нужен только Docker.
Архитектура пайплайнов
feature/fix branches + merge requests:
validate (lint+typecheck+test) + security:audit (Trivy) — early feedback
develop push:
validate + security:audit → docker build dev → deploy staging (auto) → e2e
main push (единый пайплайн):
release (semantic-release → тег + release.env с APP_VERSION)
→ build:prod (v1.2.3 + latest) → deploy:prod (manual) → cleanup:prod
tag v* (только для rollback):
deploy:rollback (manual, без пересборки — retag + restart)DEV-324: Ранее release и build/deploy были в разных пайплайнах (main → tag). Теперь всё в одном через dotenv-артефакт. Tag-пайплайн оставлен только для rollback.
.gitlab-ci.yml (полный)
yaml
workflow:
rules:
- if: $CI_COMMIT_TAG =~ /^v/ # rollback only
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_COMMIT_BRANCH == "main"
stages:
- validate
- release
- build
- deploy
variables:
DEPLOY_DIR: /opt/myapp-frontend
DOCKER_BUILDKIT: "1"
NODE_IMAGE: node:22-alpine
# ===================================
# Validate (develop only)
# ===================================
lint:
stage: validate
tags: [myapp-shell]
script:
- >-
docker run --rm
--user "$(id -u):$(id -g)"
-e HOME=/tmp
-v "$CI_PROJECT_DIR:/app" -w /app
$NODE_IMAGE
sh -c "npm ci && npm run lint"
rules:
- if: $CI_COMMIT_BRANCH == "develop"
typecheck:
stage: validate
tags: [myapp-shell]
script:
- >-
docker run --rm
--user "$(id -u):$(id -g)"
-e HOME=/tmp
-v "$CI_PROJECT_DIR:/app" -w /app
$NODE_IMAGE
sh -c "npm ci && npm run typecheck"
rules:
- if: $CI_COMMIT_BRANCH == "develop"
test:
stage: validate
tags: [myapp-shell]
script:
- >-
docker run --rm
--user "$(id -u):$(id -g)"
-e HOME=/tmp
-v "$CI_PROJECT_DIR:/app" -w /app
$NODE_IMAGE
sh -c "npm ci && npm run test:run"
allow_failure: true # убрать когда тесты стабилизируются
rules:
- if: $CI_COMMIT_BRANCH == "develop"
# ===================================
# Release (main only)
# semantic-release → тег v* + release.env (dotenv artifact)
# ===================================
release:
stage: release
tags: [myapp-shell]
script:
- >-
docker run --rm
--network=host
-e HOST_UID="$(id -u)" -e HOST_GID="$(id -g)"
-v "$CI_PROJECT_DIR:/app" -w /app
-e GITLAB_TOKEN=$GITLAB_TOKEN
-e GL_TOKEN=$GITLAB_TOKEN
-e CI=true
-e GITLAB_CI=true
-e CI_SERVER_URL=$CI_SERVER_URL
-e CI_SERVER_HOST=$CI_SERVER_HOST
-e GITLAB_URL=http://127.0.0.1:8081
-e CI_PROJECT_ID=$CI_PROJECT_ID
-e CI_PROJECT_PATH=$CI_PROJECT_PATH
-e CI_PROJECT_URL=$CI_PROJECT_URL
-e CI_COMMIT_BRANCH=$CI_COMMIT_BRANCH
-e CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME
-e CI_COMMIT_SHA=$CI_COMMIT_SHA
-e CI_JOB_TOKEN=$CI_JOB_TOKEN
-e CI_REPOSITORY_URL=$CI_REPOSITORY_URL
$NODE_IMAGE
sh -c "apk add --no-cache git &&
git config --global safe.directory /app &&
git config --global credential.helper '!f() { echo username=oauth2; echo \"password=\${GL_TOKEN}\"; }; f' &&
git checkout -B \${CI_COMMIT_BRANCH} HEAD &&
npm ci --ignore-scripts &&
npx semantic-release;
status=\$?; rm -rf node_modules; chown -R \${HOST_UID}:\${HOST_GID} .git; exit \$status"
# Fallback: если semantic-release не создал новый релиз
- |
if [ ! -f release.env ]; then
FALLBACK_VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//')
if [ -n "$FALLBACK_VERSION" ]; then
echo "APP_VERSION=${FALLBACK_VERSION}" > release.env
else
echo "APP_VERSION=0.0.0" > release.env
fi
fi
artifacts:
reports:
dotenv: release.env
expire_in: 1 day
rules:
- if: $CI_COMMIT_BRANCH == "main"
# ===================================
# Develop: build + deploy
# ===================================
build:dev:
stage: build
tags: [myapp-shell]
needs: [lint, typecheck, test]
resource_group: frontend-build
script:
- APP_VERSION=$(git describe --tags --always 2>/dev/null || echo "dev")
- docker build --memory=1g --memory-swap=1536m
--build-arg NUXT_PUBLIC_APP_VERSION=${APP_VERSION}
-t myapp-dev:latest .
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy:dev:
stage: deploy
tags: [myapp-shell]
needs: [build:dev]
script:
- cp docker-compose.dev.yml $DEPLOY_DIR/docker-compose.dev.yml
- cd $DEPLOY_DIR
- docker compose -p myapp-dev -f docker-compose.dev.yml up -d --force-recreate
- docker image prune -f
environment:
name: development
url: https://dev.example.com
rules:
- if: $CI_COMMIT_BRANCH == "develop"
# ===================================
# Production: build + deploy (main, after release)
# APP_VERSION приходит из dotenv-артефакта release-джобы
# ===================================
build:prod:
stage: build
tags: [myapp-shell]
needs:
- job: release
artifacts: true
resource_group: frontend-build
script:
- >-
docker build --memory=1g --memory-swap=1536m
--build-arg NUXT_PUBLIC_APP_VERSION=${APP_VERSION}
-t myapp-prod:v${APP_VERSION}
-t myapp-prod:latest .
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy:prod:
stage: deploy
tags: [myapp-shell]
needs:
- job: build:prod
- job: release
artifacts: true
script:
- docker rm -f myapp-prod || true
- cp docker-compose.prod.yml $DEPLOY_DIR/docker-compose.prod.yml
- cd $DEPLOY_DIR
- docker compose -p myapp-prod -f docker-compose.prod.yml up -d --force-recreate
- docker image prune -f
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
# ===================================
# Rollback: tag-triggered deploy (без пересборки)
# Использует существующий образ — retag + restart
# ===================================
deploy:rollback:
stage: deploy
tags: [myapp-shell]
script:
- docker tag myapp-prod:${CI_COMMIT_TAG} myapp-prod:latest
- docker rm -f myapp-prod || true
- cd $DEPLOY_DIR
- docker compose -p myapp-prod -f docker-compose.prod.yml up -d --force-recreate
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG =~ /^v/
when: manualКлючевые моменты Docker-варианта
| Параметр | Зачем |
|---|---|
--user "$(id -u):$(id -g)" | Файлы в volume создаются от пользователя runner, а не root |
-e HOME=/tmp | npm cache writable для non-root пользователя |
--network=host | Release-джоба достучится до GitLab на localhost |
--ignore-scripts | В release-джобе nuxt prepare не нужен |
rm -rf node_modules | Очистка root-owned файлов в конце release-джобы |
chown -R $UID:$GID .git | Возврат прав на .git для runner'а после git ops в контейнере |
git checkout -B $BRANCH HEAD | GitLab делает detached HEAD, semantic-release требует ветку |
git credential.helper | Авторизация для git push тега обратно в репозиторий |
GITLAB_URL=http://127.0.0.1:8081 | Плагин @semantic-release/gitlab ходит на API напрямую |
artifacts.reports.dotenv | release.env экспортирует $APP_VERSION в последующие джобы |
needs: [release] + artifacts: true | build:prod/deploy:prod получают $APP_VERSION из dotenv |
deploy:rollback | Manual-джоба по тегу v* — retag + restart без пересборки |
4. GitLab CI: без Docker
Node.js установлен на хосте runner'а. Используется pnpm (или npm).
Архитектура пайплайнов
main push:
install → lint / type-check / test (параллельно) → release (тег v*)
tag v*:
install → build staging (auto) → build production (manual) → deploy.gitlab-ci.yml (полный, pnpm)
yaml
workflow:
rules:
- if: $CI_COMMIT_TAG =~ /^v/
- if: $CI_COMMIT_BRANCH == "main"
stages:
- install
- validate
- release
- build
- deploy
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .pnpm-store/
# ===================================
# Install
# ===================================
install:
stage: install
script:
- pnpm install --frozen-lockfile
artifacts:
paths:
- node_modules/
expire_in: 1 hour
# ===================================
# Validate (main only)
# ===================================
lint:
stage: validate
needs: [install]
script:
- pnpm run lint
rules:
- if: $CI_COMMIT_BRANCH == "main"
type-check:
stage: validate
needs: [install]
script:
- pnpm run type-check
rules:
- if: $CI_COMMIT_BRANCH == "main"
test:
stage: validate
needs: [install]
script:
- pnpm run test
rules:
- if: $CI_COMMIT_BRANCH == "main"
# ===================================
# Release (main, after validation)
# ===================================
release:
stage: release
needs: [lint, type-check, test]
script:
- npx semantic-release
rules:
- if: $CI_COMMIT_BRANCH == "main"
# ===================================
# Build + Deploy (tag v*)
# ===================================
build_staging:
stage: build
needs: [install]
script:
- pnpm vite build --mode staging
artifacts:
paths:
- dist/
expire_in: 1 hour
rules:
- if: $CI_COMMIT_TAG =~ /^v/
deploy_staging:
stage: deploy
needs: [build_staging]
script:
- scp -r dist/* user@staging-server:/var/www/app/
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_TAG =~ /^v/
build_production:
stage: build
needs: [install]
script:
- pnpm vite build --mode production
artifacts:
paths:
- dist/
expire_in: 1 hour
rules:
- if: $CI_COMMIT_TAG =~ /^v/
deploy_production:
stage: deploy
needs: [build_production]
script:
- scp -r dist/* user@prod-server:/var/www/app/
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_TAG =~ /^v/
when: manualКлючевые отличия от Docker-варианта
| Аспект | Docker | Без Docker |
|---|---|---|
| Node.js на хосте | Не нужен | Обязателен |
| Изоляция | Полная (контейнер) | Нет (shared state) |
| Install стадия | Внутри каждой джобы | Отдельная джоба + artifacts |
| Cache | Не нужен | pnpm-lock / package-lock |
| Release-джоба | Прокидывание env vars | Всё доступно из коробки |
nuxt prepare | Может потребовать musl bindings | Работает нативно |
| Права на файлы | --user + HOME=/tmp | Нет проблем |
5. Trivy: security audit gate
Trivy сканирует npm-зависимости на известные уязвимости (CVE). Блокирует пайплайн при HIGH/CRITICAL.
CI-джоба
yaml
variables:
TRIVY_IMAGE: aquasec/trivy:0.69.6 # пин версии, обновлять вручную
security:audit:
stage: validate
tags:
- partizap-shell
before_script:
# Используем локальный кеш образа, пулим только если нет (обход Docker Hub rate limit)
- docker image inspect $TRIVY_IMAGE >/dev/null 2>&1 || docker pull $TRIVY_IMAGE
script:
# JSON report — артефакт для уведомлений (DEV-182)
- >-
docker run --rm
-v "$CI_PROJECT_DIR:/app" -w /app
-v trivy-cache:/root/.cache/trivy
$TRIVY_IMAGE
fs --scanners vuln --severity HIGH,CRITICAL
--format json --output /app/trivy-report.json
/app
# Table output — exit 1 если есть уязвимости
- >-
docker run --rm
-v "$CI_PROJECT_DIR:/app" -w /app
-v trivy-cache:/root/.cache/trivy
$TRIVY_IMAGE
fs --scanners vuln --severity HIGH,CRITICAL
--exit-code 1
/app
artifacts:
when: on_failure
paths:
- trivy-report.json
expire_in: 30 days
rules:
- if: $CI_COMMIT_BRANCH == "develop"
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != "develop" && $CI_COMMIT_BRANCH != "main"Ключевые параметры
| Параметр | Зачем |
|---|---|
TRIVY_IMAGE variable | Пин версии образа — обход Docker Hub rate limit, воспроизводимость |
docker image inspect || docker pull | Используем локальный кеш, пулим только если образа нет на runner'е |
fs --scanners vuln | Сканирование файловой системы, только уязвимости (без secrets/misconfig) |
--severity HIGH,CRITICAL | Игнорировать LOW/MEDIUM — слишком шумно |
--exit-code 1 | Падать при обнаружении уязвимостей (gate) |
-v trivy-cache:/root/.cache/trivy | Кэш БД уязвимостей (~88MB) между запусками |
--format json --output | JSON-отчёт для будущих уведомлений |
Когда запускается
- feature/fix ветки — при каждом пуше (ранний фидбек)
- merge request — блокирует MR
- develop push — блокирует build если есть уязвимости
.trivyignore
Файл для подавления ложных срабатываний или unfixable CVE. Формат — один CVE на строку:
# Review by 2026-04-15: no fix in transitive dep
CVE-2026-XXXXXКаждая запись должна иметь дату ревью. Не оставлять stale исключения.
Локальная проверка
bash
docker run --rm \
-v "$(pwd):/app" -w /app \
-v trivy-cache:/root/.cache/trivy \
aquasec/trivy:0.69.6 \
fs --scanners vuln --severity HIGH,CRITICAL \
--exit-code 1 \
/appВерсию образа держать в синхроне с
TRIVY_IMAGEв.gitlab-ci.yml. БД уязвимостей обновляется автоматически при каждом запуске — образ обновлять редко (раз в пару месяцев).
Исправление уязвимостей
- Запустить скан, посмотреть таблицу
- Все уязвимые пакеты транзитивные? →
npm audit fix - Прямая зависимость? → обновить в
package.json - Нет фикса? → добавить в
.trivyignoreс датой ревью - Перезапустить скан — убедиться что 0 HIGH/CRITICAL
6. Dockerfile
Multi-stage build для Nuxt/Node.js приложений:
dockerfile
# syntax=docker/dockerfile:1
# Stage 1: Install dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
npm ci
# Stage 2: Build
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ARG NUXT_PUBLIC_APP_VERSION=""
ENV NUXT_PUBLIC_APP_VERSION=$NUXT_PUBLIC_APP_VERSION
ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=1536"
RUN --mount=type=cache,target=/root/.npm \
npm run build
# Stage 3: Production (только .output, минимальный образ)
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/.output ./.output
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]Принципы
- deps ставит ВСЕ зависимости (без
--omit=dev) — postinstall (nuxt prepare) нужен - build собирает приложение; версия передаётся через
--build-arg NUXT_PUBLIC_APP_VERSION(из dotenv-артефакта release-джобы) и вшивается вruntimeConfig.public.appVersion— отображается в футере.package.jsonсодержит"version": "0.0.0-development"— не является источником версии - production копирует только
.output— финальный образ ~50MB --mount=type=cacheускоряет повторные сборки
7. .npmrc
Необходим при использовании node:22-alpine в Docker. Lockfile с macOS не содержит native bindings для Alpine (musl libc).
ini
supportedArchitectures[os][]=current
supportedArchitectures[os][]=linux
supportedArchitectures[cpu][]=current
supportedArchitectures[cpu][]=x64
supportedArchitectures[libc][]=current
supportedArchitectures[libc][]=muslЧто это делает
npm installна macOS включает в lockfile bindings для обеих платформ (darwin + linux-musl)npm ciв Alpine-контейнере находит свой@oxc-parser/binding-linux-x64-muslв lockfile- Лишние бинарники (пара МБ) скачиваются локально, но не используются
current= текущая платформа разработчика (macOS arm64/x64)
Когда нужен
- Dockerfile использует Alpine (
node:*-alpine) - Lockfile генерируется на macOS/Windows
- Есть native bindings (oxc-parser, esbuild, lightningcss, rollup и др.)
Когда НЕ нужен
- Dockerfile использует Debian (
node:22) — нет проблем с musl - Node.js установлен на хосте (без Docker) — та же платформа
- Lockfile генерируется на той же платформе где собирается
Никогда не пересоздавайте lockfile внутри Docker. Это ломает воспроизводимость.
.npmrcсsupportedArchitectures— правильное решение.
8. GitLab: токены и переменные
Project Access Token
Settings → Access Tokens → Add new token:
| Параметр | Значение |
|---|---|
| Name | semantic-release |
| Scopes | api, read_api, read_repository, write_repository |
| Role | Maintainer |
| Expiration | 12 месяцев (обновлять!) |
CI/CD Variables
Settings → CI/CD → Variables:
| Variable | Значение | Flags |
|---|---|---|
GITLAB_TOKEN | значение Project Access Token | Masked, Protected |
Protected= доступна только на protected branches (main). Для develop-пайплайна не нужна.
Проверка
Если semantic-release выдаёт EINVALIDGLTOKEN:
- Токен действующий? (не истёк)
- Scope
apiесть? - Role = Maintainer?
GITLAB_URLуказывает на доступный адрес? (для self-hosted)- Из Docker-контейнера доступен GitLab API? (
--network=hostили DNS)
9. Подводные камни
Docker: root-owned файлы
Проблема: docker run -v + npm ci создаёт node_modules/ от root. Runner не может удалить при следующем checkout.
Решение: --user "$(id -u):$(id -g)" для validate-джоб. Для release-джобы (нужен root для apk add) — rm -rf node_modules в конце скрипта.
Docker: npm cache permission denied
Проблема: --user + npm пытается писать в /.npm (root-owned).
Решение: -e HOME=/tmp — npm cache уходит в /tmp/.npm.
Docker: detached HEAD
Проблема: GitLab checkout делает detached HEAD. semantic-release не видит ветку (branch: undefined).
Решение: git checkout -B ${CI_COMMIT_BRANCH} HEAD перед npx semantic-release. Также передавать CI_COMMIT_REF_NAME как fallback.
Docker: GitLab API недоступен
Проблема: @semantic-release/gitlab ходит на https://gitlab.example.com (внешний URL из CI_SERVER_URL). Из контейнера может быть недоступен (SSL, DNS).
Решение: gitlabUrl в .releaserc.json → http://127.0.0.1:PORT. Контейнер должен использовать --network=host.
Docker: git authentication
Проблема: semantic-release делает git ls-remote и git push — нужны credentials.
Решение: git credential helper внутри контейнера:
sh
git config --global credential.helper \
'!f() { echo username=oauth2; echo "password=${GL_TOKEN}"; }; f'Docker Hub: rate limit при pull
Проблема: error from registry: You have reached your unauthenticated pull rate limit. Анонимный лимит Docker Hub — 100 pull/6h на IP.
Решение:
- Пинить версию образа (переменная
TRIVY_IMAGE), не использовать:latest before_script: docker image inspect || docker pull— пулить только если образа нет- Первичная загрузка образа на сервер вручную:bash
# На Mac (arm64) пулим amd64 версию для сервера docker pull --platform linux/amd64 aquasec/trivy:0.69.6 docker save aquasec/trivy:0.69.6 -o /tmp/trivy-amd64.tar scp /tmp/trivy-amd64.tar user@server:/tmp/ ssh user@server "docker load < /tmp/trivy-amd64.tar && rm /tmp/trivy-amd64.tar"
Важно: если разработка на Mac (arm64), а сервер amd64 — нужен
--platform linux/amd64приdocker pull. Иначеexec format error.
Alpine: missing native bindings
Проблема: Cannot find module '@oxc-parser/binding-linux-x64-musl'. Lockfile с macOS не включает musl-bindings.
Решение: .npmrc с supportedArchitectures (см. раздел 6). Затем rm -rf node_modules && npm install локально.
semantic-release: нет релиза
Проблема: Пайплайн зелёный, но тег не создан.
Причины:
- Все коммиты
chore:/docs:/test:— не триггерят релиз (при стандартных releaseRules) ci:,refactor:,style:,build:по умолчанию не триггерят — нуженreleaseRules- Ветка не в списке
branchesв.releaserc.json
semantic-release: первый релиз
При первом запуске semantic-release анализирует ВСЕ коммиты с начала репозитория и создаёт v1.0.0 (если есть хотя бы один feat: или fix:).
Чеклист инициализации
Новый проект с Docker
[ ] npm install -D semantic-release и плагины (exec, gitlab, commit-analyzer, release-notes-generator)
[ ] Создать .releaserc.json (с @semantic-release/exec для release.env)
[ ] Установить "version": "0.0.0-development" в package.json
[ ] Добавить release.env в .gitignore
[ ] Создать .npmrc (если Alpine)
[ ] rm -rf node_modules && npm install (пересоздать lockfile)
[ ] Создать Dockerfile (multi-stage)
[ ] Создать docker-compose.dev.yml / docker-compose.prod.yml
[ ] Создать .gitlab-ci.yml (release с dotenv-артефактом, build:prod по main, rollback по тегу)
[ ] GitLab: создать Project Access Token (api + write_repository, Maintainer)
[ ] GitLab: добавить GITLAB_TOKEN в CI/CD Variables (Masked + Protected)
[ ] Первый push в main — проверить что release → build:prod → deploy:prod работает в одном пайплайнеНовый проект без Docker
[ ] npm install -D semantic-release и плагины (или pnpm add -D)
[ ] Создать .releaserc.json
[ ] Установить "version": "0.0.0-development" в package.json
[ ] Создать .gitlab-ci.yml
[ ] GitLab: создать Project Access Token
[ ] GitLab: добавить GITLAB_TOKEN в CI/CD Variables
[ ] Убедиться что Node.js установлен на runner
[ ] Первый push в main — проверить release-джобу