Docker Compose для Битрикс — production-ready конфиг

Полный docker-compose.yml для Битрикс: nginx, PHP-FPM, MySQL, Redis, cron. Оптимизация для production, persistent volumes, healthchecks.

Дмитрий Мещеряков
Дмитрий Мещеряков
📅 20 июля 2026 г.📖 8 мин чтения

Это разбор полной конфигурации: nginx, PHP-FPM, MySQL, Redis, отдельный контейнер для агентов и push-сервер. В отличие от минимального набора «поднять и посмотреть», здесь учтены вещи, которые всплывают при попытке пользоваться таким окружением каждый день, — cron, сессии в Redis, разделение dev- и общих настроек.

Сразу оговорю границу: это окружение для разработки. Битрикс в продакшене живёт в конфигурации, которую поддерживает вендор, и различия между ней и вашим контейнером — источник классического «на локалке работает».

Что входит в окружение и зачем

Docker Compose — это описание нескольких связанных контейнеров одним файлом. Здесь их шесть: nginx как веб-сервер, PHP-FPM для приложения, MySQL для базы, Redis для кэша и сессий, отдельный контейнер под cron с агентами и push-сервер.

text
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

yaml
# 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

dockerfile
# 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 для Битрикс

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

ini
; 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

ini
# 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

Как запускать агенты Битрикса в контейнере

bash
#!/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 файл

bash
# .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
<?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)

Команды

bash
# Запуск
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

Итоги

КонтейнерОбразНазначение
nginxnginx:1.25-alpineВеб-сервер
phpphp:8.2-fpm-alpinePHP-FPM
mysqlmysql:8.0База данных
redisredis:7-alpineКэш, сессии
cronphp:8.2-fpm-alpineАгенты
pushbitrix-push-serverPush & 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 ради скорости, настроить резервное копирование базы и добавить мониторинг. И помнить, что запуск Битрикса в контейнерах в продакшене — отдельное решение со своими последствиями для поддержки от вендора.

Как сохранять данные между перезапусками контейнеров?

Постоянными томами для базы данных и для каталога загрузок. Всё, что не в томе, исчезает при пересоздании контейнера, а каталог загрузок в Битриксе — это пользовательские файлы, которых нет в репозитории. Отдельно проверьте, что тома объявлены именованными, а не привязаны к случайным путям: иначе очистка неиспользуемых томов однажды заберёт вашу базу.