Setting up Docker containers for 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Docker Container Configuration for 1C-Bitrix

Docker Container Configuration for 1C-Bitrix

Running Bitrix in Docker is straightforward. Configuring containers to operate stably in production — restarting correctly, retaining data, and avoiding permission issues — requires knowledge of the platform's specifics. Common problems: Bitrix writes files as root, but nginx reads them as www-data; OPcache does not detect changes in code files; agents do not run because cron is not configured inside the container.

nginx Container Configuration

# docker/nginx/conf.d/bitrix.conf
server {
    listen 80;
    server_name _;
    root /var/www/html;
    index index.php;
    charset utf-8;
    client_max_body_size 256m;

    # Bitrix security
    location ~* /\.ht { deny all; }
    location ~* /bitrix/modules { deny all; }
    location ~* /bitrix/php_interface { deny all; }
    location ~* /bitrix/tools { deny all; }

    # Static files with caching
    location ~* \.(jpg|jpeg|png|gif|webp|svg|ico|css|js|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
        try_files $uri =404;
    }

    # Bitrix clean URLs
    location / {
        try_files $uri $uri/ /bitrix/urlrewrite.php$is_args$args;
    }

    # PHP via FPM
    location ~ \.php$ {
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # Timeouts for long-running operations (import, reports)
        fastcgi_read_timeout 300;
        fastcgi_send_timeout 300;
    }

    # Bitrix urlrewrite
    location = /bitrix/urlrewrite.php {
        fastcgi_pass php-fpm:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

File Permissions: Resolving the UID Problem

A classic problem: PHP-FPM inside the container runs as www-data (UID 33), while files in the volume were created by root or a different host user. Bitrix cannot write cache or save uploaded files.

Set the UID explicitly in the Dockerfile:

FROM php:8.1-fpm-alpine

# Create a user with the host UID (passed via build arg)
ARG HOST_UID=1000
RUN addgroup -g $HOST_UID bitrix \
    && adduser -u $HOST_UID -G bitrix -D bitrix

# Run PHP-FPM as bitrix
RUN sed -i 's/user = www-data/user = bitrix/g' /usr/local/etc/php-fpm.d/www.conf \
    && sed -i 's/group = www-data/group = bitrix/g' /usr/local/etc/php-fpm.d/www.conf

Pass the UID in docker-compose:

php-fpm:
  build:
    context: ./docker/php
    args:
      HOST_UID: ${HOST_UID:-1000}

.env:

HOST_UID=1000  # or the result of `id -u` on the host

Healthcheck for PHP-FPM

php-fpm:
  healthcheck:
    test: ["CMD-SHELL", "SCRIPT_FILENAME=/var/www/html/index.php SCRIPT_NAME=/index.php REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q '200 OK'"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s

Simpler alternative:

healthcheck:
  test: ["CMD-SHELL", "php-fpm -t && kill -0 1"]
  interval: 30s

Restart Policies

services:
  nginx:
    restart: unless-stopped  # restart always except after explicit stop

  php-fpm:
    restart: unless-stopped

  mysql:
    restart: always  # MySQL always, including after host reboot

unless-stopped vs always: with always, the container will restart even if explicitly stopped with docker stop. unless-stopped — does not restart after an explicit stop, but restarts after the Docker daemon restarts.

Log Rotation

By default, Docker accumulates logs indefinitely. On an active Bitrix site this can reach 1–5 GB per week:

# docker-compose.yml
services:
  nginx:
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"

  php-fpm:
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"

Or globally in /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

Zero-Downtime Container Updates

# Update only php-fpm without stopping nginx
docker-compose pull php-fpm
docker-compose up -d --no-deps --build php-fpm

# Verify the new container is healthy
docker-compose ps php-fpm

# If something went wrong — rollback
docker-compose up -d --no-deps php-fpm --scale php-fpm=1

When using multiple PHP-FPM replicas (via docker-compose scale), update one at a time while the others handle traffic.

Backups

#!/bin/bash
# backup.sh — run via cron on the host

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/bitrix"

# Database dump through the running container
docker-compose exec -T mysql mysqldump \
    -u bitrix -p"${DB_PASSWORD}" \
    --single-transaction bitrix | gzip > "$BACKUP_DIR/db_$DATE.sql.gz"

# Archive upload/ and config files
docker run --rm \
    -v bitrix_files:/data:ro \
    -v "$BACKUP_DIR":/backup \
    alpine tar czf "/backup/files_$DATE.tar.gz" /data/upload /data/bitrix/.settings.php

# Remove backups older than 7 days
find "$BACKUP_DIR" -mtime +7 -delete