Мої продакшн-деплої займають 15 хвилин. Я їх не дивлюсь. Не затримую дихання. Заварюю чай — і поки він настоюється, нова версія вже працює.
Так було не завжди.
Два роки тому деплой був ритуалом. Заблокуй годину. Попередь команду. Запусти скрипт вручну. Дивись логи. Молись. Полагодь те, що зламалося. Запусти знову. Молись сильніше. Весь процес займав 2-3 години і вичавлював усіх. Ми деплоїли в п'ятницю, бо ненавиділи себе.
Якщо це знайоме — ти запускаєш деплої на адреналіні замість систем. Станом на березень 2026-го для цього нуль причин. Ось як я перетворив 3-годинний фестиваль тривоги на 15 нудних хвилин. ⚙️
Проблема: одинадцять кроків у чиїйсь голові
Деплой — процес відправки нового коду з твого комп'ютера на сервер, де його бачать користувачі — означав 11 різних дій одночасно. Витягти код. Встановити залежності (зовнішні бібліотеки, потрібні додатку). Запустити міграції (оновити структуру бази даних під новий код). Зібрати ассети. Перезапустити сервіс. Очистити кеш. Оновити реверс-проксі. Перевірити health endpoint.
Усе це жило на wiki-сторінці, яка на 40% застаріла. Новачки боялися. Досвідчені — просто втомилися.
Рішення було не у модному інструменті. А в дисципліні.
Крок 1: Один скрипт, одна команда
Я написав один bash-скрипт — невелику програму, що виконує команди послідовно, як рецепт, який комп'ютер читає рядок за рядком. Назвав його deploy.sh. Він робить усі 11 речей по порядку. Якщо будь-який крок падає — все зупиняється й каже, який саме крок зламався і чому.
#!/bin/bash
set -euo pipefail
APP=$1
DEPLOY_LOG="/var/log/deploys/${APP}-$(date +%Y%m%d-%H%M%S).log"
echo "Deploying ${APP}..." | tee "$DEPLOY_LOG"
# Each step: name, command, rollback
deploy_step "Pull code" "git -C /srv/${APP} pull origin main"
deploy_step "Install deps" "cd /srv/${APP} && pip install -r requirements.txt"
deploy_step "Run migrations" "cd /srv/${APP} && python manage.py migrate"
deploy_step "Build assets" "cd /srv/${APP} && npm run build"
deploy_step "Restart service" "systemctl restart ${APP}"
deploy_step "Health check" "curl -sf http://localhost:8080/health"
echo "Deploy complete: ${APP}" | tee -a "$DEPLOY_LOG"
Функція deploy_step обгортає кожну команду логуванням, таймінгом і обробкою помилок. Якщо health check падає — автоматично перезапускається попередня версія, тобто відбувається rollback — 'відкотити і повернути те, що працювало'.
Загальний час налаштування: 45 хвилин. Ці 45 хвилин зекономили сотні годин відтоді. Ключова ідея — прямо з методології Twelve-Factor App: одна кодова база, одна збірка, одна команда деплою.
Крок 2: CI/CD — робот, який деплоїть за тебе
CI/CD розшифровується як Continuous Integration / Continuous Deployment. Простою мовою: система, яка автоматично тестує код, коли ти пушиш зміни (це CI), і якщо тести пройшли — автоматично деплоїть на сервер (це CD). Ти пушиш код, робот робить решту.
Для невеликої команди не потрібні Jenkins (важкий сервер автоматизації), ArgoCD (інструмент деплою для Kubernetes) чи кластер Kubernetes (система оркестрації контейнерів — якщо ці слова нічого не значать, чудово, вам це не треба). Потрібні GitHub Actions і сервер із SSH-доступом. GitHub Actions запустився у 2019-му і став стандартним вибором CI/CD для команд на GitHub — стабільний, добре задокументований і безкоштовний для невеликих навантажень.
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -r requirements.txt
- run: pytest --tb=short
deploy:
needs: test
runs-on: ubuntu-latest
if: success()
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_KEY }}
script: bash /srv/myapp/deploy.sh myapp
Це весь пайплайн — послідовність автоматизованих кроків, якими код подорожує з твого ноутбука на продакшн. Пуш у main (основна гілка коду). Тести запускаються на серверах GitHub. Якщо пройшли — GitHub підключається до сервера через SSH (протокол безпечного віддаленого з'єднання) і запускає deploy.sh.
Без контейнерів. Без оркестраторів. Без YAML-файлів на 200 рядків. Безкоштовний план GitHub Actions дає 2 000 хвилин на місяць — більш ніж достатньо для маленької команди, що деплоїть 2-3 рази на день.
Крок 3: Health check, який справді перевіряє здоров'я
Більшість health checks — це URL /health, що повертає {"status": "ok"}. Це каже, що веб-сервер запущений. Нічого про те, чи додаток реально працює. Це як запитати людину 'ти живий?', коли треба питати 'ти бачиш, чуєш і можеш ворушити пальцями?'
Мої health checks перевіряють три речі:
@app.get("/health")
async def health():
checks = {
"database": await check_db_connection(),
"cache": await check_redis_ping(),
"disk": check_disk_space_above(20), # percent
}
all_ok = all(checks.values())
return JSONResponse(
status_code=200 if all_ok else 503,
content={"status": "healthy" if all_ok else "degraded", "checks": checks}
)
База впала? Health check падає. Redis (in-memory кеш — швидке тимчасове сховище) недоступний? Падає. Диск заповнений на 95%? Падає. Скрипт деплою викликає цей endpoint після рестарту. Якщо повертає 503 (HTTP-код 'сервіс недоступний') — деплой автоматично відкочується.
Цей один endpoint зловив більше проблем після деплою, ніж усе наше ручне тестування разом узяте. ⚙️
Крок 4: Сповіщення, а не дашборди
Я не сиджу і не дивлюся моніторинг-дашборди. Скрипт деплою надсилає рівно два повідомлення:
- Деплой розпочато — 'Деплою myapp о 14:32. Тригер: коміт abc123.'
- Деплой завершено — 'myapp задеплоєно за 14хв 22с' або 'деплой myapp ВПАВ на кроці: health check. Відкочено.'
Це йде в Telegram-канал. Бачу повідомлення про помилку — розбираюсь. Бачу успіх — п'ю чай.
Без Grafana (популярний інструмент дашбордів). Без PagerDuty (сервіс алертів, що будить о 3 ночі). Для маленької команди з 2-3 сервісами повідомлення в Telegram — це правильний рівень складності. Складність має відповідати масштабу проблеми, а не масштабу амбіцій. 🫶
Крок 5: Деплой у понеділок вранці
Ми перестали деплоїти в п'ятницю. Тепер деплоїмо в понеділок і середу вранці.
Чому? Бо якщо щось зламається в понеділок — є п'ять днів, щоб полагодити свіжою головою. П'ятничний деплой — це дебаг на вихідних. Дебаг на вихідних — це вигорання. Вигорання — це помилки. Помилки — це ще більше дебагу на вихідних.
Це негативний зворотний зв'язок — цикл, де кожен поганий результат спричиняє наступний. Це не стратегія деплою.
Нудний графік деплоїв: понеділок і середа вранці. Нічого після 15:00. Нічого в п'ятницю. Нічого під час обіду. Якщо не готово до понеділкового вікна — чекає. Очікування дешеве. Вигорання — дороге.
Від чого ти відмовляєшся
Час для чесності. У цього підходу є компроміси.
Немає zero-downtime деплоїв. Коли виконується systemctl restart, є вікно 2-5 секунд, коли додаток недоступний. Для сервісу з мільйонами запитів це важливо. Для більшості маленьких команд ніхто не помічає. Якщо переростеш цей рівень — подивись на blue-green deployments (запуск двох копій додатка з перемиканням трафіку між ними) — але не починай із цього.
Один сервер — одна точка відмови. Якщо сервер помре — все помре. Резервування коштує грошей. Для більшості проєктів із доходом менше $10K на місяць один добре обслуговуваний сервер із щоденними бекапами — правильна відповідь. Проблеми масштабування — це хороші проблеми.
Немає контейнеризації. Docker (інструмент, що пакує додаток з усіма залежностями в ізольований блок) — чудовий для складних середовищ. Для Python-додатка з базою і Redis на одному сервері — це зайве навантаження. Завжди можна додати потім.
Дослідження DORA metrics від Google показує, що елітні команди деплоять на вимогу з часом виконання менше години. Але вони також показують, що до цього приходять поступово — а не впровадженням усіх інструментів одночасно.
Результат
До: 3-годинні деплої, 1-2 рази на тиждень, команда з 3 людей заблокована. Після: 15-хвилинні деплої, 2-3 рази на тиждень, нуль людей спостерігає.
Щомісячна економія часу: приблизно 24 години колективного часу команди. Річна вартість CI/CD-налаштування: $0 (безкоштовний план GitHub Actions плюс bash-скрипт). Час на налаштування: одні вихідні.
Зроби це нудним
Мета хорошого ops — не зробити деплої захопливими. А зробити їх настільки нудними, що забуваєш, що вони відбуваються.
Коли деплоїш без адреналіну — деплоїш частіше. Коли деплоїш частіше — кожен деплой менший. Коли кожен деплой менший — менше ламається. Коли менше ламається — краще спиш.
Уся філософія. Один скрипт. Один пайплайн. Один health check. Один канал сповіщень. Один спокійний ранок на тиждень.
Зроби нудно. Зроби маленько. Зроби автоматично. Йди заварювати чай. 🫶





