Deployment workflows
Типовые CD-паттерны: deploy after CI, tag-based deploy, manual `workflow_dispatch`, promotion между environments, rollback jobs и `concurrency` для защиты продакшена.
Deployment workflows
Deployment workflow — это workflow, который не просто проверяет код, а доставляет уже выбранную версию в конкретную среду: staging, production, preview environment, registry или cloud service. В GitHub Actions он обычно собирается из трёх слоёв: trigger решает, когда вообще стартовать; needs, if и artifacts связывают CI с deploy; environment, protection rules и concurrency защищают цель деплоя. Поэтому эта тема стоит рядом с Environments и protection rules, но фокус здесь другой: не настройки environment как объекта, а рабочие CD-паттерны в YAML.
flowchart TD
A[push / tag / workflow_dispatch] --> B[CI: test, build, package]
B --> C[upload artifact или image]
C --> D[deploy staging]
D --> E{production gate}
E -->|approved| F[deploy production]
E -->|rejected| G[stop]
F --> H[smoke check и deployment history]
R[manual rollback] --> Fflowchart TD
A[push / tag / workflow_dispatch] --> B[CI: test, build, package]
B --> C[upload artifact или image]
C --> D[deploy staging]
D --> E{production gate}
E -->|approved| F[deploy production]
E -->|rejected| G[stop]
F --> H[smoke check и deployment history]
R[manual rollback] --> FDeploy after CI: самый обычный путь
Базовый CD-паттерн: сначала CI job проверяет и собирает артефакт, потом deploy job забирает именно этот артефакт. Это важнее, чем кажется. Если production job заново делает npm install && npm run build, вы уже деплоите не строго тот build, который прошёл тесты: могли измениться lockfile resolution, registry, системные пакеты runner image или переменные сборки. Надёжнее собрать один output и передать его через Artifacts и reports или опубликовать image/package, что дальше подробно связано с Packages и container images.
name: cd
on:
push:
branches: [main]
permissions:
contents: read
jobs:
ci:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: web-dist
path: dist/
deploy-staging:
needs: ci
runs-on: ubuntu-24.04
environment:
name: staging
url: https://staging.example.com
concurrency:
group: deploy-staging
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: web-dist
path: dist/
- run: ./scripts/deploy.sh staging dist
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}needs: ci делает deploy зависимым от успешной сборки. environment: staging даёт environment secrets/vars и пишет deployment history. concurrency не связан с environment автоматически: это отдельный ключ. Если другой workflow тоже деплоит в staging, но не использует ту же concurrency group, GitHub его не остановит.
Production concurrency: очередь или отмена
Для production обычно нужно «не больше одного деплоя одновременно». GitHub Actions поддерживает concurrency на уровне workflow или job. В одной concurrency group одновременно может выполняться только один run или job; новый run в той же группе становится pending, а старый pending по умолчанию заменяется новым. Если указать cancel-in-progress: true, GitHub также отменит уже выполняющийся run/job в этой группе.
Для staging отмена часто нормальна: новый commit делает старый staging-deploy бесполезным. Для production это опаснее: прерванный deploy может оставить систему в промежуточном состоянии, если deploy script не атомарный. Поэтому production часто ставят без cancel-in-progress: true или используют явно спроектированный rollback.
deploy-production:
needs: ci
runs-on: ubuntu-24.04
environment:
name: production
url: https://example.com
concurrency:
group: deploy-production
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: web-dist
path: dist/
- run: ./scripts/deploy.sh production dist
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}Здесь protection rules из Environments и protection rules решают, кто и с каких refs может пройти в production, а concurrency защищает сам процесс от параллельного выполнения. Это разные предохранители.
Tag-based deploy
Tag-based deploy подходит для релизов: CI на main может быть быстрым и частым, а production стартует только от v1.8.0, v1.8.1 и похожих tags. Тогда tag становится явной точкой выпуска. Этот паттерн естественно продолжает Release automation: release notes, assets и публикация package часто привязываются к тому же tag.
name: release-deploy
on:
push:
tags: ['v*.*.*']
jobs:
deploy-production:
runs-on: ubuntu-24.04
environment: production
concurrency:
group: deploy-production
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy-release.sh "${GITHUB_REF_NAME}"
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}У tag-based deploy есть дисциплина: нельзя тихо «додеплоить main». Если нужно выкатить fix, создаётся новый tag. Если production environment разрешает только tags v*, branch/tag restrictions дополнительно страхуют от ручного запуска с неправильного ref.
Manual deploy через workflow_dispatch
workflow_dispatch — ручной запуск workflow из GitHub UI или API. Он полезен для promotion, emergency rollback, one-off maintenance и деплоя в выбранную среду. В inputs можно задать choice, boolean, string и другие типы; внутри workflow значения доступны через inputs.
name: promote
on:
workflow_dispatch:
inputs:
target:
description: Environment
type: choice
required: true
options: [staging, production]
version:
description: Artifact version or image tag
type: string
required: true
jobs:
deploy:
runs-on: ubuntu-24.04
environment: ${{ inputs.target }}
concurrency:
group: deploy-${{ inputs.target }}
steps:
- uses: actions/checkout@v4
- run: ./scripts/promote.sh "$TARGET" "$VERSION"
env:
TARGET: ${{ inputs.target }}
VERSION: ${{ inputs.version }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}Такой workflow выглядит универсально, но не должен превращаться в «кнопку всемогущества». Для production всё равно нужны required reviewers, branch/tag restrictions, минимальные Permissions и GITHUB_TOKEN и нормальная история, какой artifact или image tag был выкачен.
Promotion между environments
Promotion — это не «собрать ещё раз для production». Это «взять уже проверенную версию и продвинуть её дальше». В web-app это может быть artifact из CI; в container-пайплайне — image digest; в package-пайплайне — immutable package version. Смысл один: staging и production получают один и тот же build output, только с разными environment secrets/vars из Variables, env и secrets.
Практический вариант:
jobs:
deploy-staging:
needs: ci
environment: staging
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy-image.sh staging "$IMAGE_DIGEST"
env:
IMAGE_DIGEST: ${{ needs.ci.outputs.image_digest }}
deploy-production:
needs: [ci, deploy-staging]
environment: production
runs-on: ubuntu-24.04
concurrency:
group: deploy-production
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy-image.sh production "$IMAGE_DIGEST"
env:
IMAGE_DIGEST: ${{ needs.ci.outputs.image_digest }}Если production требует ручного approval, job остановится перед доступом к production secrets. Это хорошо: reviewer видит, какой workflow и commit ждут approve, а deployment history потом покажет, что именно ушло в среду.
Rollback jobs
Rollback — это тоже deployment workflow, а не shell-команда «где-то на сервере». Его стоит делать отдельным workflow_dispatch с явным input: версия, image tag, release id или artifact id. Rollback должен использовать тот же environment и ту же concurrency group, что production deploy, иначе rollback может начать гонку с обычным deploy.
name: rollback
on:
workflow_dispatch:
inputs:
version:
description: Version to restore, for example v1.7.4
type: string
required: true
jobs:
rollback-production:
runs-on: ubuntu-24.04
environment: production
concurrency:
group: deploy-production
steps:
- uses: actions/checkout@v4
- run: ./scripts/rollback.sh "$VERSION"
env:
VERSION: ${{ inputs.version }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}Хороший rollback не требует пересобирать старый commit. Он переключает систему на уже существующий artifact, image digest, package version или release asset. Если deploy в облако получает credentials через OIDC, rollback job должен использовать тот же подход: короткоживущий доступ через Cloud deploy через OIDC, а не старый аварийный cloud key в secrets.
Частые ошибки
Первая ошибка — объединить CI, staging, production, release publish и rollback в один огромный YAML без явных границ. GitHub Actions это позволит, но troubleshooting станет хуже: непонятно, где build, где promotion, где ручное решение. Лучше иметь понятные jobs и осмысленные names.
Вторая — ставить cancel-in-progress: true на production просто потому, что так делают в CI. Для линтера это удобно; для деплоя с миграциями БД может быть вредно. Production deploy должен быть либо безопасно прерываемым, либо сериализованным без отмены running job.
Третья — деплоить по push в main без environment gate. Это может быть нормально для маленького staging, но production обычно требует хотя бы protected branch, required reviewers или tag-based process. Даже если команда маленькая, automation должна переживать усталость, случайный re-run и неправильный branch.
See also
- Environments и protection rules — approval, wait timers, branch/tag restrictions и environment secrets.
- CI pipeline — как собрать и проверить artifact до deploy.
- Artifacts и reports — передача build output между jobs.
- Release automation — tags, GitHub Releases, changelog и release assets.
- Packages и container images — публикация immutable artifacts и container images.
- Cloud deploy через OIDC — cloud credentials без долгоживущих secrets.
- Monitoring и troubleshooting — где смотреть run logs, deployment history и причины зависших deploy jobs.
Внешние справки: GitHub Docs по control deployments, workflow syntax, events that trigger workflows, deploying to an environment и contexts.
