Community
0
HostiServer
2026-09-30 11:19

Docker + CI/CD: автодеплой з GitHub Actions

⏱️ Час читання: ~12 хвилин | 📅 Оновлено: вересень 2026

Docker + CI/CD: автоматичний деплой

Стек із попередньої статті вже production-ready: Nginx, SSL, автозапуск після reboot. Але деплой нової версії досі виглядає так само, як і в самому першому пункті серії — змінили код, зробили git push, зайшли по SSH на сервер, вручну виконали docker compose build && docker compose up -d. Це монотонно, схильно до помилок і зовсім не масштабується, щойно деплоїти починає не одна людина.

CI/CD-pipeline знімає саме цей ручний крок. Кожен push у гілку main автоматично запускає послідовність дій: збірку образу, публікацію його в registry і оновлення сервісу на сервері — без входу по SSH вручну.

Ця стаття свідомо не прив'язана до конкретної CI-платформи. Концепцію CI/CD, різницю між Continuous Integration, Delivery і Deployment, а також структуру pipeline (тригер, stage, job, artifact) ми вже розбирали в окремій серії — почати варто з «Що таке CI/CD: від ручного deploy до автоматизованих pipelines». docker compose зі статті 2 і Nginx з SSL зі статті 3 цієї серії теж вважаються відомими. Тут — тільки та частина, яка специфічна саме для Docker: як образ потрапляє з CI на сервер і що саме змінюється в docker-compose.yml.

Результат наприкінці статті: git push у main — і за дві-три хвилини нова версія вже працює на сервері.

2. Docker image registry

2.1 Навіщо потрібен registry

CI збирає образ у своєму власному, ефемерному оточенні — цей образ там же і зникає після завершення job. Серверу потрібно звідкись його забрати. Registry — це проміжна ланка: CI публікує образ туди одразу після збірки, сервер пізніше тягне його звідти командою pull.

2.2 Варіанти registry

Registry Коли підходить
Docker Hub Безкоштовно, є rate limit на анонімні pull-запити; достатньо для особистих і невеликих проєктів
GitHub Container Registry (ghcr.io) Безкоштовно для публічних репозиторіїв, зручно, якщо CI вже налаштований на GitHub Actions
GitLab Registry / Gitea Registry Вбудовані прямо в платформу — зручно для self-hosted сценаріїв без додаткового сервісу
Self-hosted (registry:2) Повний контроль, без зовнішніх лімітів; потребує окремого сервера чи ресурсів того самого VPS

2.3 Іменування образів і теги

Типовий формат — registry/namespace/app:tag. Як тег використовують або короткий git SHA (git rev-parse --short HEAD), або latest. SHA дає відтворюваність — завжди відомо, який саме коміт стоїть на сервері; latest зручний для швидкого доступу до найновішої версії. На практиці CI пушить обидва теги одночасно на один і той самий образ.

2.4 Приватний registry і авторизація

Якщо registry приватний, і CI, і сервер повинні авторизуватись перед роботою з ним — docker login з обліковими даними. Ці облікові дані передаються тільки через секрети CI-платформи, ніколи не прописуються прямо в pipeline-файлі чи в коді.

3. Зміни в docker-compose.yml

3.1 Як було раніше

До появи автодеплою сервіс app мав build: . — Compose сам збирав образ прямо на сервері з локального Dockerfile при кожному запуску.

3.2 Як стає з CI/CD

build: . замінюється на image: registry/namespace/app:latest. Compose на сервері більше нічого не збирає — тільки тягне вже готовий образ, який CI зібрав і опублікував.

Перехід docker-compose.yml від build: до image: — CI збирає і публікує образ, сервер лише тягне готовий результат

3.3 Два compose-файли

Практичне рішення — тримати два файли: docker-compose.yml для production з image:, і docker-compose.override.yml (або docker-compose.dev.yml) для локальної розробки з build:. Обидва зберігаються в git — в .gitignore нічого додатково виключати не потрібно, а pipeline на сервері явно вказує використовувати саме production-файл.

3.4 Тег через змінну

Замість жорстко прописаного latest зручніше задати тег змінною: image: registry/namespace/app:${APP_VERSION:-latest}. Це дозволяє деплоїти конкретну версію, просто передавши значення APP_VERSION через .env або напряму з pipeline, не редагуючи сам compose-файл.

4. Pipeline: кроки і логіка

4.1 Тригер

Деплой запускається на push у гілку main (чи окрему production). Pull request-и і feature-гілки збираються і тестуються, як описано в CI/CD-серії, але не деплояться — це відповідає різниці між Continuous Integration і Continuous Deployment.

4.2 Загальна послідовність

  1. Checkout коду
  2. Авторизація в registry (docker login)
  3. Збірка образу: docker build -t registry/namespace/app:sha .
  4. Публікація образу в registry (тег sha і тег latest)
  5. SSH-з'єднання із сервером
  6. docker compose pull app — тягне новий образ
  7. docker compose up -d --no-deps app — перезапускає тільки app

Повний CI/CD pipeline для Docker: від checkout коду до SSH-деплою на сервер

4.3 Чому --no-deps критично важливий

Без прапора --no-deps команда up -d перезапустить не тільки app, а й усі сервіси, від яких він залежить через depends_on — тобто і db, і cache. Це коротка, але зовсім не обов'язкова недоступність бази даних під час звичайного оновлення застосунку. При rolling-деплої нової версії потрібен перезапуск лише самого app.

4.4 SSH-доступ з CI

Для деплою заводиться окрема пара SSH-ключів, а не особистий ключ будь-кого з команди. Публічний ключ додається в ~/.ssh/authorized_keys на сервері, приватний зберігається виключно в секретах CI-платформи.

4.5 Альтернатива SSH

Замість прямого SSH-з'єднання можна підняти невеликий HTTP-сервіс на самому сервері — webhook, який приймає POST-запит від CI і всередині виконує pull та up -d. Такий підхід дає менше прав CI-платформі над сервером (немає прямого shell-доступу), але вимагає окремого налаштування і підтримки самого webhook-сервісу.

5. Секрети і безпека

5.1 Що зберігати в секретах CI

Мінімальний набір: REGISTRY_USER, REGISTRY_PASSWORD, SSH_PRIVATE_KEY, SERVER_HOST, SERVER_USER.

5.2 Чого не повинно бути в коді

Жодного з цих значень не повинно бути ні в самому коді, ні в Dockerfile, ні в docker-compose.yml, ні в git-історії — навіть у .env-файлах усередині репозиторію. У репозиторії має лежати лише .env.example з порожніми чи заглушковими значеннями, як орієнтир для того, які змінні взагалі потрібні.

5.3 Обмеження SSH-ключа деплою

Ключ для деплою варто робити окремим від усіх інших і, за можливості, обмежувати команду через директиву command= у authorized_keys на сервері — тоді навіть якщо ключ скомпрометований, ним не можна виконати довільну команду. Якщо компрометація все ж сталась, такий окремий ключ можна відкликати, не зачіпаючи доступ інших людей чи сервісів.

5.4 Registry credentials на сервері

docker login на сервері виконується один раз вручну, і облікові дані зберігаються локально в ~/.docker/config.json. Автоматичний повторний login у складі деплою потрібен лише тоді, коли облікові дані регулярно ротуються.

6. Версіонування і rollback

6.1 Чому одного latest недостатньо

Якщо деплой нової версії щось зламав, тег latest уже перезаписаний — на нього більше не можна спертись, щоб відкотитись назад. Потрібен інший, незмінний ідентифікатор конкретної версії.

6.2 SHA-тег як рятівний засіб

Кожен push зберігає в registry образ з тегом, який більше ніколи не перевикористовується — коротким git SHA цього коміту. Rollback у такому разі — це передати попередній SHA назад у pipeline або виконати вручну:

docker compose pull
APP_VERSION=abc1234 docker compose up -d

6.3 Зберігання старих образів

Registry не звільняє місце сам — варто налаштувати політику зберігання останніх N образів (через вбудовані retention policies реєстру або окремий cron-скрипт очищення). Це особливо актуально там, де storage registry тарифікується — необмежена історія тегів поступово стає платним баластом.

6.4 Rollback у самому pipeline

Найзручніший варіант — окремий job у CI, який запускається вручну з параметром SHA чи версії і виконує ті самі кроки деплою, тільки зі старим тегом. Без такого job'а rollback перетворюється назад на ручний SSH-вхід — саме те, від чого автодеплой мав позбавити.

7. Zero-downtime деплой

7.1 Реалістичний рівень для більшості проєктів

docker compose up -d --no-deps app має короткий, зазвичай 1-2 секунди, проміжок між зупинкою старого контейнера і стартом нового. Для переважної більшості сайтів і внутрішніх сервісів це прийнятний рівень — користувач у гіршому випадку побачить одну повільну відповідь замість помітного простою.

7.2 Справжній zero-downtime

Повністю прибрати цей проміжок можна схемою blue/green: два паралельні контейнери (app_blue і app_green), і Nginx перемикає upstream на новий тільки після того, як він підтвердив готовність. Це помітно складніша інфраструктура, і виправдана вона там, де SLA вимагає доступності понад 99.9% — для типового self-hosted проєкту на одному VPS це, як правило, надлишково.

7.3 Страховка на випадок невдалого старту

restart: unless-stopped разом із healthcheck на сервісі app — мінімальна, але дієва страховка: якщо новий контейнер не піднявся (краш при старті, OOM через недостатній ліміт пам'яті), Docker перезапустить його сам, а Nginx поверне 502 лише на короткий час рестарту, а не назавжди.

8. Практичний приклад pipeline (псевдокод)

8.1 Абстрактна схема

Незалежно від конкретної платформи логіка pipeline виглядає однаково:

on: push to main
steps:
  - checkout
  - login to registry
  - build image
  - push image
  - ssh deploy

ℹ️ Реалізація для конкретної платформи: покроковий приклад саме такого pipeline на GitHub Actions, GitLab CI та Gitea Actions розібраний у серії «CI/CD з нуля» — там же пояснено, чим синтаксис платформ відрізняється в деталях.

8.2 Змінні середовища pipeline

Назва образу і тег зазвичай виносяться в змінні: IMAGE_NAME, IMAGE_TAG. Сам SHA-коміту кожна платформа передає під своєю назвою — $GITHUB_SHA у GitHub Actions, $CI_COMMIT_SHORT_SHA у GitLab CI, свій аналог у Gitea Actions. Назви змінних різняться, концепція та сама.

8.3 Сама команда деплою

Останній крок — SSH на сервер і виконання команди оновлення. Це можна оформити або як одну складену команду прямо в pipeline-файлі, або винести в окремий скрипт deploy.sh у репозиторії. Другий варіант зручніший: скрипт можна протестувати локально до того, як він потрапить у production-pipeline, і його зміни видно в історії git так само, як і зміни коду.

9. Висновок

Тепер кожен git push у main означає оновлену версію на сервері за дві-три хвилини, без ручного входу по SSH і без ризику забути один із кроків.

Повна схема виглядає так: код у git → CI збирає образ і публікує його в registry → сервер тягне готовий образ і перезапускає лише сервіс застосунку → Nginx, як і раніше, роздає трафік, навіть не помічаючи, що позаду відбулось оновлення.

Деталі налаштування самого pipeline під конкретну платформу — GitHub Actions, GitLab CI чи Gitea Actions — виходять за межі цієї статті: вони докладно розібрані в окремій серії «CI/CD з нуля». Тут ми пройшли тільки Docker-специфічну частину пазла: registry, зміни в compose-файлі, SSH-деплой, версіонування і rollback.

Повний цикл — від коду до контейнера на сервері — тепер зібраний. Останній крок серії — привести на той самий сервер і сам Git: наступна стаття замикає серію Gitea чи Forgejo, піднятими в Docker поруч із CI-runner'ом, у повністю self-hosted стек.

📚 Навігація серії:
Ви читаєте частину 4 з 5 «Docker + CI/CD: автоматичний деплой».
Попередня: Частина 3. Docker у production ←
Далі: Частина 5. Gitea / Forgejo у Docker: повний self-hosted стек →

🚀 Сервер, готовий приймати деплой з CI щодня

Автодеплой означає регулярні pull і перезапуски контейнерів — і сервер має витримувати це без просідань для тих, хто в цей момент користується застосунком. Hostiserver дає для цього передбачувані ресурси.

🖥️ Виділені Сервери

  • Від $90/міс, повний контроль над залізом для частих production-деплоїв
  • Без сусідів по ресурсах: короткий момент перезапуску контейнера не залежить від чужого навантаження
  • 24/7 підтримка: допоможемо з SSH-доступом для CI та налаштуванням self-hosted registry

💻 Cloud (VPS) Хостинг

  • Від $19.95/міс, KVM-ізоляція, виділені vCPU і RAM
  • NVMe-диски: швидкий pull нових образів без затримок на диску
  • Стабільна мережа: SSH-деплой з CI не залежить від нестабільного каналу
  • Легко масштабувати: більше ресурсів, коли деплоїв стає більше і вони частіші

💬 Не впевнені, який варіант потрібен?
💬 Напишіть нам — допоможемо з вибором!

Часті запитання

Docker Hub чи self-hosted registry — що обрати для невеликого проєкту?

Для одного-двох приватних проєктів Docker Hub чи вбудований registry GitHub/GitLab/Gitea (залежно від того, де вже живе код) — найпростіший старт без додаткової інфраструктури. Self-hosted registry:2 має сенс, коли лімітів безкоштовних варіантів вже не вистачає або є вимога тримати все у власному периметрі.

Чому не можна деплоїти тільки за тегом latest?

Тег latest перезаписується кожним новим пушем, тому він не годиться як точка для відкату — попередній стан просто зникає. Паралельний тег з git SHA лишається незмінним назавжди, і саме він дає можливість повернутись до конкретної версії, а не тільки рухатись вперед.

Навіщо обов'язково --no-deps при оновленні app?

Без цього прапора docker compose up -d перезапустить усі сервіси, пов'язані через depends_on, включно з базою даних і кешем — навіть якщо змінився тільки код застосунку. --no-deps обмежує перезапуск лише тим сервісом, який дійсно оновився.

Як безпечно зберігати SSH-ключ для CI?

Окремий ключ, який використовується тільки для деплою і ніде більше, з приватною частиною виключно в секретах CI-платформи (ніколи в репозиторії). На сервері варто обмежити, що саме цей ключ може виконати, директивою command= у authorized_keys — тоді компрометація ключа не дає зловмиснику довільний shell на сервері.

SSH-деплой чи webhook — з чого простіше почати?

SSH простіше налаштувати одразу: пара ключів і кілька команд у pipeline. Webhook вимагає підняти і підтримувати окремий невеликий сервіс на сервері, зате дає CI менше прав — без прямого shell-доступу. Для одного проєкту на одному VPS SSH зазвичай достатньо.

Новий контейнер після деплою одразу впав — pipeline вважається успішним?

Формально так — docker compose up -d сам по собі лише запускає контейнер і не чекає, поки застосунок усередині дійсно стане готовим. Тому в pipeline варто додавати окрему перевірку після деплою: короткий curl на health-ендпоінт застосунку з невеликою затримкою, а не покладатись тільки на код завершення команди.

Чи потрібен zero-downtime (blue/green) для невеликого проєкту?

Здебільшого ні. Стандартний --no-deps перезапуск дає перерву в одну-дві секунди, що прийнятно для більшості сайтів і внутрішніх сервісів. Blue/green виправданий, коли є формальна вимога SLA понад 99.9% або коли навіть короткий 502 неприпустимий — наприклад, для платіжного шлюзу з високим трафіком.

Contents

Поділіться цією статтею

VPS з підтримкою від

$19 95 / міс

Виділені сервери від

$80 / міс

CDN починаючи від

$0 / міс

 

Користуючись цим сайтом, ви погоджуєтеся на використання файлів cookies відповідно до нашої Політики Конфіденційності.