Это разбор полной конфигурации: nginx, PHP-FPM, MySQL, Redis, отдельный контейнер для агентов и push-сервер. В отличие от минимального набора «поднять и посмотреть», здесь учтены вещи, которые всплывают при попытке пользоваться таким окружением каждый день, — cron, сессии в Redis, разделение dev- и общих настроек.
Сразу оговорю границу: это окружение для разработки. Битрикс в продакшене живёт в конфигурации, которую поддерживает вендор, и различия между ней и вашим контейнером — источник классического «на локалке работает».
Что входит в окружение и зачем
Docker Compose — это описание нескольких связанных контейнеров одним файлом. Здесь их шесть: nginx как веб-сервер, PHP-FPM для приложения, MySQL для базы, Redis для кэша и сессий, отдельный контейнер под cron с агентами и push-сервер.
project/
├── docker-compose.yml
├── docker-compose.override.yml # dev-настройки
├── .env
├── docker/
│ ├── nginx/
│ │ ├── nginx.conf
│ │ └── sites/
│ │ └── bitrix.conf
│ ├── php/
│ │ ├── Dockerfile
│ │ ├── php.ini
│ │ └── www.conf
│ └── mysql/
│ └── my.cnf
└── www/ # Код Битрикс
└── bitrix/docker-compose.yml
# docker-compose.yml
version: '3.8'
services:
nginx:
image: nginx:1.25-alpine
container_name: bitrix-nginx
restart: unless-stopped
ports:
- "${NGINX_PORT:-80}:80"
- "${NGINX_SSL_PORT:-443}:443"
volumes:
- ./www:/var/www/html:ro
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./docker/nginx/sites:/etc/nginx/sites-enabled:ro
- ./logs/nginx:/var/log/nginx
depends_on:
php:
condition: service_healthy
networks:
- bitrix-network
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
php:
build:
context: ./docker/php
args:
PHP_VERSION: ${PHP_VERSION:-8.2}
container_name: bitrix-php
restart: unless-stopped
volumes:
- ./www:/var/www/html
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
- ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro
- ./logs/php:/var/log/php
environment:
- PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-512M}
- PHP_MAX_EXECUTION_TIME=${PHP_MAX_EXECUTION_TIME:-300}
- PHP_UPLOAD_MAX_FILESIZE=${PHP_UPLOAD_MAX_FILESIZE:-100M}
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- bitrix-network
healthcheck:
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
interval: 30s
timeout: 10s
retries: 3
mysql:
image: mysql:8.0
container_name: bitrix-mysql
restart: unless-stopped
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE:-bitrix}
MYSQL_USER: ${MYSQL_USER:-bitrix}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/custom.cnf:ro
- ./backups/mysql:/backups
networks:
- bitrix-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 30s
timeout: 10s
retries: 5
redis:
image: redis:7-alpine
container_name: bitrix-redis
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis-data:/data
networks:
- bitrix-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
cron:
build:
context: ./docker/php
args:
PHP_VERSION: ${PHP_VERSION:-8.2}
container_name: bitrix-cron
restart: unless-stopped
entrypoint: /usr/local/bin/cron-entrypoint.sh
volumes:
- ./www:/var/www/html
- ./docker/php/php.ini:/usr/local/etc/php/php.ini:ro
- ./logs/cron:/var/log/cron
depends_on:
- php
- mysql
networks:
- bitrix-network
push:
image: ikarpovich/bitrix-push-server:latest
container_name: bitrix-push
restart: unless-stopped
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
ports:
- "${PUSH_PORT:-8010}:80"
depends_on:
- redis
networks:
- bitrix-network
networks:
bitrix-network:
driver: bridge
volumes:
mysql-data:
redis-data:Dockerfile для PHP
# docker/php/Dockerfile
ARG PHP_VERSION=8.2
FROM php:${PHP_VERSION}-fpm-alpine
# Зависимости для сборки
RUN apk add --no-cache --virtual .build-deps \
$PHPIZE_DEPS \
freetype-dev \
libjpeg-turbo-dev \
libpng-dev \
libwebp-dev \
libzip-dev \
icu-dev \
libxml2-dev \
oniguruma-dev \
curl-dev
# Runtime зависимости
RUN apk add --no-cache \
freetype \
libjpeg-turbo \
libpng \
libwebp \
libzip \
icu-libs \
libxml2 \
oniguruma \
curl \
bash \
dcron \
fcgi
# PHP расширения
RUN docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp
RUN docker-php-ext-install -j$(nproc) \
gd \
mysqli \
pdo_mysql \
opcache \
zip \
intl \
exif \
mbstring \
xml \
curl \
bcmath \
sockets
# Redis
RUN pecl install redis && docker-php-ext-enable redis
# Xdebug (только для dev)
ARG INSTALL_XDEBUG=false
RUN if [ "$INSTALL_XDEBUG" = "true" ]; then \
pecl install xdebug && docker-php-ext-enable xdebug; \
fi
# Очистка
RUN apk del .build-deps && rm -rf /var/cache/apk/*
# Healthcheck скрипт
RUN set -xe && echo "#!/bin/sh" > /usr/local/bin/php-fpm-healthcheck && \
echo "SCRIPT_NAME=/health SCRIPT_FILENAME=/health REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 || exit 1" >> /usr/local/bin/php-fpm-healthcheck && \
chmod +x /usr/local/bin/php-fpm-healthcheck
# Cron entrypoint
COPY cron-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/cron-entrypoint.sh
WORKDIR /var/www/html
USER www-data
EXPOSE 9000
CMD ["php-fpm"]Конфиг nginx для Битрикс
# docker/nginx/sites/bitrix.conf
upstream php-fpm {
server php:9000;
}
server {
listen 80;
server_name _;
root /var/www/html;
index index.php index.html;
# Лимиты
client_max_body_size 100M;
client_body_buffer_size 10M;
# Gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
gzip_comp_level 6;
# Healthcheck
location = /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
# Статика
location ~* \.(jpg|jpeg|gif|png|svg|webp|ico|css|js|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
access_log off;
}
# Запрет доступа к служебным файлам
location ~ /(\.ht|\.git|\.env|bitrix/\.settings|bitrix/php_interface/dbconn) {
deny all;
return 404;
}
# Битрикс urlrewrite
location / {
try_files $uri $uri/ @bitrix;
}
location @bitrix {
fastcgi_pass php-fpm;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/bitrix/urlrewrite.php;
fastcgi_param QUERY_STRING $args;
}
# PHP
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass php-fpm;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
# Таймауты для длинных операций
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
fastcgi_connect_timeout 60;
# Буферы
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
}
# Push & Pull
location /bitrix/sub/ {
proxy_pass http://push:80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Конфиг PHP
; docker/php/php.ini
[PHP]
memory_limit = ${PHP_MEMORY_LIMIT:-512M}
max_execution_time = ${PHP_MAX_EXECUTION_TIME:-300}
max_input_time = 300
post_max_size = 100M
upload_max_filesize = 100M
max_file_uploads = 50
short_open_tag = On
display_errors = Off
log_errors = On
error_log = /var/log/php/error.log
date.timezone = Europe/Moscow
[Session]
session.save_handler = redis
session.save_path = "tcp://redis:6379"
session.gc_maxlifetime = 86400
[opcache]
opcache.enable = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0
opcache.revalidate_freq = 0
opcache.fast_shutdown = 1
opcache.enable_cli = 1
[mbstring]
mbstring.internal_encoding = UTF-8
mbstring.func_overload = 0Конфиг MySQL
# docker/mysql/my.cnf
[mysqld]
# Кодировка
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# InnoDB
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT
# Оптимизации
max_connections = 200
table_open_cache = 4000
thread_cache_size = 50
query_cache_type = 0
query_cache_size = 0
# Таймауты
wait_timeout = 28800
interactive_timeout = 28800
# Логи
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
[client]
default-character-set = utf8mb4Как запускать агенты Битрикса в контейнере
#!/bin/bash
# docker/php/cron-entrypoint.sh
# Создаём crontab
cat > /var/spool/cron/crontabs/www-data << 'EOF'
# Агенты Битрикс каждую минуту
* * * * * /usr/local/bin/php /var/www/html/bitrix/modules/main/tools/cron_events.php >> /var/log/cron/agents.log 2>&1
# Очистка кэша раз в день
0 4 * * * /usr/local/bin/php /var/www/html/bitrix/modules/main/tools/backup.php full >> /var/log/cron/backup.log 2>&1
EOF
chmod 0600 /var/spool/cron/crontabs/www-data
chown www-data:www-data /var/spool/cron/crontabs/www-data
# Запускаем cron в foreground
exec crond -f -d 8.env файл
# .env
# PHP
PHP_VERSION=8.2
PHP_MEMORY_LIMIT=512M
PHP_MAX_EXECUTION_TIME=300
PHP_UPLOAD_MAX_FILESIZE=100M
# MySQL
MYSQL_ROOT_PASSWORD=super_secret_root_password
MYSQL_DATABASE=bitrix
MYSQL_USER=bitrix
MYSQL_PASSWORD=super_secret_password
# Ports
NGINX_PORT=80
NGINX_SSL_PORT=443
PUSH_PORT=8010Настройка Битрикс
<?php
// /bitrix/.settings_extra.php
return [
// Redis кэш
'cache' => [
'value' => [
'type' => [
'class_name' => '\\Bitrix\\Main\\Data\\CacheEngineRedis',
'extension' => 'redis',
],
'redis' => [
'host' => 'redis',
'port' => 6379,
],
'sid' => $_SERVER['DOCUMENT_ROOT'] . '#01',
],
],
// Агенты через cron
'agents' => [
'value' => [
'crontab' => true,
],
],
];Production checklist:
- Смените все пароли в
.env - Настройте SSL сертификаты
- Включите
opcache.validate_timestamps = 0 - Настройте backup MySQL
- Добавьте мониторинг (Prometheus/Grafana)
Команды
# Запуск
docker-compose up -d
# Логи
docker-compose logs -f php
# Войти в контейнер
docker-compose exec php sh
# Перезагрузить nginx
docker-compose exec nginx nginx -s reload
# Бэкап MySQL
docker-compose exec mysql mysqldump -u bitrix -p bitrix > backup.sqlИтоги
| Контейнер | Образ | Назначение |
|---|---|---|
| nginx | nginx:1.25-alpine | Веб-сервер |
| php | php:8.2-fpm-alpine | PHP-FPM |
| mysql | mysql:8.0 | База данных |
| redis | redis:7-alpine | Кэш, сессии |
| cron | php:8.2-fpm-alpine | Агенты |
| push | bitrix-push-server | Push & Pull |
Несколько решений в этой конфигурации, которые стоит перенести в любое другое окружение для Битрикса.
Отдельный контейнер для cron. Разделение веб-процессов и фоновых задач — не эстетика: агент, съевший всю память, не должен ронять сайт, а тяжёлый обмен не должен конкурировать за процессы PHP-FPM с посетителями. Плюс на локальной машине это единственный способ убедиться, что агенты действительно работают через cron, а не на хитах, — то есть воспроизвести продакшен.
Сессии в Redis. При нескольких контейнерах PHP файловые сессии перестают работать: пользователь попадает то на один контейнер, то на другой, и его каждый раз разлогинивает. Проблема выглядит как «Битрикс глючит с авторизацией» и стоит нескольких часов до догадки.
Alpine-образы там, где можно. Разница в размере ощутима, но проверяйте набор расширений: часть из них требует сборки, а несовместимость с musl вылезает в самый неподходящий момент. Если возникают странные сбои — попробуйте обычный образ на Debian, прежде чем искать причину в коде.
Разделение docker-compose.yml и docker-compose.override.yml. Общая конфигурация одна на команду, локальные отличия (порты, xdebug, монтирование) — в override, который не коммитится. Иначе каждый разработчик правит общий файл под себя, и конфликт в нём становится ежедневным ритуалом.
Не переносите этот compose-файл на боевой сервер. Пароли в .env, порты наружу, отсутствие ограничений по ресурсам, отладочные настройки PHP — всё это допустимо на локальной машине и недопустимо в проде. Продакшен-конфигурация — отдельная работа с другими требованиями, а не тот же файл с изменённым паролем.
Частые вопросы
Из каких контейнеров состоит окружение для Битрикса?
Из шести: nginx как веб-сервер, PHP-FPM для приложения, MySQL для базы, Redis для кэша и сессий, отдельный контейнер под cron и агенты, и push-сервер для мгновенных сообщений. Разделение агентов в свой контейнер важно: иначе они выполняются на хитах и замедляют работу разработчика.
Почему агенты выносят в отдельный контейнер?
Потому что по умолчанию агенты Битрикса выполняются при загрузке страницы, то есть внутри пользовательского запроса. Отдельный контейнер с cron решает это так же, как на боевом сервере: агенты запускаются по расписанию независимо от трафика, а страницы отдаются быстрее. Заодно окружение становится ближе к продакшену.
Зачем нужен Redis в окружении Битрикса?
Для кэша и сессий. Хранение сессий в Redis, а не в файлах, снимает проблему прав на директорию сессий и делает поведение предсказуемым при перезапуске контейнеров. Для кэша Redis даёт согласованность между несколькими процессами PHP, чего файловый кэш в контейнерах не гарантирует.
Чем это окружение отличается от продакшена?
Тем, что продакшен большинства проектов — это виртуальная машина от вендора со своей связкой веб-серверов, своими путями и своими настройками PHP. Различия регулярно дают классическое «на локалке работает, на проде нет»: разное поведение конфигурации веб-сервера, разные пути к сессиям, разный набор расширений. Сверяйте вывод phpinfo в обеих средах.
Что поменять при подготовке боевой конфигурации?
Сменить все пароли в файле переменных окружения, настроить сертификаты, включить отключение проверки временных меток в opcache ради скорости, настроить резервное копирование базы и добавить мониторинг. И помнить, что запуск Битрикса в контейнерах в продакшене — отдельное решение со своими последствиями для поддержки от вендора.
Как сохранять данные между перезапусками контейнеров?
Постоянными томами для базы данных и для каталога загрузок. Всё, что не в томе, исчезает при пересоздании контейнера, а каталог загрузок в Битриксе — это пользовательские файлы, которых нет в репозитории. Отдельно проверьте, что тома объявлены именованными, а не привязаны к случайным путям: иначе очистка неиспользуемых томов однажды заберёт вашу базу.