Современные web‑проекты требуют быстрых и надёжных процессов доставки кода. В статье рассматривается построение продвинутого CI/CD для двух популярных стеков: Laravel (PHP) и Django (Python). Мы используем Docker для изоляции окружений, GitHub Actions как оркестратор и динамические среды (preview‑environments) для тестирования изменений перед продакшеном.
Общая архитектура пайплайна
Схема выглядит так:
- Исходный код хранится в репозитории GitHub.
- Для каждого коммита запускается GitHub Actions workflow.
- Workflow собирает Docker‑образ, прогоняет тесты и, при успешном результате, деплоит в staging‑окружение.
- Для pull‑request создаётся preview‑environment – временный контейнер, доступный по уникальному URL.
- После мержа в
mainпроисходит деплой в продакшен.
Docker‑конфигурация
Ниже приведён базовый docker-compose.yml, который подходит для обоих стеков, используя отдельные сервисы для веб‑приложения и базы данных.
version: "3.8"
services:
# Laravel service
laravel:
build:
context: ./laravel
dockerfile: Dockerfile
container_name: laravel_app
env_file: ./laravel/.env
ports:
- "8000:80"
depends_on:
- db
# Django service
django:
build:
context: ./django
dockerfile: Dockerfile
container_name: django_app
env_file: ./django/.env
ports:
- "8001:8000"
depends_on:
- db
# Общая база данных
db:
image: postgres:15-alpine
container_name: postgres_db
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: app_db
volumes:
- db_data:/var/lib/postgresql/data
volumes:
db_data:
Dockerfile для Laravel
FROM php:8.1-fpm
# Установка зависимостей
RUN apt-get update && apt-get install -y \
git \
unzip \
libpq-dev \
&& docker-php-ext-install pdo pdo_pgsql
# Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
COPY . .
RUN composer install --no-interaction --prefer-dist --optimize-autoloader
EXPOSE 80
CMD ["php-fpm"]
Dockerfile для Django
FROM python:3.11-slim
# Установка зависимостей
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
GitHub Actions: Laravel pipeline
name: Laravel CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-test-deploy:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: app_db
ports: [5432:5432]
options: >-
--health-cmd "pg_isready -U user"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to DockerHub (optional)
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build Laravel image
run: |
docker build -t myorg/laravel:${{ github.sha }} ./laravel
- name: Run tests
run: |
docker run --rm \
-e DB_CONNECTION=pgsql \
-e DB_HOST=host.docker.internal \
-e DB_PORT=5432 \
-e DB_DATABASE=app_db \
-e DB_USERNAME=user \
-e DB_PASSWORD=password \
myorg/laravel:${{ github.sha }} php artisan test
- name: Push image to registry
if: github.ref == 'refs/heads/main'
run: |
docker push myorg/laravel:${{ github.sha }}
- name: Deploy to Staging (Docker Swarm / Kubernetes)
if: github.ref == 'refs/heads/main'
env:
IMAGE_TAG: ${{ github.sha }}
run: |
# Пример для Kubernetes
kubectl set image deployment/laravel-deploy laravel=myorg/laravel:${IMAGE_TAG} --record
- name: Create Preview Environment (for PR)
if: github.event_name == 'pull_request'
env:
PREVIEW_TAG: preview-${{ github.event.pull_request.number }}
run: |
docker tag myorg/laravel:${{ github.sha }} myorg/laravel:${PREVIEW_TAG}
docker push myorg/laravel:${PREVIEW_TAG}
# Деплой в отдельный namespace
kubectl create namespace preview-${{ github.event.pull_request.number }} || true
kubectl set image deployment/laravel-deploy laravel=myorg/laravel:${PREVIEW_TAG} -n preview-${{ github.event.pull_request.number }} --record
GitHub Actions: Django pipeline
name: Django CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-test-deploy:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: app_db
ports: [5432:5432]
options: >-
--health-cmd "pg_isready -U user"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r ./django/requirements.txt
- name: Build Docker image
run: |
docker build -t myorg/django:${{ github.sha }} ./django
- name: Run tests
run: |
docker run --rm \
-e DATABASE_URL=postgres://user:password@host.docker.internal:5432/app_db \
myorg/django:${{ github.sha }} python manage.py test
- name: Push image to registry
if: github.ref == 'refs/heads/main'
run: |
docker push myorg/django:${{ github.sha }}
- name: Deploy to Staging
if: github.ref == 'refs/heads/main'
env:
IMAGE_TAG: ${{ github.sha }}
run: |
kubectl set image deployment/django-deploy django=myorg/django:${IMAGE_TAG} --record
- name: Create Preview Environment (for PR)
if: github.event_name == 'pull_request'
env:
PREVIEW_TAG: preview-${{ github.event.pull_request.number }}
run: |
docker tag myorg/django:${{ github.sha }} myorg/django:${PREVIEW_TAG}
docker push myorg/django:${PREVIEW_TAG}
kubectl create namespace preview-${{ github.event.pull_request.number }} || true
kubectl set image deployment/django-deploy django=myorg/django:${PREVIEW_TAG} -n preview-${{ github.event.pull_request.number }} --record
Динамические preview‑окружения
Для каждого pull‑request создаётся отдельный namespace (Kubernetes) или отдельный стек в Docker‑Swarm. Это позволяет QA‑инженерам и дизайнерам проверять изменения в изолированном окружении без влияния на основную ветку.
- URL формируется автоматически, например
https://pr-12.myproject.dev. - Окружения автоматически уничтожаются через 24 ч или после закрытия PR.
- Для управления сроком жизни можно использовать
kubectl delete namespaceв отдельном workflow, триггерящем по событиюpull_request.closed.
Лучшие практики
- Секреты в GitHub Secrets – храните токены Docker Hub, креденшалы БД и SSH‑ключи.
- Кеширование зависимостей – используйте Docker layer caching и actions/cache для pip/composer.
- Тесты на уровне контейнеров – запускать их в том же образе, что и продакшен, чтобы избежать «works on my machine».
- Версионирование образов – тегировать как
commit‑sha,branch‑nameиlatestпри необходимости. - Мониторинг и алерты – интегрировать с Sentry, Prometheus или GitHub Checks для мгновенного фидбэка.
Заключение
Сочетание Docker, GitHub Actions и динамических preview‑окружений позволяет построить полностью автоматизированный, масштабируемый и безопасный процесс доставки кода как для Laravel, так и для Django. Такой пайплайн ускоряет выпуск новых функций, повышает качество продукта и снижает риск ошибок в продакшене.
Компания RybinskLab предлагает профессиональные услуги по разработке, настройке CI/CD, контейнеризации и поддержке инфраструктуры под ключ. Свяжитесь с нами, чтобы вывести ваш проект на новый уровень автоматизации.