Автоматичний деплой Rails на AWS EC2 через GitHub Actions + rsync + Docker Compose

💡 Усі статті, обговорення, новини про DevOps — в одному місці. Приєднуйтесь до DevOps спільноти!

Розповім, як налаштував повністю автоматичний деплой для Rails-додатку на AWS EC2 без жодного платного сервісу — тільки GitHub Actions + rsync + Docker Compose.

Проблема

Раніше деплой виглядав так: зробив зміни → зайшов по SSH → git pull → перезапустив контейнери. Руками, кожного разу. Набридло.

Що вийшло в результаті

Push у main → за 2-3 хвилини сайт оновлений. Якщо щось зламалось — білд падає, на сервер нічого не їде.

Стек

  • Rails 7.2 + Docker Compose
  • AWS EC2 (t3.micro, Ubuntu 22.04)
  • Nginx + Let’s Encrypt
  • GitHub Actions із self-hosted runner
  • rsync для синхронізації файлів

Конфігурація GitHub Actions

name: Deploy

on:
  push:
    branches: [master]

jobs:
  deploy:
    runs-on: self-hosted

    steps:
      - uses: actions/checkout@v4

      - name: Build CSS
        run: yarn build:css

      - name: Rsync to server
        run: |
          rsync -avz --delete \
            --exclude='.git' \
            --exclude='node_modules' \
            --exclude='tmp/cache' \
            --exclude='config/database.yml' \
            ./ ubuntu@${{ secrets.SERVER_HOST }}:/home/ubuntu/shop/

      - name: Restart containers
        run: |
          ssh ubuntu@${{ secrets.SERVER_HOST }} \
            "cd /home/ubuntu/shop && docker compose restart web nginx"

      - name: Health check
        run: |
          sleep 5
          curl -f https://${{ secrets.SERVER_HOST }}/health || exit 1

Чому rsync, а не git pull

git pull на сервері — погана практика: треба тримати git на продакшені, вирішувати конфлікти, думати про merge commits. Rsync просто синхронізує файли — як це робить Capistrano або Kamal під капотом.

Важливий момент: --exclude='config/database.yml' — цей файл живе тільки на сервері і не повинен перезаписуватись при деплої.

Self-hosted runner

GitHub дає 500 хв/місяць безкоштовно для приватних репо. Але у мене є Mac mini вдома — запустив runner локально, і деплой займає ~40 секунд замість 3-4 хвилин на хмарному runner-і.

Встановлення — 5 хвилин через Settings → Actions → Runners у репозиторії.

Health check endpoint

Додав мінімальний endpoint у Rails:

# config/routes.rb
get '/health', to: proc { [200, {}, ['OK']] }

Якщо після деплою health check не пройшов — workflow падає з помилкою. Одразу видно що щось не так, не треба лізти на сервер вручну.

Результат

  • 0 ручних деплоїв вже кілька місяців
  • Час деплою: ~40 сек (self-hosted runner)
  • Помилка в CSS або падіння health check — деплою не буде
  • Повний лог кожного деплою в GitHub Actions

Якщо є питання про конфігурацію Nginx, Docker Compose або Let’s Encrypt auto-renewal — пишіть у коментарях.

👍ПодобаєтьсяСподобалось3
До обраногоВ обраному2
LinkedIn
Ctrl + Enter
Ctrl + Enter

Дякую всім за коментарі — корисна дискусія!

Після обговорення вніс кілька змін у пайплайн:

Перейшов з self-hosted runner на ubuntu-latest — головна проблема self-hosted була в тому, що білд падав якщо Docker Desktop не запущений на моєму Mac. GitHub-hosted runner
вирішує це повністю, Docker там завжди є.

Прибрав linux/arm64 з білду — EC2 t3.micro це amd64, arm64 там просто не потрібен. Час білду скоротився з ~20 хв до ~5-8 хв.

Додав concurrency — при новому пуші попередній білд автоматично скасовується, хвилини не витрачаються даремно. Актуально бо GitHub Free дає 2000 хв/місяць на приватні репо.

Щодо Docker remote context (Володимир) — цікавий підхід, але поки влаштовує поточна схема з GHCR: образ будується в Actions, пушиться в реєстр, сервер тільки робить docker pull. Деплой займає ~1 хв після білду.

Ви можете з локального компʼютера зробити docker build та docker compose up за допомогою remote context docs.docker.com/...​anage-resources/contexts та скоротити деплой мабуть до 5-10 секунд.

Якщо вже там docker compose то краще у actions билдить образ та пхати його у acr.

Раніше деплой виглядав так: зробив зміни → зайшов по SSH → git pull → перезапустив контейнери. Руками, кожного разу. Набридло.

У чому проблема зробити це bash-скриптом, навіщо створювати велосипед?

Раніше деплой виглядав так: зробив зміни → зайшов по SSH → git pull → перезапустив контейнери. Руками, кожного разу. Набридло.

Досі так роблю для власних проєктів — значно швидше, ніж GitHub Actions. Для комерційних, звісно, використовую GitHub Actions.

Підписатись на коментарі