Автоматичний деплой Rails на AWS EC2 через GitHub Actions + rsync + Docker Compose
Розповім, як налаштував повністю автоматичний деплой для Rails-додатку на AWS EC2 без жодного платного сервісу — тільки GitHub Actions + rsync + Docker Compose.
Проблема
Раніше деплой виглядав так: зробив зміни → зайшов по SSH → git pull → перезапустив контейнери. Руками, кожного разу. Набридло.
Що вийшло в результаті
Push у main → за
Стек
- 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 секунд замість
Встановлення — 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 — пишіть у коментарях.
5 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівДякую всім за коментарі — корисна дискусія!
Після обговорення вніс кілька змін у пайплайн:
Перейшов з 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.
У чому проблема зробити це bash-скриптом, навіщо створювати велосипед?
Досі так роблю для власних проєктів — значно швидше, ніж GitHub Actions. Для комерційних, звісно, використовую GitHub Actions.