Bitrix.infra
https://favor-group.ru Bitrix site infrastructure as a code
Install / Use
/learn @paskal/Bitrix.infraREADME
Bitrix infrastructure as a code

This repository contains infrastructure code behind Bitrix-based site of my father's metal decking business operating in multiple cities.
It's a Bitrix website completely enclosed within docker-compose to be as portable and maintainable as possible, and a set of scripts around its maintenance like dev site redeploy or production site backup.
Architecture
flowchart TB
User["Browser"] -->|"HTTP/3, TLS 1.3,<br>Brotli"| Nginx
subgraph Docker["Docker Compose"]
Nginx["Nginx<br>(brotli + lua + HTTP/3)"]
Nginx -->|"FastCGI :9000"| PHP["PHP-FPM 8.4"]
Nginx -->|"static files"| Web["Web Files<br>prod / dev"]
PHP -->|"Unix socket"| MySQL[("Percona MySQL 8.0<br>(socket-only, no TCP)")]
PHP --> Memcached["Memcached<br>Cache (2 GB)"]
PHP --> MemSessions["Memcached<br>Sessions (128 MB)"]
PHP --> Web
PHPCron["PHP Cron<br>(agents, exports,<br>sitemaps)"] -->|"Unix socket"| MySQL
PHPCron --> Memcached
PHPCron --> MemSessions
PHPCron --> Web
subgraph Optional["Optional Services (profiles)"]
Certbot["DNSroboCert<br>(Let's Encrypt)"]
Zabbix["Zabbix Agent 2"]
Adminer["Adminer"]
Updater["Updater<br>(webhooks)"]
FTP["Pure-FTPD"]
end
Zabbix -->|"monitor"| MySQL
Zabbix -->|"monitor"| Nginx
Adminer -->|"Unix socket"| MySQL
end
subgraph HostCron["Host Cron"]
Backup["Backups<br>(duplicity + mysqldump)"]
Minify["JS/CSS Minify<br>(hourly)"]
ImgOpt["Image Optimisation<br>(weekly)"]
end
Backup -->|"incremental + dumps"| S3[("Yandex S3")]
Certbot -->|"DNS-01 challenge"| YcDNS["Yandex Cloud DNS"]
subgraph Regions["Domains"]
MSK["favor-group.ru"]
SPB["spb.favor-group.ru"]
Tula["tula.favor-group.ru"]
Dev["dev.favor-group.ru"]
CDN["static.cdn-favor-group.ru"]
end
Regions --> Nginx
The site serves three regions (Moscow, St Petersburg, Tula) via subdomains, each with its own robots.txt, sitemap, redirect map, and product export feeds. All traffic goes through a single nginx instance with HTTP/3 (QUIC), brotli compression, and multi-layer bot detection. MySQL is accessible only via Unix socket (no TCP port exposed). Backups run to Yandex Object Storage: incremental file backups via duplicity daily, MySQL dumps twice daily.
Is it fast?
You bet! Here is a performance on Yandex.Cloud server with Intel Cascade Lake 8 vCPUs, 16Gb of RAM and 120Gb SSD 4000 read\write IOPS and 60Mb/s bandwidth.
<img width="1100" alt="image" src="https://user-images.githubusercontent.com/712534/172490266-88710b9f-3776-4c5b-9852-590181d1d204.png">What's inside?
Core
- Nginx (ghcr.io/paskal/nginx) with brotli, HTTP/3 (QUIC) and Lua modules — proxies requests to php-fpm and serves static assets directly
- php-fpm 8.3 / 8.4 / 8.5 (ghcr.io/paskal/bitrix-php) for Bitrix with msmtp for mail sending
- Percona MySQL 8.0 because of its monitoring capabilities
- memcached for Bitrix cache and user sessions
Multi-region setup
The site serves three cities — Moscow (favor-group.ru), Saint Petersburg (spb.favor-group.ru) and Tula (tula.favor-group.ru) — from a single Bitrix installation, database and document root. The Bitrix aspro.max module handles region-aware content, while nginx and cron scripts handle the SEO layer.
- robots.txt — nginx rewrites
/robots.txtto/aspro_regions/robots/robots_$host.txt, so each subdomain gets its own file. A cron script (alter-robots-txt.sh, every 10 minutes) patches these files after Bitrix regenerates them: Moscow indexes everything, SPb blocks/info/blog/(centralised on Moscow to avoid duplicate content), Tula additionally blocks/montag/and/projects/which don't exist for that region. - sitemaps — nginx rewrites
/sitemap*.xmlto/aspro_regions/sitemap/sitemap*_$host.xml. Four cron jobs generate them nightly:sitemap.bitrix.php,sitemap.aspro.php,sitemap.offers.phpandsitemap.regions.php. - redirect maps —
config/nginx/conf.d/redirects-map.confcontains fourmapblocks: one per region ($new_uri_msk,$new_uri_spb,$new_uri_tula) for region-specific redirects (e.g. Tula bounces all/montag/and/projects/URLs to Moscow), plus a global$new_urimap for site-wide URL cleanup.
Yandex Metrika cookie extension
Safari's Intelligent Tracking Prevention (ITP) limits cookies set by JavaScript to 7 days (24 hours in some cases). This means the Metrika visitor identifier (_ym_uid) expires between visits, causing returning visitors to appear as new ones in analytics. Following Yandex's official recommendation, nginx re-sets the Metrika cookies (_ym_uid, _ym_d, _ym_ucs) server-side via Set-Cookie headers with a 1-year lifetime — browsers respect the full expiry for server-set cookies.
The implementation uses nginx map blocks (config/nginx/conf.d/metrika-cookies.conf) rather than if directives to avoid the "if is evil" problem — using add_header inside an if block replaces all parent-level headers, which would drop Cache-Control, security headers and CSP from static file responses. When the cookie is absent the map resolves to an empty string and no header is emitted.
Optional
- PHP cron container (
php-cron) with same settings as PHP serving web requests - adminer (
adminer) as phpMyAdmin alternative for work with MySQL - pure-ftpd (
ftp) for FTP access - DNSroboCert (
certbot) for Let's Encrypt HTTPS certificate generation - zabbix-agent2 (
zabbix-agent, ghcr.io/paskal/zabbix-agent2) for monitoring - Webhooks server (
updater) for automated tasks.
Automation (host cron)
These run on the host machine outside Docker, scheduled via config/cron/host.cron:
- JS/CSS minification — runs hourly via
tdewolff/minifyDocker image onweb/prod/localandweb/dev/local, producing.min.js/.min.cssfiles - Image optimisation — runs weekly (Saturday night) via
scripts/optimise-images.sh, processing PNG (optipng + advpng), JPEG (jpegoptim), WebP (cwebp) and GIF (gifsicle) inweb/prod/upload. Uses a SQLite database to track already-processed files and avoid redundant work - Log rotation — configured in
config/logrotate/for nginx (weekly for production access logs at 100 MB minimum, monthly for others) and PHP (monthly for error, cron and msmtp logs). Nginx logs are reopened vianginx -s reopen, PHP-FPM viaUSR1signal
Bitrix configuration
These are the relevant Bitrix config files that connect the CMS to the dockerised services (memcached for sessions/cache, MySQL via socket, cron agents). Documentation: sessions 1 2 (ru 1, 2), cache (ru)
<details><summary>bitrix/php_interface/dbconn.php</summary>// Enable cron-based agent execution
define('BX_CRONTAB_SUPPORT', true);
// Database connection (legacy, also configured in .settings.php)
$DBType = "mysql";
$DBHost = "localhost";
$DBName = "<DBNAME>";
$DBLogin = "<DBUSER>";
$DBPassword = "<DBPASSWORD>";
// Temporary files directory
define('BX_TEMPORARY_FILES_DIRECTORY', '/tmp');
// Standard Bitrix configuration
define("BX_UTF", true);
define("BX_FILE_PERMISSIONS", 0644);
define("BX_DIR_PERMISSIONS", 0755);
@umask(~(BX_FILE_PERMISSIONS|BX_DIR_PERMISSIONS)&0777);
define("BX_DISABLE_INDEX_PAGE", true);
</details>
<details><summary>bitrix/.settings.php</summary>
'session' => array (
'value' =>
array (
'mode' => 'separated',
'lifetime' => 14400,
'handlers' =>
array (
'kernel' => 'encrypted_cookies',
'general' =>
array (
'type' => 'memcache',
'host' => 'memcached-sessions',
'port' => '11211',
