За 10+ лет работы я настраивал рабочее окружение сотни раз — от простых WordPress-проектов до сложных корпоративных систем на Laravel. И честно говоря, правильная настройка окружения экономит месяцы времени в будущем.
Почему Docker изменил мою жизнь разработчика
Я помню времена, когда настройка нового проекта занимала целый день. XAMPP, локальные базы данных, конфликты версий PHP — это был настоящий ад. У меня был клиент с проектом на PHP 7.4, а другой требовал PHP 8.1. Постоянные переключения версий через Homebrew на macOS превращались в рутину.
Docker решил все эти проблемы одним махом. Сейчас я могу запустить любой проект с нужным окружением за 2 минуты. На моей практике Docker показал себя как абсолютно необходимый инструмент для современной разработки.
Основные преимущества Docker в веб-разработке:
- Изоляция проектов — каждый в своём контейнере
- Воспроизводимость окружения на всех машинах команды
- Лёгкое переключение между версиями PHP, MySQL, nginx
- Одинаковое окружение на локалке, тестовом и продакшене
Грубо говоря, если вы до сих пор не используете Docker — вы теряете кучу времени впустую. Я настраиваю Docker Compose для каждого проекта, даже для простых лендингов на WordPress.
Настройка Docker Compose для веб-проектов
Я использую стандартную связку: nginx, PHP-FPM, MySQL, Redis. Вот мой базовый docker-compose.yml для Laravel-проектов, который работает безотказно:
version: '3.8'
services:
nginx:
image: nginx:1.25-alpine
container_name: ${APP_NAME}-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./:/var/www/html
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./docker/nginx/sites/:/etc/nginx/sites-available/
- ./docker/nginx/ssl/:/etc/ssl/
depends_on:
- php
networks:
- laravel
php:
build:
context: ./docker/php
dockerfile: Dockerfile
container_name: ${APP_NAME}-php
volumes:
- ./:/var/www/html
- ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- laravel
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
container_name: ${APP_NAME}-mysql
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: ${DB_DATABASE}
MYSQL_USER: ${DB_USERNAME}
MYSQL_PASSWORD: ${DB_PASSWORD}
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./docker/mysql/data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/my.cnf
networks:
- laravel
redis:
image: redis:7-alpine
container_name: ${APP_NAME}-redis
restart: unless-stopped
ports:
- "6379:6379"
networks:
- laravel
networks:
laravel:
driver: bridge
volumes:
mysql_data:
driver: local
Для PHP-FPM я собираю свой образ с нужными расширениями. Вот Dockerfile, который использую для большинства проектов:
FROM php:8.2-fpm
# Устанавливаем системные зависимости
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
libzip-dev \
zip \
unzip \
nodejs \
npm
# Устанавливаем расширения PHP
RUN docker-php-ext-install \
pdo_mysql \
mbstring \
exif \
pcntl \
bcmath \
gd \
zip
# Устанавливаем Redis расширение
RUN pecl install redis && docker-php-ext-enable redis
# Устанавливаем Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Создаём пользователя
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www
# Копируем существующие права на приложение
COPY --chown=www:www . /var/www/html
# Меняем текущего пользователя на www
USER www
# Экспонируем порт 9000 и запускаем php-fpm сервер
EXPOSE 9000
CMD ["php-fpm"]
На практике эта связка позволяет разворачивать проект на любой машине командой docker-compose up -d. У меня был случай, когда новый разработчик в команде запустил проект за 5 минут, хотя раньше на настройку локалки уходило пол дня.
Git workflow для командной разработки
Честно говоря, я видел проекты, где Git использовался как простое хранилище файлов. Это плохая идея. Правильный Git workflow — это основа продуктивной работы команды.
Я использую модифицированный Git Flow для большинства проектов. Основные ветки:
- main — продакшен, только стабильный код
- develop — основная ветка разработки
- feature/* — ветки для новых фич
- hotfix/* — срочные исправления для продакшена
- release/* — подготовка релизов
Вот мой стандартный .gitignore для Laravel-проектов, который экономит время на исключение ненужных файлов:
# Laravel
/vendor
/node_modules
/public/hot
/public/storage
/storage/*.key
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
# Docker
docker/mysql/data/*
!docker/mysql/data/.gitkeep
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
storage/logs/*
!storage/logs/.gitkeep
Для автоматизации я настраиваю Git hooks. Вот pre-commit hook, который запускает PHP CS Fixer перед каждым коммитом:
#!/bin/sh
# Pre-commit hook для проверки кода
PROJECT=$(php artisan --version | head -n1 | awk '{print $1}' | tr '[:upper:]' '[:lower:]')
STAGED_FILES_CMD=$(git diff --cached --name-only --diff-filter=ACMR HEAD | grep \\.php)
# Проверяем, есть ли файлы для коммита
if [ "$STAGED_FILES_CMD" = "" ]; then
exit 0
fi
STAGED_FILES=$(echo "$STAGED_FILES_CMD")
echo "Checking PHP Lint..."
for FILE in $STAGED_FILES
do
php -l -d display_errors=0 $PROJECT/$FILE
if [ $? != 0 ]
then
echo "Fix the error before commit."
exit 1
fi
FILES="$FILES $PROJECT/$FILE"
done
# Запускаем PHP CS Fixer
if [ "$FILES" != "" ]
then
echo "Running Code Sniffer..."
./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff $FILES
if [ $? != 0 ]
then
echo "Fix the error before commit!"
echo "Run php-cs-fixer fix to fix automatically or fix it manually."
exit 1
fi
fi
exit $?
Настройка CI/CD pipeline
Автоматизация деплоя — это не роскошь, а необходимость. У меня был клиент, который деплоил сайт через FTP каждый раз вручную. Один раз он случайно удалил продакшен базу. После этого случая мы настроили полноценный CI/CD.
Я предпочитаю GitLab CI для большинства проектов. Вот базовый .gitlab-ci.yml, который использую для Laravel:
stages:
- test
- build
- deploy
variables:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: laravel_test
MYSQL_USER: laravel
MYSQL_PASSWORD: secret
DB_HOST: mysql
cache:
paths:
- vendor/
- node_modules/
# Тестирование
test:php:
stage: test
image: php:8.2
services:
- mysql:8.0
before_script:
- apt-get update -qq && apt-get install -y -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev libzip-dev
- docker-php-ext-install zip pdo_mysql
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --no-dev --no-scripts
- cp .env.testing .env
- php artisan key:generate
- php artisan migrate
script:
- php vendor/bin/phpunit --configuration phpunit.xml --coverage-text --colors=never
# Проверка кода
test:code-style:
stage: test
image: php:8.2
before_script:
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --no-scripts
script:
- php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --dry-run --diff
# Сборка для продакшена
build:production:
stage: build
image: node:18
before_script:
- npm install
script:
- npm run production
artifacts:
paths:
- public/css/
- public/js/
- public/mix-manifest.json
only:
- main
# Деплой на staging
deploy:staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache rsync openssh
- eval $(ssh-agent -s)
- echo "$STAGING_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$STAGING_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- rsync -rav --delete --exclude='.git/' --exclude='storage/app/' --exclude='storage/logs/' . $STAGING_USER@$STAGING_HOST:$STAGING_PATH
- ssh $STAGING_USER@$STAGING_HOST "cd $STAGING_PATH && php artisan migrate --force && php artisan config:cache && php artisan route:cache"
only:
- develop
# Деплой на продакшен
deploy:production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache rsync openssh
- eval $(ssh-agent -s)
- echo "$PRODUCTION_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$PRODUCTION_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- rsync -rav --delete --exclude='.git/' --exclude='storage/app/' --exclude='storage/logs/' . $PRODUCTION_USER@$PRODUCTION_HOST:$PRODUCTION_PATH
- ssh $PRODUCTION_USER@$PRODUCTION_HOST "cd $PRODUCTION_PATH && php artisan migrate --force && php artisan config:cache && php artisan route:cache && php artisan queue:restart"
when: manual
only:
- main
Для GitHub Actions использую похожую логику, но с другим синтаксисом. GitHub Actions удобнее для open-source проектов, но GitLab CI мне нравится больше для коммерческой разработки.
Обязательно настраиваю уведомления в Slack или Telegram при успешном/неуспешном деплое. Вот простой скрипт для уведомлений в Telegram:
#!/bin/bash
TELEGRAM_BOT_TOKEN="your_bot_token"
TELEGRAM_CHAT_ID="your_chat_id"
PROJECT_NAME="$1"
STATUS="$2"
BRANCH="$3"
if [ "$STATUS" = "success" ]; then
MESSAGE="✅ $PROJECT_NAME: деплой ветки $BRANCH успешно завершён"
else
MESSAGE="❌ $PROJECT_NAME: ошибка деплоя ветки $BRANCH"
fi
curl -s -X POST https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage \
-d chat_id=$TELEGRAM_CHAT_ID \
-d text="$MESSAGE"
Мониторинг и логирование в CI/CD
После настройки автоматического деплоя обязательно нужен мониторинг. Я интегрирую каждый CI/CD pipeline с системой мониторинга. Использую связку Prometheus + Grafana для метрик и ELK Stack для логов.
В GitLab CI добавляю этап проверки здоровья после деплоя:
health_check:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl
script:
- sleep 30 # Ждём запуска приложения
- curl -f http://your-domain.com/health || exit 1
- curl -f http://your-domain.com/api/status || exit 1
only:
- main
- develop
На практике эта проверка не раз спасала от деплоя сломанного кода. У меня был случай, когда после деплоя сайт возвращал 500 ошибку из-за неправильной конфигурации Redis. Health check поймал это сразу.
Для детального мониторинга добавляю метрики в код приложения. Вот пример middleware для Laravel, который отправляет метрики времени ответа:
sendMetrics([
'request_duration_ms' => $duration,
'request_method' => $request->method(),
'request_uri' => $request->path(),
'response_status' => $response->status(),
'timestamp' => time()
]);
return $response;
}
private function sendMetrics(array $metrics)
{
// Отправка в InfluxDB или Prometheus
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, config('monitoring.metrics_endpoint'));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($metrics));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . config('monitoring.api_token')
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_exec($ch);
curl_close($ch);
}
}
Безопасность в CI/CD pipeline
Безопасность CI/CD — это отдельная большая тема. За годы практики я видел множество проектов, где секреты хранились прямо в коде репозитория. Это однозначно плохая идея.
Основные принципы безопасности, которые я применяю:
- Все секреты только через переменные окружения CI/CD
- Ротация ключей доступа каждые 3-6 месяцев
- Отдельные учётные записи для CI/CD с минимальными правами
- Обязательная подпись коммитов для продакшена
- Сканирование зависимостей на уязвимости
В GitLab CI добавляю этап сканирования безопасности:
security:dependency-scan:
stage: test
image: php:8.2
before_script:
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --no-scripts
script:
- php composer.phar audit
- npm audit
allow_failure: false
security:secret-scan:
stage: test
image: alpine:latest
before_script:
- apk add --no-cache git
- git clone https://github.com/trufflesecurity/trufflehog.git
script:
- ./trufflehog/trufflehog --regex --entropy=False .
allow_failure: false
Для хранения секретов использую HashiCorp Vault или встроенные возможности GitLab/GitHub. Никогда не храню пароли и API ключи в .env файлах в репозитории.
Оптимизация скорости CI/CD
Медленный CI/CD убивает продуктивность команды. У меня был проект, где полный цикл тестирования и деплоя занимал 45 минут. Разработчики начали пропускать тесты, чтобы не ждать. Это плохая практика.
Основные способы ускорения CI/CD:
- Параллелизация — запускаем тесты параллельно
- Кеширование — кешируем зависимости между запусками
- Инкрементальная сборка — собираем только изменённые части
- Docker layer caching — переиспользуем слои Docker образов
Вот оптимизированная версия .gitlab-ci.yml с параллельным запуском тестов:
stages:
- test
- build
- deploy
# Глобальный кеш
cache: &global_cache
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
- node_modules/
- .composer-cache/
policy: pull-push
# Параллельные тесты
test:unit:
stage: test
image: php:8.2
cache:
<<: *global_cache
policy: pull
script:
- php vendor/bin/phpunit --testsuite=Unit
test:feature:
stage: test
image: php:8.2
services:
- mysql:8.0
cache:
<<: *global_cache
policy: pull
script:
- php vendor/bin/phpunit --testsuite=Feature
test:integration:
stage: test
image: php:8.2
services:
- mysql:8.0
- redis:7
cache:
<<: *global_cache
policy: pull
script:
- php vendor/bin/phpunit --testsuite=Integration
Также использую многоэтапную сборку Docker для ускорения. Вот пример оптимизированного Dockerfile:
# Многоэтапная сборка для оптимизации
FROM php:8.2-fpm as base
# Устанавливаем системные зависимости
RUN apt-get update && apt-get install -y \
git curl libpng-dev libzip-dev zip unzip \
&& docker-php-ext-install pdo_mysql zip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Этап для Composer
FROM base as composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.* ./
RUN composer install --no-dev --optimize-autoloader --no-scripts
# Этап для фронтенда
FROM node:18-alpine as frontend
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run production
# Финальный этап
FROM base as production
WORKDIR /var/www/html
COPY . .
COPY --from=composer /app/vendor ./vendor
COPY --from=frontend /app/public/css ./public/css
COPY --from=frontend /app/public/js ./public/js
COPY --from=frontend /app/mix-manifest.json ./mix-manifest.json
RUN chown -R www-data:www-data /var/www/html
На практике эти оптимизации сокращают время сборки с 15-20 минут до 3-5 минут. Это критично важно для продуктивности команды.
Интеграция с системами мониторинга
После настройки CI/CD обязательно интегрирую его с системами мониторинга. Использую Sentry для отслеживания ошибок, New Relic для APM и собственный стек на базе Prometheus + Grafana.
В Laravel добавляю автоматическую отправку уведомлений о деплое в Sentry:
env('SENTRY_LARAVEL_DSN'),
'release' => env('APP_VERSION', 'unknown'),
'environment' => env('APP_ENV', 'production'),
// Уведомления о релизах
'send_default_pii' => false,
'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE', 0.1),
'before_send' => function (\Sentry\Event $event): ?\Sentry\Event {
// Фильтруем чувствительные данные
return $event;
},
];
А в CI/CD добавляю команду для создания релиза в Sentry:
# В .gitlab-ci.yml после успешного деплоя
- curl -sL https://sentry.io/get-cli/ | bash
- sentry-cli releases new $CI_COMMIT_SHA
- sentry-cli releases set-commits $CI_COMMIT_SHA --auto
- sentry-cli releases finalize $CI_COMMIT_SHA
- sentry-cli releases deploys $CI_COMMIT_SHA new -e production
Это позволяет связывать ошибки в продакшене с конкретными релизами и быстро откатываться при необходимости.
Типовые ошибки и их решение
За годы работы я собрал список самых частых проблем с CI/CD и их решений. Это сэкономит вам кучу времени на отладке.
Проблема 1: Docker контейнеры падают с ошибкой памяти
Решение: Увеличиваем лимиты памяти в docker-compose.yml и оптимизируем PHP конфигурацию:
services:
php:
image: php:8.2-fpm
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
environment:
- PHP_MEMORY_LIMIT=256M
- PHP_MAX_EXECUTION_TIME=300
Проблема 2: Медленная установка зависимостей в CI
Решение: Используем кеширование и зеркала репозиториев:
# .gitlab-ci.yml
variables:
COMPOSER_CACHE_DIR: ".composer-cache"
NPM_CONFIG_CACHE: ".npm-cache"
cache:
paths:
- .composer-cache/
- .npm-cache/
- vendor/
- node_modules/
before_script:
- composer config cache-dir .composer-cache
- composer config repos.packagist composer https://packagist.org
Проблема 3: Падение тестов из-за состояния базы данных
Решение: Используем транзакции в тестах и отдельную тестовую БД:
У меня был проект, где тесты падали из-за того, что использовали продакшн Redis. После настройки отдельного окружения для тестов проблема исчезла.
Настройка правильного окружения разработчика — это инвестиция в будущее проекта. Грубо говоря, час потраченный на настройку Docker и CI/CD экономит недели времени в процессе разработки. И это не преувеличение — я это проверил на десятках проектов.
Если нужна помощь с настройкой окружения для вашего проекта, обращайтесь за поддержкой Битрикс или поддержкой WordPress. А для более сложных задач можем обсудить доработку сайта под ваши потребности.
Правильно настроенное окружение — это основа успешного проекта. И чем раньше вы это сделаете, тем больше времени и нервов сэкономите в будущем.
Нужна помощь с настройкой DevOps-процессов?
Настроим современное окружение разработки и автоматизируем ваши процессы развертывания.