Docker-compose в GitHub Actions: настройка CI/CD для контейнеров
Для кого эта статья:
- Для разработчиков, работающих с Docker и GitHub Actions
- Для специалистов в области DevOps и CI/CD
Для студентов и новичков в веб-разработке, интересующихся автоматизацией процессов разработки
Docker-compose в GitHub Actions — это не просто набор инструментов, а мощная комбинация для автоматизации сборки и деплоя многоконтейнерных приложений. Когда я впервые столкнулся с необходимостью запускать docker-compose в CI/CD пайплайнах, это казалось почти невозможной задачей из-за различий в окружениях и непредсказуемых проблем совместимости. Но правильная интеграция этих технологий способна радикально повысить эффективность разработки, обеспечивая полностью автоматизированное тестирование и деплой сложных приложений. Давайте разберемся, как настроить эту интеграцию правильно с первого раза. 🚀
Если вы хотите углубить свои знания в веб-разработке и DevOps, обратите внимание на обучение веб-разработке от Skypro. Курс включает не только базовые навыки программирования, но и практические модули по CI/CD, Docker и GitHub Actions — именно те инструменты, которые сделают вас востребованным специалистом, способным автоматизировать процессы разработки от начала до конца.
Зачем объединять docker-compose и GitHub Actions
Интеграция docker-compose в GitHub Actions решает одну из ключевых проблем современной разработки — обеспечение идентичности сред разработки, тестирования и продакшена. Когда разработчик говорит "у меня на локальной машине всё работает", а в продакшене система падает — это классический пример разрыва между средами выполнения.
Docker-compose в GitHub Actions позволяет:
- Запускать многоконтейнерные приложения в CI/CD процессах точно так же, как на локальной машине
- Тестировать взаимодействие между микросервисами в изолированной среде
- Автоматизировать интеграционное тестирование с реальной базой данных и другими зависимостями
- Создавать единообразные процессы сборки, которые одинаково работают у всех членов команды
- Валидировать docker-compose конфигурации перед деплоем в продакшен
Алексей, DevOps-инженер
Я занимался внедрением CI/CD для стартапа, где приложение состояло из 5 взаимосвязанных микросервисов. Каждый разработчик использовал docker-compose на локальной машине, но тестирование при пулл-реквестах происходило в "чистой" среде, что приводило к постоянным проблемам.
Решением стала интеграция docker-compose в GitHub Actions. Мы настроили workflow, который поднимал все контейнеры в том же порядке и с теми же параметрами, что и локально. Это немедленно выявило несколько скрытых зависимостей, которые работали только на машинах разработчиков.
После внедрения количество "неожиданных" багов в деплойментах уменьшилось на 78%, а время, затрачиваемое на отладку проблем окружения, сократилось более чем втрое. Теперь, если тесты проходят в GitHub Actions, мы уверены, что они пройдут и в продакшене.
Давайте сравним подходы к тестированию в CI/CD пайплайнах:
| Подход | Преимущества | Недостатки | Применимость |
|---|---|---|---|
| Тестирование без контейнеризации | Простота настройки, быстрый старт | Проблемы совместимости, "it works on my machine" | Малые проекты, монолитные приложения |
| Использование одиночных Docker контейнеров | Изоляция, повторяемость | Сложность настройки взаимодействия сервисов | Средние проекты с одним сервисом |
| Docker-compose в GitHub Actions | Полная изоляция, тестирование взаимодействия, идентичность сред | Сложнее настройка, потребление ресурсов | Микросервисная архитектура, сложные приложения |
Объединение docker-compose и GitHub Actions — не просто дань моде на контейнеризацию. Это практический инструмент, который делает процесс CI/CD более надежным, особенно для сложных систем с множеством взаимодействующих компонентов. 🔄

Подготовка проекта для интеграции docker-compose
Прежде чем интегрировать docker-compose в GitHub Actions, необходимо правильно подготовить структуру проекта. Без этого этапа вы рискуете столкнуться с проблемами совместимости и неожиданным поведением контейнеров в CI/CD среде.
Основные шаги подготовки проекта:
- Оптимизация docker-compose.yml для CI/CD окружения
- Разделение конфигураций для разработки и тестирования
- Настройка параметров времени ожидания и проверок готовности сервисов
- Обеспечение идемпотентности процессов
- Минимизация размеров образов для ускорения CI/CD пайплайнов
Начнем с анализа существующего docker-compose.yml файла. В нем могут быть параметры, которые хорошо работают локально, но создадут проблемы в GitHub Actions. Например, монтирование локальных томов, использование специфичных для разработчика переменных окружения, или hardcoded пути к файлам.
Ключевые элементы, которые следует оптимизировать в docker-compose.yml:
version: '3.8'
services:
app:
build:
context: ./app
# Используем ARG для переключения между development и CI
args:
- NODE_ENV=${NODE_ENV:-production}
environment:
# Используем переменные окружения из GitHub Actions
- DATABASE_URL=${DATABASE_URL}
# Задаем значения по умолчанию для локальной разработки
- PORT=${PORT:-3000}
# Настраиваем проверки готовности для CI/CD
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
db:
image: postgres:14
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD:-postgres}
# Вместо volumes используем инициализационные скрипты
volumes:
- ./init-db:/docker-entrypoint-initdb.d
# Для CI/CD избегаем постоянных томов
# – postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 10s
timeout: 5s
retries: 5
Часто бывает удобно создать отдельный docker-compose.ci.yml файл, который будет специально оптимизирован для CI/CD и переопределит некоторые параметры основного файла:
version: '3.8'
services:
app:
build:
args:
- NODE_ENV=test
environment:
- CI=true
- TEST_DATABASE_URL=postgres://postgres:postgres@db:5432/test
db:
environment:
- POSTGRES_DB=test
# Используем фиксированные учетные данные для тестов
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
# В CI нам не нужны персистентные данные
volumes:
- ./init-db:/docker-entrypoint-initdb.d
Михаил, Lead DevOps-инженер
Мой опыт показал, что правильная подготовка docker-compose для GitHub Actions может сэкономить недели отладки в будущем. В одном из проектов мы мигрировали CI/CD из Jenkins в GitHub Actions и столкнулись с загадочными сбоями тестов, которые проходили локально, но стабильно падали в CI.
Проблема оказалась в том, что мы использовали volume для базы данных в docker-compose.yml без явного указания каталога. Локально это работало отлично — данные сохранялись между запусками. Но в GitHub Actions каждый запуск происходил в чистом окружении, и база данных каждый раз инициализировалась с нуля, что приводило к конфликтам в миграциях.
Мы решили проблему, создав два файла: docker-compose.yml для разработки и docker-compose.ci.yml для CI/CD, который переопределял настройки томов. Дополнительно мы добавили wait-for-it скрипт, гарантирующий, что тесты запускаются только когда все сервисы полностью готовы.
Этот подход сократил время выполнения CI/CD пайплайнов на 40% и полностью устранил "мерцающие" тесты, которые случайно проходили или не проходили из-за проблем с окружением.
Важный аспект подготовки — оптимизация размеров Docker-образов для CI/CD. Чем меньше образы, тем быстрее будут выполняться ваши workflow в GitHub Actions:
| Техника оптимизации | Описание | Примерное уменьшение размера |
|---|---|---|
| Multi-stage builds | Разделение этапа сборки и запуска в Dockerfile | 50-80% |
| Минимальные базовые образы | Использование alpine или slim вариантов образов | 60-90% |
| Оптимизация слоев | Объединение RUN команд, удаление временных файлов | 10-30% |
| .dockerignore | Исключение ненужных файлов из контекста сборки | 5-50% (зависит от проекта) |
| Кэширование зависимостей | Правильный порядок операций для максимального использования кэша | Сокращение времени сборки на 30-70% |
Подготовка проекта для интеграции docker-compose в GitHub Actions — не единоразовая задача, а непрерывный процесс. С каждой новой итерацией ваш CI/CD пайплайн должен становиться быстрее и надежнее. 🛠️
Создание YAML-файла workflow для запуска контейнеров
Ключевой элемент интеграции docker-compose в GitHub Actions — корректно настроенный workflow файл. Этот YAML документ определяет, когда и как будут запускаться ваши контейнеры, а также последовательность выполнения команд. Давайте разберем создание такого файла пошагово.
Для начала создайте в корне проекта директорию .github/workflows/ и файл внутри неё, например docker-compose-ci.yml. В этом файле мы определим workflow для запуска docker-compose в GitHub Actions.
Базовая структура workflow файла выглядит так:
name: Docker Compose CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Docker Compose
uses: docker/setup-buildx-action@v1
# Здесь будут шаги для работы с docker-compose
Теперь добавим шаги для запуска и взаимодействия с docker-compose. Обратите внимание на оптимальную последовательность действий:
name: Docker Compose CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build containers
run: docker-compose build
- name: Start containers
run: docker-compose up -d
- name: Wait for services to be ready
run: |
# Ждем 10 секунд для инициализации сервисов
sleep 10
# Проверяем, что все сервисы запущены и здоровы
docker-compose ps
- name: Run tests
run: docker-compose exec -T app npm test
- name: Show logs on failure
if: failure()
run: docker-compose logs
- name: Stop containers
if: always()
run: docker-compose down
Для более сложных сценариев может потребоваться использование нескольких docker-compose файлов или дополнительных опций:
name: Docker Compose Advanced CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and start containers
run: |
# Используем несколько конфигурационных файлов
docker-compose -f docker-compose.yml -f docker-compose.ci.yml build
docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d
- name: Wait for services to be healthy
run: |
# Более продвинутое ожидание готовности сервисов
MAX_RETRIES=30
RETRY_INTERVAL=5
for i in $(seq 1 $MAX_RETRIES); do
if docker-compose ps | grep -q "healthy"; then
echo "Services are healthy!"
break
fi
echo "Waiting for services to be healthy... ($i/$MAX_RETRIES)"
sleep $RETRY_INTERVAL
if [ $i -eq $MAX_RETRIES ]; then
echo "Services did not become healthy in time."
docker-compose ps
docker-compose logs
exit 1
fi
done
- name: Run integration tests
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml exec -T app npm run test:integration
- name: Run e2e tests
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml exec -T app npm run test:e2e
- name: Push images if tests passed
if: success() && github.ref == 'refs/heads/main'
run: |
docker-compose push app
- name: Cleanup
if: always()
run: |
docker-compose -f docker-compose.yml -f docker-compose.ci.yml down -v
docker system prune -f
Важные рекомендации при создании workflow файла для docker-compose в GitHub Actions:
- Всегда используйте флаг
-Tпри запуске команд сdocker-compose exec, так как GitHub Actions работает в неинтерактивном режиме - Добавляйте шаг очистки ресурсов с условием
if: always(), чтобы контейнеры были остановлены даже при ошибке - Выводите логи контейнеров при неудаче тестов для упрощения отладки
- Используйте несколько docker-compose файлов для разделения конфигураций разработки и CI/CD
- Добавляйте достаточное время ожидания и проверки готовности сервисов перед запуском тестов
Для сложных приложений может потребоваться более тонкая настройка параллельного выполнения задач в GitHub Actions. Рассмотрим пример с использованием матрицы для выполнения разных типов тестов:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-type: [unit, integration, e2e]
# Продолжать выполнение других тестов даже если один тип упал
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Build and start containers
run: |
docker-compose -f docker-compose.yml -f docker-compose.ci.yml build
docker-compose -f docker-compose.yml -f docker-compose.ci.yml up -d
- name: Run ${{ matrix.test-type }} tests
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml exec -T app npm run test:${{ matrix.test-type }}
- name: Cleanup
if: always()
run: docker-compose -f docker-compose.yml -f docker-compose.ci.yml down -v
Кэширование Docker образов и слоев существенно ускоряет выполнение workflow. Для этого можно использовать GitHub Actions cache:
steps:
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and load
uses: docker/build-push-action@v2
with:
context: ./app
load: true
tags: myapp:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
# Костыль для обхода ограничения GitHub Actions cache
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
Создание эффективного YAML-файла workflow требует понимания не только docker-compose, но и особенностей работы GitHub Actions. Постепенно улучшайте ваш workflow, добавляя кэширование, параллельное выполнение тестов и другие оптимизации. 🔧
Настройка секретов и переменных окружения для docker-compose
Безопасная работа с секретами и переменными окружения — критически важный аспект интеграции docker-compose в GitHub Actions. Неправильная настройка может привести как к утечке конфиденциальных данных, так и к неработоспособности ваших контейнеров в CI/CD окружении.
GitHub Actions предоставляет два основных механизма для работы с конфиденциальными данными:
- Secrets — зашифрованные переменные, безопасные для хранения чувствительной информации
- Environment Variables — обычные переменные для параметризации workflow
Рассмотрим процесс настройки секретов для использования в docker-compose:
- Перейдите в настройки вашего репозитория на GitHub: Repository → Settings → Secrets → New repository secret
- Создайте необходимые секреты, например: DATABASEPASSWORD, APIKEY, JWT_SECRET
- Обновите ваш workflow файл, чтобы передать секреты в docker-compose
name: Docker Compose CI with Secrets
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Создаем файл .env с секретами для docker-compose
- name: Create .env file
run: |
echo "DATABASE_URL=postgresql://user:${{ secrets.DB_PASSWORD }}@postgres:5432/mydb" > .env
echo "REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}" >> .env
echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .env
# Запускаем docker-compose с переменными окружения из .env
- name: Start services
run: docker-compose up -d
# Для команд, требующих переменные окружения напрямую
- name: Run migrations
env:
DATABASE_URL: postgresql://user:${{ secrets.DB_PASSWORD }}@postgres:5432/mydb
run: docker-compose exec -T app npm run migrate
Для передачи переменных окружения напрямую в docker-compose, можно использовать один из следующих подходов:
# Передача через env в workflow
- name: Start services with env variables
env:
DATABASE_PASSWORD: ${{ secrets.DB_PASSWORD }}
API_KEY: ${{ secrets.API_KEY }}
run: docker-compose up -d
# Или с использованием аргументов command line
- name: Start services with command line args
run: |
DATABASE_PASSWORD=${{ secrets.DB_PASSWORD }} \
API_KEY=${{ secrets.API_KEY }} \
docker-compose up -d
Хорошей практикой является создание отдельной GitHub Actions среды (environment) для разных стадий разработки:
name: Docker Compose Multi-Environment CI
on:
push:
branches:
- main
- develop
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
steps:
- uses: actions/checkout@v2
- name: Create environment-specific .env file
run: |
echo "DATABASE_URL=${{ secrets.DATABASE_URL }}" > .env
echo "ENVIRONMENT=${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}" >> .env
- name: Start services
run: docker-compose -f docker-compose.yml -f docker-compose.${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }}.yml up -d
При работе с секретами и переменными окружения в docker-compose важно помнить о следующих практиках безопасности:
- Никогда не выводите секреты в логи — GitHub автоматически скрывает секреты, но не если вы намеренно их выводите
- Не сохраняйте секреты в файлы, которые могут быть зафиксированы в репозитории
- Удаляйте файлы .env после использования
- Используйте минимально необходимые привилегии для контейнеров
- Регулярно обновляйте и ротируйте секреты
Для сложных проектов с множеством переменных окружения удобно использовать матрицу переменных:
| Среда | Переменные окружения | Файл docker-compose |
|---|---|---|
| Development | NODE_ENV=development, DEBUG=true | docker-compose.yml + docker-compose.dev.yml |
| Testing (CI) | NODE_ENV=test, CI=true | docker-compose.yml + docker-compose.ci.yml |
| Staging | NODEENV=staging, APIURL=staging-api | docker-compose.yml + docker-compose.staging.yml |
| Production | NODEENV=production, HIGHAVAILABILITY=true | docker-compose.yml + docker-compose.prod.yml |
При работе с переменными окружения для docker-compose в GitHub Actions обращайте внимание на область видимости переменных. Если они нужны только внутри контейнера, указывайте их в docker-compose файле. Если они нужны для принятия решений в workflow, используйте env в шагах GitHub Actions.
Настройка секретов и переменных окружения для docker-compose — это баланс между удобством разработки и безопасностью. Тщательно продуманная стратегия управления конфигурациями сделает ваш CI/CD процесс более надежным и безопасным. 🔐
Решение проблем совместимости docker-compose в GitHub Actions
Интеграция docker-compose в GitHub Actions часто сопровождается непредвиденными проблемами совместимости, которые могут существенно затруднить настройку CI/CD пайплайна. Рассмотрим наиболее распространенные проблемы и их решения.
Основные категории проблем совместимости:
- Различия в версиях docker и docker-compose
- Проблемы с сетевыми настройками и DNS-разрешением
- Ограничения ресурсов и тайм-ауты в GitHub Actions
- Особенности файловой системы и прав доступа
- Различия в поведении контейнеров в разных окружениях
Проблема 1: Несовместимость версий
GitHub Actions может использовать версию docker-compose, отличающуюся от вашей локальной среды, что приводит к неожиданному поведению.
Решение:
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install specific docker-compose version
run: |
sudo rm /usr/local/bin/docker-compose
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o docker-compose
chmod +x docker-compose
sudo mv docker-compose /usr/local/bin
docker-compose --version
Проблема 2: Тайм-ауты при инициализации сервисов
В GitHub Actions сервисы могут запускаться дольше, чем на локальной машине, что приводит к преждевременному запуску тестов до полной готовности всех компонентов.
Решение — использовать скрипт ожидания:
- name: Wait for services
run: |
# Сохраняем скрипт wait-for-it.sh
curl -L https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -o wait-for-it.sh
chmod +x wait-for-it.sh
# Ожидаем готовности необходимых сервисов
./wait-for-it.sh db:5432 -t 60
./wait-for-it.sh redis:6379 -t 60
./wait-for-it.sh app:3000 -t 120
Проблема 3: Проблемы с Docker сетью
В GitHub Actions могут возникать проблемы с DNS-разрешением или сетевыми подключениями между контейнерами.
Решение:
- name: Debug network
run: |
# Проверяем сети Docker
docker network ls
# Получаем информацию о сети нашего приложения
docker network inspect docker_default
# Проверяем DNS-разрешение из контейнера
docker-compose exec -T app ping -c 3 db
docker-compose exec -T app cat /etc/hosts
Проблема 4: Ограничения ресурсов
GitHub Actions имеет ограничения по CPU и памяти, что может приводить к неожиданным сбоям при высокой нагрузке.
Решение — оптимизация ресурсов в docker-compose.ci.yml:
services:
app:
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
db:
deploy:
resources:
limits:
memory: 256M
Проблема 5: Зависание или "зомби" контейнеры
Иногда контейнеры могут "застревать" и не завершаться корректно, блокируя последующие запуски.
Решение — добавьте шаг очистки с принудительным удалением:
- name: Cleanup containers
if: always()
run: |
docker-compose down -v --remove-orphans
# Удаляем все контейнеры и образы для полной очистки
docker ps -aq | xargs -r docker rm -f
docker system prune -af --volumes
Сравнительная таблица проблем совместимости и их решений:
| Проблема | Симптомы | Решение | Превентивные меры |
|---|---|---|---|
| Несовместимость версий | Ошибки синтаксиса, неподдерживаемые опции | Явно установить требуемую версию | Указать версию в docker-compose.yml |
| Тайм-ауты сервисов | Тесты падают из-за недоступности сервисов | Использовать скрипты ожидания, healthcheck | Добавить healthcheck во все сервисы |
| Сетевые проблемы | Сервисы не видят друг друга, DNS ошибки | Отладка сети, явное указание hostname | Использовать фиксированные имена сетей |
| Ограничения ресурсов | OOM ошибки, неожиданное завершение | Ограничить ресурсы в docker-compose | Мониторить использование ресурсов |
| "Зомби" контейнеры | Новые запуски не могут стартовать | Принудительная очистка контейнеров | Всегда использовать if: always() для cleanup |
| Проблемы с томами | Ошибки прав доступа, отсутствие данных | Использовать временные тома или bind mounts | Тестировать в CI с теми же настройками томов |
Для систематического выявления и решения проблем совместимости полезно добавить отладочный step в workflow:
- name: Debug information
if: always()
run: |
echo "=== Docker version ==="
docker --version
docker-compose --version
echo "=== Running containers ==="
docker ps -a
echo "=== Docker networks ==="
docker network ls
echo "=== Docker volumes ==="
docker volume ls
echo "=== Disk space ==="
df -h
echo "=== Memory usage ==="
free -h
echo "=== Container logs ==="
docker-compose logs
Проблемы совместимости docker-compose в GitHub Actions — это не разовые препятствия, а системные вызовы, требующие стратегического подхода. Разработайте практики, которые позволят своевременно выявлять и устранять эти проблемы, что существенно повысит надежность ваших CI/CD пайплайнов. 🛡️
Docker-compose в GitHub Actions — это действенный инструмент для создания надежных и воспроизводимых CI/CD пайплайнов. Правильная интеграция этих технологий значительно снижает разрыв между средами разработки и продакшена, минимизируя печально известное "на моей машине работает". Преимущества этого подхода становятся особенно очевидны при работе с микросервисной архитектурой, когда тестирование взаимодействия между компонентами становится критически важным. Применяйте описанные техники последовательно, уделяя внимание безопасности и оптимизации производительности, и вы получите CI/CD процесс, который вызовет восхищение у ваших коллег и уважение у руководства.
Читайте также
- SDR-системы и хранение данных: вызовы радиосвязи будущего
- 7 инструментов для эффективного администрирования баз данных
- Создание почтового сервера: настройка, защита и обслуживание
- Визуализация кода в блок-схемах: превращаем алгоритмы в понятные диаграммы
- Chrome DevTools: мощный инструмент отладки веб-разработки
- Стратегии ветвления в Git: основы эффективной разработки
- Xenia и Zennoposter: настройка для максимальной производительности
- 8 отечественных аналогов Notion и Trello: импортозамещение в IT
- Framer Motion в React: плавные анимации без головной боли
- Allure Framework: создание информативных отчетов о тестировании


