CI/CD з GitHub Actions: тести, збірка образу й деплой
Повний пайплайн для вебзастосунку: перевірки на пул-реквест, збірка Docker-образу з кешем, публікація в реєстр і деплой з ручним підтвердженням для продакшену.
CI/CD — це не «щоб було». Це спосіб доставляти зміни часто, маленькими частинами і без страху. GitHub Actions зручний тим, що живе поруч з кодом: пайплайн — це YAML-файл у репозиторії, який проходить code review разом з усім іншим.
Структура пайплайна
pull request ──► lint + тести ──► (зелено) ──► можна мерджити
push у main ──► тести ──► збірка образу ──► push у реєстр ──► деплой на staging
тег v* ──► деплой у production (з підтвердженням)Перевірки на пул-реквест
# .github/workflows/ci.yml
name: ci
on:
pull_request:
push:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true # новий push скасовує застарілий прогін
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_PASSWORD: test
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s --health-retries 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: pip
- run: pip install -r requirements.txt -r requirements-dev.txt
- run: ruff check .
- run: pytest -q --maxfail=1
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/postgresПорада
concurrencyзcancel-in-progressекономить хвилини: якщо ви запушили тричі поспіль, перевірятиметься лише останній коміт.
Збірка і публікація образу
image:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # публікація в GitHub Container Registry
outputs:
tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: type=sha,format=short
- uses: docker/build-push-action@v6
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=maxТег образу — короткий SHA коміту. Так завжди відомо, який саме код працює на сервері, і відкат — це просто деплой попереднього тегу.
ВажливоНе використовуйте
latestдля деплою. Тегlatestзмінюється, а значить, два сервери з «однаковим» образом можуть запускати різний код.
Деплой з environments
Environments у GitHub дають окремі секрети для кожного середовища і правила захисту: обов'язкові рев'юери, дозволені гілки, таймер очікування.
deploy-staging:
needs: image
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy
run: |
curl -fsS -X POST "$DEPLOY_HOOK" \
-H "Authorization: Bearer $DEPLOY_TOKEN" \
-d "image=ghcr.io/${{ github.repository }}:${{ needs.image.outputs.tag }}"
env:
DEPLOY_HOOK: ${{ vars.DEPLOY_HOOK }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
deploy-production:
needs: [image, deploy-staging]
runs-on: ubuntu-latest
environment: production # тут налаштовано обов'язкове підтвердження
steps:
- run: echo "Деплой ${{ needs.image.outputs.tag }} у production"Безпека пайплайна
- Мінімальні
permissionsна рівні workflow і розширення лише там, де треба (packages: writeтільки для збірки). - OIDC замість довгоживучих ключів для хмар: GitHub видає короткоживучий токен, а AWS/GCP/Azure довіряють йому за умовами (репозиторій, гілка, environment).
- Фіксуйте сторонні actions за SHA коміту, а не за тегом: тег можна перевісити на шкідливий код.
- uses: some-org/some-action@3f1c9e2b7a5d... # v2.1.0 - Обережно з
pull_request_target: він має доступ до секретів, і якщо в ньому запустити код з форку, секрети можуть витекти.
ОбережноНіколи не підставляйте недовірені дані (назву гілки, заголовок PR, текст коміту) прямо в
run:через${{ }}. Це пряма ін'єкція команд. Передавайте їх черезenv:і використовуйте як змінні оболонки.
Швидкість
- кешуйте залежності (
cache:у setup-actions) і шари Docker (type=gha); - розпаралелюйте незалежні джоби — лінтер, юніт і інтеграційні тести можуть іти одночасно;
- запускайте важкі перевірки лише при зміні відповідних файлів (
on.pull_request.paths).
Чому для деплою краще тег з SHA коміту, а не latest?
SHA однозначно вказує на версію коду: видно, що саме задеплоєно, а відкат — це деплой попереднього SHA. latest перезаписується при кожній збірці, тож неможливо гарантувати, що на різних серверах і в різний час працює однаковий код.
Збережіть статтю собі
Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.
Увійти через Google Інші способи входу