Это руководство про контейнеры. Если вы ставите инструмент прямо на машину с 1С (обычный путь для Windows), вам нужно другое: пошаговое руководство по установке.
Здесь всё то же самое, но программа живёт в контейнере, а машина с Docker может быть отдельным сервером Linux.
| Образ | Что внутри | Можно ли публиковать |
|---|---|---|
ядро (core) |
Python, Git, сам инструмент | да: ничего чужого внутри нет |
с платформой (native) |
ядро + толстый клиент 1С:Предприятие | нет, никогда |
Платформа 1С:Предприятие распространяется по лицензии и её нельзя класть в образ, который увидит кто-то ещё. Поэтому образ с платформой вы собираете у себя, из своего легально полученного дистрибутива, и он остаётся только на вашей машине. Ни в какой реестр его выкладывать нельзя.
Актуальная native-приёмка: VERIFIED_STATUS.md и TCP-проверка 20.09.2026. Числа ранних автотестов ниже относятся к прежнему этапу. Compose проверялся в прежнем сценарии ядра/фикстуры; для native используйте launcher из OPERATIONS_RU.md, а не этот Compose.
| Состояние | |
|---|---|
| Сборка и запуск образа ядра на Linux | проверено |
| Все автотесты внутри Linux-контейнера | проверено, 321 прошли, 19 пропущены (им нужен Windows: NTFS junction, описатель каталога без FILE_SHARE_DELETE и обёртка на PowerShell) |
| Сценарии пользователя на файловой фикстуре в контейнере | проверено, 45 проверок |
| Разделение доступа к хранилищу между двумя контейнерами | проверено на подменённом конфигураторе (см. §9) |
Запуск docker compose и отправка в Git |
проверено, включая отправку по ssh с проверкой ключа сервера |
| Сборка приватного образа из настоящего комплекта 8.3.27.2130 | проверено, платформа ставится и запускается |
| Какие системные пакеты нужны платформе | проверено — список снят ldd с установленного 1cv8, а не из документации |
| Нужен ли Xvfb | проверено: нужен. Без DISPLAY конфигуратор не стартует вовсе (код 255) |
Остановка контейнера (docker stop) доходит до работы |
проверено после исправления: xvfb-run сигнал не пропускал |
| Произвольный UID для образа с платформой | проверено: нужна учётная запись. Без записи в /etc/passwd толстый клиент падает по SIGSEGV; образ дописывает её на старте (§10) |
| Причина отказа конфигуратора видна вызывающему | проверено на живой платформе: текст из файла /Out попадает в ошибку (§10) |
| Работа с настоящим хранилищем 1С из контейнера Linux | ПРОВЕРЕНО на стенде, файловый и TCP-доступ, 17/17 версий при jobs=1/2; отчёт |
| Лицензирование платформы в контейнере | Действующая лицензия обеспечила реальные выгрузки на стенде; универсальная настройка для чужой среды и первопричина редких отказов не доказаны (§10, границы) |
На раннем этапе стенд останавливался с ошибкой «Не найдена лицензия». Это исторический результат, а не текущий статус: после подключения действующего лицензирования выполнены реальные файловые и TCP-выгрузки. Приёмка своей среды всё равно обязательна; инструкции ниже не выдают и не заменяют лицензию.
Ничем по сути: те же команды, тот же манифест, тот же результат в Git. Разница только в том, что пути внутри контейнера свои, и их надо правильно связать с папками на вашей машине. Этим и занято всё руководство.
- Docker — на Linux это
dockerиdocker compose; на Windows — Docker Desktop с включённым линуксовым движком. - Место на диске — считайте не меньше 40 ГБ там, где Docker хранит данные. На Windows это виртуальный диск Docker Desktop, а не папка проекта.
- Дистрибутив 1С для Linux — только для образа с платформой (§10). Для всего остального не нужен.
Проверить, что Docker жив:
docker version
docker compose versionЗаведите отдельную папку для работы инструмента. Внутри репозитория ничего из этого лежать не должно.
/srv/gitsync/ ← любое место, какое вам удобно
├── база/ ← ВЫХОДНОЙ репозиторий Git (сюда пишется история)
├── конфигурация/
│ └── storages.json ← манифест: какие хранилища выгружать
└── секреты/
└── storage.txt ← файл с паролем хранилища
Три вещи, которые чаще всего путают:
- Исходный репозиторий и выходной — разные вещи. Исходное — это хранилище конфигурации 1С (сервер хранилища или папка), и инструмент его только читает. Выходное — это репозиторий Git, куда ложится история. Их нельзя совмещать.
- Манифест лежит ВНЕ выходного репозитория. Иначе он попадёт в историю и будет мешать: инструмент не даёт класть служебные файлы в рабочую копию.
- Пароль лежит в отдельном файле, вне репозитория. Ни в манифесте, ни в командной строке (там его видно в списке процессов), ни в образе.
Контейнер видит не ваши пути, а свои. Соответствие всегда одно и то же:
| Папка на машине | Путь внутри контейнера | Права |
|---|---|---|
/srv/gitsync/база |
/repo |
запись |
/srv/gitsync/конфигурация |
/config |
только чтение |
/srv/gitsync/секреты/storage.txt |
/run/secrets/storage_password |
только чтение |
| (том Docker) | /work/tmp |
запись, прочие временные файлы |
| (том Docker) | /var/lib/gitsync/sessions |
запись, общие блокировки |
| не монтируется | /var/lib/gitsync/scratch |
КАТАЛОГ ЗАПУСКА: только слой контейнера |
В манифесте пишутся пути КОНТЕЙНЕРА, то есть /repo, /config, а не
/srv/gitsync/база и не D:\.... Это самая частая ошибка.
Каталог запуска не монтируется — это проверяется и нарушение отвергается. Выгрузка
конфигуратора складывается в /var/lib/gitsync/scratch, и этот путь обязан оставаться на
собственном слое контейнера. Замерено на стенде (evidence/stage-18): если каталог запуска
лежит на смонтированном томе — bind-mount каталога хоста ИЛИ том Docker, именованный либо
анонимный, — то другой контейнер с тем же UID видит выгрузку, читает её и уничтожает
rm -rf. На собственном слое контейнера тот же нападающий каталога не видит вовсе.
Поэтому temp_root в манифесте контейнера задавать не нужно: умолчание уже верное. Если
задать его на смонтированный том, задание откажется работать — до создания каталога и
до любого удаления.
Слева пишутся пути Windows, справа — всё равно линуксовые:
REPO_HOST=D:/git/база → /repo
CONFIG_HOST=D:/gitsync/конфиг → /config
Косые чёрточки — обычные /, даже для Windows-путей. Обратные слэши в файле
.env работают ненадёжно. И ещё раз: в манифесте D:/... не пишут никогда —
только /repo.
Контейнер работает не от администратора, а от обычного пользователя. Чтобы он мог писать в ваши папки, надо запускать его своим UID. Запуск от root инструмент прекращает сам (см. §8): под root файлы в ваших папках достались бы root, и следующий обычный запуск уже не смог бы их переписать.
Узнайте свой номер:
id -u # например 1000Штатная форма запуска — свой UID и группа 0:
docker run --rm --user "$(id -u):0" ... gitsync-py:core --versionГруппа 0 здесь — не «права root». Внутри образа рабочие каталоги
(/home/app, /work/tmp, /var/lib/gitsync/sessions, /var/lib/gitsync/scratch,
/repo) принадлежат
app:0 и открыты на запись группе, поэтому с GID=0 работает любой UID —
и тот, которого нет в /etc/passwd образа. Прав администратора это не даёт:
контейнер запускается с cap_drop: ALL и no-new-privileges.
Единственное исключение — отправка в Git по ssh: OpenSSH требует, чтобы UID
был описан в /etc/passwd образа. Подробности и два выхода — в §7, во врезке про
No user exists for uid.
Хотите запускаться именно своей группой (--user со своим GID) — соберите образ
под неё, тогда владельцем каталогов внутри образа станет ваша пара:
docker build -f docker/Dockerfile.core --target core \
--build-arg APP_UID=$(id -u) --build-arg APP_GID=$(id -g) -t gitsync-py:core .Оба пути равноправны. Первый не требует своей сборки образа, второй — своего
GID. Чего делать не надо — запускать с чужим GID без пересборки: тогда
контейнер не сможет писать в /home/app, и предполётная проверка остановит
работу с кодом 78 ещё до обращения к хранилищу.
docker volume create gitsync-sessionsНовый именованный том при первом подключении получает владельца и права
от каталога образа — здесь это app:0 с правом записи для группы. Поэтому
при запуске с GID=0 том готов сразу: менять владельца не нужно, рекурсивный
chown чужих данных нигде не требуется.
Проверить, что связка «UID + тома» рабочая, можно до всякой синхронизации:
docker run --rm --user "$(id -u):0" \
-v gitsync-sessions:/var/lib/gitsync/sessions \
gitsync-py:core --versionОтветил номер версии — предполётная проверка пройдена. Ответил 78 — в
сообщении написано, что именно не так и какой из двух путей выбрать.
Уже созданный том владельца не меняет. Если вы переезжаете на другой UID/GID
(например, пересобрали образ под свой GID), старый том блокировок останется за
прежним владельцем, и предполётная проверка честно откажет. Заведите новый том
под новую пару и укажите его в SESSIONS_VOLUME:
docker volume create gitsync-sessions-1000Менять владельца существующего тома тоже можно, но это уже действие над чужими данными — делайте его осознанно и когда синхронизация не работает.
Ваши собственные папки (рабочая копия Git, настройки, хранилище) остаются вашими: их владельца инструмент не меняет никогда. Если рабочая копия принадлежит другому пользователю, Git откажется с ней работать — разбор в §8.
Это важнее, чем кажется. Все контейнеры, которые работают с одним и тем же хранилищем, обязаны запускаться под одним и тем же UID. Файл блокировки, который не даёт двум конфигураторам зайти в хранилище одновременно, создаётся с правами «только владелец». Под другим UID его не открыть — и такой запуск не «проскочит мимо блокировки», а остановится с ошибкой доступа к файлу блокировки (проверено запуском). Данные это не портит, но половина запусков будет просто падать, поэтому единый UID — не формальность.
Из корня проекта:
docker build -f docker/Dockerfile.core --target core -t gitsync-py:core .Проверить, что собралось то, что нужно:
# должен ответить номер версии
docker run --rm gitsync-py:core --version
# должен быть обычный пользователь, а не root
docker run --rm --entrypoint id gitsync-py:coreБазовый образ закреплён по digest (python:3.11-slim-bookworm@sha256:5282…bdf84).
Это сделано специально: тег со временем переезжает на новую сборку, и без digest
завтрашний образ отличался бы от сегодняшнего. Проверенная сборка —
Python 3.11.16, Debian 12, Git 2.39.5.
Занимает образ 342 МБ, но 251 МБ из них — общий базовый образ Python, который переиспользуется и другими образами; собственный вклад — около 91 МБ (из них примерно 9 МБ — клиент ssh, нужный для отправки в Git).
В образе выполнения нет: тестов, истории Git, документации, примеров, никаких дистрибутивов и никаких паролей. Проверить самому:
docker run --rm --entrypoint sh gitsync-py:core -c \
'find / -xdev \( -name "test_*.py" -o -name "*.deb" -o -name "*.lic" \) | head'Пусто — значит всё в порядке.
Файл .dockerignore не пускает в сборку историю Git, виртуальные окружения,
кэши, журналы, а также всё, похожее на секрет или на дистрибутив: .env,
*.key, *.pem, *.lic, *.deb, *.run, *.exe, *.cf. Docker отправляет
демону всю папку сборки целиком, поэтому случайно положенный рядом файл с
паролем уехал бы вместе с ней — даже если ни одна команда его не копирует.
Единственное исключение из запрета на *.cfe — src/gitsync/data/tempExtension.cfe.
Это пустой шаблон расширения из оригинального gitsync под лицензией MPL-2.0
(происхождение и контрольная сумма — в src/gitsync/data/README.md), он нужен
самой программе и ничьей выгрузкой не является.
Прежде чем связываться с настоящим хранилищем, стоит убедиться, что папки,
права и манифест настроены верно. Для этого есть учебное «хранилище» — обычная
папка с заранее подготовленной выгрузкой (--backend fixture).
Подготовьте:
mkdir -p /srv/gitsync/{база,конфигурация,фикстура}
git -C /srv/gitsync/база init -b main
cp -r examples/fixture-storage/* /srv/gitsync/фикстура/
chown -R "$(id -u)" /srv/gitsync # свои же папки; чужих каталогов это не касаетсяМанифест /srv/gitsync/конфигурация/storages-fixture.json — обратите внимание,
пути внутренние:
{
"repository": "/repo",
"defaults": {
"backend": "fixture",
"init": true,
"jobs": 2,
"email_domain": "corp.local"
},
"storages": [
{ "name": "Конфигурация", "subtree": "Конфигурация", "fixture_root": "/fixture" }
]
}Запуск:
docker volume create gitsync-sessions
docker run --rm \
--user "$(id -u):0" \
-e TZ=Europe/Moscow \
-v /srv/gitsync/база:/repo \
-v /srv/gitsync/конфигурация:/config:ro \
-v /srv/gitsync/фикстура:/fixture:ro \
-v gitsync-sessions:/var/lib/gitsync/sessions \
gitsync-py:core sync-all --config /config/storages-fixture.jsonДолжно получиться примерно так:
Общий репозиторий: /repo
=== Конфигурация ===
Рабочая копия подготовлена: /repo/Конфигурация
Зафиксировано версий: 2 (1..2)
Проверьте результат:
git -C /srv/gitsync/база log --format='%h %an <%ae> %ad %s' --date=iso
cat /srv/gitsync/база/Конфигурация/VERSIONЗапустите ту же команду ещё раз — новых коммитов быть не должно, в ответ придёт «Новых версий нет». Это и есть правильное поведение: повторный запуск безопасен.
Хранилище 1С отдаёт время версии без указания пояса. Инструмент считает его местным. В контейнере «местное» по умолчанию — UTC, то есть время коммитов уехало бы на несколько часов относительно того, что видят разработчики, и SHA коммитов не совпали бы с прогоном на машине с 1С.
Поэтому TZ — обязательная переменная для команд sync, sync-all и
clone. Без неё контейнер не начнёт работу и скажет об этом:
gitsync: предполётная проверка не пройдена.
Не задан часовой пояс (TZ).
...
Укажите пояс ИСТОЧНИКА, например: -e TZ=Europe/Moscow
Указывайте пояс источника — то есть того места, где работают разработчики
1С, а не того, где стоит сервер с Docker. Если вы осознанно хотите UTC, задайте
GITSYNC_ALLOW_DEFAULT_TZ=1.
Проверено: при TZ=Europe/Moscow версия со временем 09:01:00 получает в Git
дату 2026-09-16 09:01:00 +0300.
Файлы docker/compose.yaml и docker/env.example описывают всё сразу: тома,
пользователя, секреты, ограничения.
cp docker/env.example docker/.env
# отредактируйте пути и UID под свою машину
docker volume create gitsync-sessions # один раз на машину
# проверить настройку, ничего не запуская:
docker compose --env-file docker/.env -f docker/compose.yaml config
# репетиция без 1С:
docker compose --env-file docker/.env -f docker/compose.yaml run --rm sync-fixtureОбязательные переменные заданы так, что compose откажется работать, если их забыли, и назовёт пропущенную:
required variable SOURCE_TZ is missing a value: укажите часовой пояс хранилища,
например Europe/Moscow
Том gitsync-sessions объявлен внешним намеренно. Если бы compose создавал его
сам, у каждого проекта появился бы свой том с префиксом имени проекта,
блокировка перестала бы быть общей — и два конфигуратора пошли бы в одно
хранилище вместе. Забыли создать — будет честный отказ:
external volume "gitsync-sessions" not found.
Синхронизация никуда ничего не отправляет. Это сделано нарочно: у процесса, который ходит в хранилище, не должно быть ключа от вашего репозитория Git, а у процесса отправки не должно быть пароля от хранилища.
Сначала посмотрите, что получилось (коммиты, VERSION, ветка), и только потом:
docker compose --env-file docker/.env -f docker/compose.yaml run --rm pushКлюч SSH подключается только для чтения, проверка ключа сервера не отключается,
пароль в адрес репозитория не встраивается. Клиент ssh входит в образ ядра —
без него отправка по ssh:// падала бы с ssh: not found ещё до подключения.
UID для отправки обязан быть известен образу. OpenSSH спрашивает
/etc/passwdраньше, чем делает что-либо ещё, поэтому запуск с UID, которого в образе нет, даётNo user exists for uid 1000/fatal: Could not read from remote repository.Ключи и права тут ни при чём. Два рабочих выхода:
- собрать образ под себя (годится и для всего остального):
--build-arg APP_UID=$(id -u) --build-arg APP_GID=$(id -g), запускGITSYNC_UID=$(id -u),GITSYNC_GID=$(id -g);- либо запускать отправку пользователем образа (
app, uid 10001) и дать ему права на рабочую копию.Для самой синхронизации это не нужно: без записи в
/etc/passwdона работает, и контейнер об этом честно предупреждает в начале запуска.
Ключ сервера нужно получить заранее. StrictHostKeyChecking=yes означает, что
неизвестный сервер будет отвергнут — это защита от подмены, а не помеха:
ssh-keyscan -t ed25519 git.example.org > /srv/gitsync/конфигурация/known_hosts
# сверьте отпечаток с тем, что публикует ваш сервер:
ssh-keygen -lf /srv/gitsync/конфигурация/known_hostsФайл known_hosts лежит в том же каталоге настроек, который подключается как
/config (только для чтения). Приватный ключ там лежать не должен: он
подключается отдельно, как secret (GIT_SSH_KEY_FILE в .env), и в образ не
попадает.
Проверено на локальном зеркале: после run --rm push дерево коммита в зеркале
совпадает с исходным побайтово. Отправка по ssh проверена отдельно —
одноразовым сервером Git по ssh в изолированной сети контейнеров, с ключом,
созданным только для этой проверки (см. docs/development.md).
Если запускаете из сценария или по расписанию, добавляйте
-T:docker compose ... run --rm -T sync. Без него compose выделяет терминал и забирает себе оставшийся ввод сценария — команда отработает, а остаток файла просто не выполнится. В интерактивной работе это не мешает. Имена служб —sync,sync-fixture,push; посмотреть их список:docker compose --env-file docker/.env -f docker/compose.yaml config --services.
Инструмент устроен так, чтобы останавливаться до работы, а не портить
историю. Код возврата 78 означает «неправильно настроено». Вот все случаи.
Самая частая ситуация. Папка на машине принадлежит одному пользователю, а контейнер запущен от другого — Git в такой ситуации отказывается работать (и правильно делает).
Каталог принадлежит UID 0, контейнер запущен от 10001.
Выберите одно:
1) сменить владельца каталога на хосте: chown -R 10001:10001 <каталог на хосте>
2) запустить контейнер владельцем каталога: --user <UID>:<GID>
3) если владелец доверенный — разрешить ИМЕННО этот путь: -e GITSYNC_TRUST_REPO=1
Лучший вариант — первый или второй. Третий разрешает ровно один путь и
ничего больше; разрешения «доверять любому каталогу» инструмент не выдаёт
никогда. И учтите: разрешение снимает возражение Git, но не даёт прав на
запись — если папка чужая, запись всё равно не пройдёт, и вы увидите
Permission denied с точным путём.
Том с блокировками создан под другим UID. Либо запускайте контейнер тем же UID, либо смените владельца тома.
Бывает при запуске с произвольным --user, когда домашняя папка не подключена.
Подключите /home/app томом или задайте GITSYNC_SESSION_DIR на общий том с
правом записи.
Контейнер запущен с UID, которого нет в /etc/passwd, и без HOME. Задайте
GITSYNC_SESSION_DIR (абсолютным путём) — он и нужен для общей блокировки.
| Код | Что значит |
|---|---|
0 |
всё хорошо (в том числе «новых версий нет») |
1 |
синхронизация не удалась (какое-то хранилище не выгрузилось) |
2 |
ошибка в настройке или в аргументах команды |
78 |
предполётная проверка контейнера не пройдена |
130 |
остановлено оператором (docker stop, Ctrl+C) |
Код 130 — это не авария. Он означает «попросили остановиться, остановились
аккуратно». При этом текущая версия дописывается до конца, а следующие хранилища
не запускаются. Следующий запуск продолжит с того же места.
1С не даёт одному пользователю войти в хранилище дважды одновременно. Инструмент поэтому выстраивает обращения к хранилищу в очередь. Внутри одной машины это работает и между разными контейнерами — но только если выполнены три условия:
- общий том блокировок подключён ко всем контейнерам в одно и то же место
(
/var/lib/gitsync/sessions); - все контейнеры запущены под одним UID;
- хранилище указано одинаково — один и тот же
tcp://-адрес или один и тот же путь внутри контейнера. Два разных пути к одной папке инструмент свести не может и не пытается.
Проверено: два контейнера с общим томом заходят в хранилище строго по очереди (окна не пересекаются). Для контроля тот же опыт повторён с раздельными томами — там окна наложились на 2,7 секунды. То есть проверка действительно умеет отличать рабочую настройку от сломанной, а общий том — не украшение.
Если UID всё-таки разные. Файл блокировки создаётся с правами 0600, поэтому
чужой UID не может ни взять блокировку, ни пройти мимо неё: обращение к хранилищу
не состоится. Проверено на общем томе: контейнер-держатель с --user 10001:0,
второй контейнер с --user 12345:0 и с --user 12345:12345 — оба получили отказ.
Сообщение называет причину прямо, потому что ждать тут бессмысленно:
Нет прав на файл блокировки </var/lib/gitsync/sessions/….lock>: он принадлежит другому UID.
Ожидание тут не поможет — блокировка не занята, а недоступна.
Запускайте все обращения к одному хранилищу одной и той же парой UID:GID
(в контейнерах — одинаковый --user), либо дайте им разные каталоги сессий.
Граница этой проверки. Настоящий конфигуратор 1С в опыте очерёдности не запускался: подменён последний шов перед запуском процесса. Всё, что выше шва, — настоящий код. Проверка очерёдности на живой платформе остаётся невыполненной (§11).
Чего инструмент НЕ обещает. Блокировка действует в пределах одной машины и одной учётной записи. Между разными машинами, через сетевые диски, а также относительно конфигуратора, который кто-то открыл руками, никаких гарантий нет. На одно хранилище и один логин — одна машина-выгружатель.
Блокировка выше выстраивает в очередь ВХОД В ХРАНИЛИЩЕ. Лицензия — отдельный ресурс: платформа удерживает её на всё время работы конфигуратора, и на стенде два одновременных задания приводили к тому, что одному из них платформа отвечала «Не найдена лицензия. Не обнаружен ключ защиты программы или полученная программная лицензия!» (код 1). Порядок, кому откажут, случайный.
Штатный ответ на это — не обход механизма, а очередь допуска перед запуском
контейнера: examples/gitsync-run.sh. Она берёт flock на файле в каталоге
одного владельца (права 0700) и не пускает второе задание, пока идёт первое.
export GITSYNC_IMAGE=sha256:<идентификатор образа с платформой>
export GITSYNC_ADMISSION_DIR=/var/tmp/gitsync-admission
export GITSYNC_DOCKER_ARGS="--network gitsync_lm -u 10001:0 -e TZ=Europe/Moscow ..."
examples/gitsync-run.sh sync-all --config /work/monorepo.json --log-level INFOЧто это даёт и чего не даёт:
- подача параллельная, исполнение последовательное. Два задания можно подать одновременно; оба дождутся своей очереди и завершатся штатно. Перекрытия работы конфигураторов при этом НЕТ и не обещается;
- лицензионный механизм не меняется: ни ключи, ни
nethasp.ini, ни сервер лицензий очередь не трогает и одноместность лицензии не доказывает; - ожидание ограничено
GITSYNC_ADMISSION_WAIT(умолчание 1800 с). Превышение — код возврата 75: задание не запускалось и ничего не изменило; - код возврата задания проходит насквозь;
SIGINT/SIGTERMостанавливают контейнер (docker stop) и дают 130/143; - файлы замка НЕ удаляются никогда: удалить файл, на котором висит чужой
flock, значит снять координацию — следующий процесс создаст файл заново и войдёт, пока предыдущий ещё работает; - каталог очереди с правами на запись для группы или всех — отказ (код 77): замок в таком каталоге ничего не гарантирует;
GITSYNC_SLOTSпо умолчанию 1 — единственное подтверждённое значение. Повышать его стоит, только если ваша лицензия проверена на нескольких одновременных сеансах;- задания, запущенные мимо этого сценария (в том числе конфигуратор, открытый руками), в очереди не участвуют, и на них гарантия не распространяется.
Что здесь изменилось. Раздел больше не заготовка: приватный образ собран из настоящего комплекта 8.3.27.2130 x86_64 (
setup-full-8.3.27.2130-x86_64.run) на Debian 12, платформа установлена и запускается. Команды ниже — те самые, что выполнялись, а не переписанные из документации.Исторический отказ и текущий статус. Первое обращение на раннем стенде завершилось ошибкой «Не найдена лицензия», код 1. Позднее с действующим лицензированием выполнены реальные выгрузки файловых и серверных хранилищ. См. VERIFIED_STATUS.md и TCP-отчёт. Лицензирование вашей среды остаётся задачей её владельца; ниже сохранены сведения о диагностике штатных отказов платформы.
Разбиралось на 8.3.27.2130 в этом самом образе. Здесь только то, что подтверждено запусками.
Каталог, из которого платформа читает файл лицензии в Linux, — /var/1C/licenses.
Проверено детектором чтения: копии одного и того же .lic разложены по всем
правдоподобным каталогам, каждой сдвинуто время последнего доступа, после
запуска конфигуратора время обновилось только у /var/1C/licenses/….
Каталоги $HOME/.1cv8/1C/1cv8 (и его conf), $HOME/.1cv8/conf,
$HOME/.1C/1cv8/conf, /opt/1cv8/conf, /opt/1cv8/x86_64/<версия>/licenses
платформа для этого не открывает вовсе. Каталог licenses внутри установки —
это тексты лицензионного соглашения, а не хранилище лицензий.
docker run --rm ... -v /srv/1c-licenses:/var/1C/licenses:ro gitsync-py:native-8.3.27.2130 ...Файл лицензии нельзя просто скопировать с рабочего места. Программная лицензия привязана к параметрам компьютера, на котором её активировали. Пользователю платформа в любом случае говорит «Не найдена лицензия», а настоящая причина видна только в технологическом журнале:
LIC,1,...,Func=getLicense,res=error,txt='0, local Designer, error, local Designer;
soft, file:///var/1C/licenses/20260914163343.lic, cached, binding error, long, ..., developer;
EXCP,...,Descr='...LicenseBaseImpl.cpp(8508):
Ошибка программного лицензирования. Ошибка привязки программной лицензии к компьютеру:
file:///var/1C/licenses/20260914163343.lic
Sys Name_0: РАБОЧАЯ-СТАНЦИЯ ← имя компьютера, записанное в лицензии
Sys name_0: 7b1c0f3a9d42 ← имя этого контейнера
Так выглядит попытка отдать контейнеру лицензию, активированную на другой машине (в примере — на рабочей станции с Windows). Ни размещение файла, ни права доступа этого не лечат: контейнер — другой компьютер и другая ОС. Подгонять параметры машины под чужую привязку нельзя — это обход лицензирования, а не настройка.
Рабочие пути ровно два, и оба — к владельцу лицензии:
- активировать программную лицензию внутри этого окружения (активация необратима и привязывается к нему же);
- пробросить в контейнер аппаратный ключ защиты (или дать доступ к серверу лицензий по правилам вашей организации).
Как включить этот журнал. Каталог настроек платформы задан в
/opt/1cv8/conf/conf.cfg (ConfLocation=/opt/1cv8/conf); положите туда
logcfg.xml:
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="/work/tj" history="1">
<event><eq property="name" value="LIC"/></event>
<event><eq property="name" value="EXCP"/></event>
<property name="all"/>
</log>
</config>Журнал ляжет в /work/tj/<процесс>/<дата><час>.log в UTF-8. Выключая
диагностику, файл logcfg.xml уберите: журнал пишется постоянно.
Где вообще брать причину отказа конфигуратора. Конфигуратор 1С пишет
диагностику в файл, переданный ключом /Out. В stdout/stderr при этом
уходит постороннее (например, Fontconfig error: No writable cache directories),
и по ним причина не восстанавливается. Инструмент читает файл /Out сам и
показывает его текст в ошибке — вместе с путём к файлу, чтобы можно было
посмотреть целиком:
Ошибка на версии 3: Конфигуратор завершился с кодом 1: /opt/1cv8/.../1cv8 DESIGNER ...
Сообщение конфигуратора (/var/lib/gitsync/scratch/native-.../designer-out/designer-out-….log):
Не найдена лицензия. Не обнаружен ключ защиты программы или полученная программная лицензия!
Ядро образа рассчитано на запуск с произвольным UID. Толстый клиент 1С — нет:
если текущего UID нет в /etc/passwd, 1cv8 завершается по SIGSEGV (код 139)
ещё до создания файловой базы, без сообщения и без файла /Out. Проверено:
--user 10001:0 — код 0; --user 12345:0 — код 139; тот же --user 12345:0
с подложенным /etc/passwd, где эта строка есть, — снова код 0.
Поэтому в приватном образе первым в ENTRYPOINT стоит
docker/onec-user-entrypoint.sh: он дописывает запись о текущем UID в
/etc/passwd (файл для этого принадлежит группе 0 и доступен ей на запись) и
через exec передаёт управление дальше. Если /etc/passwd подключён только на
чтение, обёртка не падает, а предупреждает — тогда запускайте контейнер с
--user 10001:0.
HOME в образе (/home/app) принадлежит app:0 и доступен на запись, в том
числе при чужом UID из группы 0. Если вы задаёте свой HOME и он оказывается
недоступен на запись, каждый запуск конфигуратора начинает сыпать
Fontconfig error: No writable cache directories (по четыре строки на запуск).
Работу это не останавливает, но засоряет диагностику. Лечится одной переменной:
docker run ... -e HOME=/мой/каталог -e XDG_CACHE_HOME=/tmp/fontcache ...- Легально полученный дистрибутив толстого клиента 1С:Предприятие для Linux x86_64. Именно толстого: тонкий клиент разработку конфигураций не поддерживает, это сказано в документации платформы явно.
- Понимание, как он будет лицензирован в контейнере. Это отдельный вопрос, и наличие сервера лицензий в сети сам по себе ничего не доказывает. Программное лицензирование привязывается к параметрам машины, а контейнер создаётся заново каждый раз.
- Желательно — та же версия, что стоит на рабочих местах.
Дистрибутив кладите в отдельную папку, где нет ничего лишнего:
/srv/private/onec-8.3.27.2130/ ← только файлы вашего комплекта
Нужен BuildKit (docker buildx): дистрибутив подключается на время сборки и не
попадает ни в контекст, ни в слои образа.
docker buildx build -f docker/Dockerfile.native \
--build-context onec=/srv/private/onec-8.3.27.2130 \
--build-arg ONEC_VERSION=8.3.27.2130 \
--build-arg CORE_IMAGE=gitsync-py:core \
-t gitsync-py:native-8.3.27.2130 --load .Файл установщика в каталоге комплекта должен быть исполняемым
(chmod +x setup-full-*.run): он запускается прямо с подключённого каталога,
копия в образ не делается.
Комплект 8.3.27.2130 — это установщик BitRock. Его собственный --help
перечисляет компоненты; выдуманных имён там нет, поэтому сверяйтесь с ним:
./setup-full-8.3.27.2130-x86_64.run --helpРецепт ставит ровно то, что нужно для выгрузки хранилища:
--mode unattended --unattendedmodeui none
--enable-components client_full,ru
--disable-components client_thin,client_thin_fib,v8_install_deps,desktop_icons,
server,ws,server_admin,config_storage_server,
additional_admin_functions,liberica_jre,integrity_monitoring,<прочие языки>
client_full— толстый клиент с конфигуратором; это он и нужен.enкомпонентом не является: английский входит в поставку всегда, а попытка передатьen— ошибка установщика («Неверное значение "en"»).v8_install_depsотключён намеренно: зависимости уже поставлены пакетами дистрибутива, и пусть их ставитapt, а не установщик по своему списку.server/ws/config_storage_serverне нужны: файловое хранилище читает клиент.
Установка занимает около полуминуты и кладёт платформу в
/opt/1cv8/x86_64/8.3.27.2130 (около 2.4 ГБ).
Список получен ldd на установленном 1cv8, а не из документации.
Толстый клиент 8.3.27 собран на wxWidgets/GTK3 и тянет WebKitGTK 4.0
(не 4.1) вместе с libsoup 2.4:
libgtk-3-0 libwebkit2gtk-4.0-37 libjavascriptcoregtk-4.0-18 libsoup2.4-1
libcups2 libgl1 libglu1-mesa libsm6 libx11-6 libxxf86vm1 libgomp1
fontconfig fonts-dejavu-core iproute2 locales
Это прямо определяет базу образа: libwebkit2gtk-4.0-37 есть в Debian 12
(bookworm) и уже отсутствует в Debian 13 и Ubuntu 24.04. Поэтому база
образа ядра закреплена на bookworm — при переезде на новую базу этот пункт
надо решать заново, а не «поднимать версию».
iproute2 здесь не для красоты: платформа перечисляет сетевые интерфейсы
вызовом /sbin/ip, и без пакета каждый запуск конфигуратора сыплет в stderr
/bin/sh: 1: /sbin/ip: not found.
Сборка намеренно падает, если после установки нет исполняемого файла
/opt/1cv8/x86_64/<версия>/1cv8 или если ldd показывает недостающие
библиотеки.
Конфигуратор не работает без X-сервера даже в пакетном режиме:
$ 1cv8 CREATEINFOBASE File=/tmp/ib /DisableStartupDialogs /DisableStartupMessages
*** Unable to initialize GTK+ or connect to the windowing system. Is DISPLAY set properly?
код возврата 255
Под Xvfb та же команда даёт код 0 и настоящий файл базы. /DisableStartupDialogs
убирает диалоги, но зависимость от дисплея не снимает. Поэтому Xvfb в образе
обязателен.
Но запускать его через xvfb-run нельзя. xvfb-run держит команду
дочерним процессом (ему надо погасить X после завершения), и SIGTERM от
docker stop достаётся посреднику, а не работе. Замерено на этом образе:
контейнер останавливался мгновенно с кодом 143, обработчик в команде не
срабатывал ни разу — то есть stop_grace_period: 70m из docker/compose.yaml
не работал вообще, и синхронизация обрывалась бы на полуслове.
Поэтому в образе своя обёртка docker/xvfb-entrypoint.sh: она поднимает Xvfb
(номер дисплея выбирает сам Xvfb через -displayfd, без гонки за номер) и
запускает команду через exec. Посредника в цепочке не остаётся. После
исправления тот же опыт даёт код 0 и сработавший обработчик.
X-сокет с хоста пробрасывать не надо, Wine использовать нельзя — это не «нативный Linux».
{
"repository": "/repo",
"defaults": {
"backend": "native",
"v8_path": "/opt/1cv8/x86_64/8.3.27.2130/1cv8",
"jobs": 1,
"queue_limit": 1,
"storage_user": "gitsync",
"storage_password_file": "/run/secrets/storage_password",
"init": true
},
"storages": [
{ "name": "Конфигурация", "subtree": "Конфигурация",
"storage_path": "tcp://сервер-хранилища/база" },
{ "name": "Расширение печати", "subtree": "Расширение печати",
"extension": "РасширениеПечатныхФорм",
"storage_path": "tcp://сервер-хранилища/база-печать" },
{ "name": "Расширение обменов", "subtree": "Расширение обменов",
"extension": "РасширениеОбменов",
"storage_path": "tcp://сервер-хранилища/база-обмены" }
]
}Про схему «основная конфигурация + N расширений» подробно написано в
docs/monorepo.md. Коротко: один репозиторий, по подкаталогу на
источник, у каждого свои VERSION и AUTHORS, один .git на всех.
Начинайте с jobs: 1, queue_limit: 1. Значения по умолчанию (4/4) для первого
запуска слишком смелые: каждый поток — это отдельная временная база 1С.
Имя расширения в ключе extension — это имя в 1С, а не имя подкаталога.
Они могут не совпадать.
- Лучше всего — сервер хранилища (
tcp://…). Тогда файлы хранилища остаются там, где были, и раздавать файловый доступ не нужно. - Файловое хранилище подключается как папка (
/storage/main). Сетевые пути Windows (\\сервер\папка) внутри контейнера не работают: их надо сначала подключить на хосте, а уже потом отдавать контейнеру. - Подключать хранилище только для чтения — надо проверить. Сам инструмент хранилище не меняет: ни захвата версий, ни фиксации, ни привязки базы он не делает. Но платформа при файловом доступе может писать служебные файлы, и на Linux это не проверялось. Порядок действий: сделайте копию хранилища, подключите её только для чтения, посмотрите, проходит ли отчёт по версиям и выгрузка. Если не проходит — оставьте запись, но работайте на копии. Молча выдавать право записи на рабочее хранилище «чтобы проверить» нельзя.
Образ собран с LANG=C.UTF-8, имена файлов в UTF-8 — проверено, кириллические
имена каталогов выгрузки работают. Для платформы дополнительно ставится
fontconfig и базовый набор шрифтов: документация 1С называет их обязательными
для части операций с табличными документами, а отчёт по версиям хранилища —
это как раз табличный документ.
Пока не закрыты пункты 4–7 и 9, нельзя говорить, что выгрузка из хранилища 1С в Linux работает. Список — по порядку, с текущим состоянием.
- ✅ Сделано. Приватный образ собран из настоящего комплекта;
1cv8— линуксовый ELF x86-64,lddбез пропаж, кириллица и UTF-8 в порядке. - ⛔ Упёрлось в лицензию, и причина теперь названа точно. Контейнер
лицензии не получает: на первом обращении к хранилищу платформа отвечает
«Не найдена лицензия. Не обнаружен ключ защиты программы или полученная
программная лицензия!» (код 1). Разобрано в §10: файл лицензии платформа
читает из
/var/1C/licenses, а отвергает его по привязке к другому компьютеру («Ошибка привязки программной лицензии к компьютеру» в технологическом журнале). Пригодна только лицензия, полученная для этого окружения. Вопросы «хватает ли лицензии приjobs: 2» и «не слетает ли она при пересоздании контейнера» остаются открытыми — до них дело не дошло. - ✅ Сделано.
CREATEINFOBASEпод Xvfb проходит с кодом 0 и создаёт настоящий файл базы (1Cv8.1CD). Лицензия для этого не требуется. - ⛔ Отчёт по версиям настоящего хранилища — не получен (лицензия).
- ⛔ Выгрузка версии
ConfigurationRepositoryDumpCfg -v N→LoadCfg→DumpConfigToFiles— не выполнялась (лицензия). Это по-прежнему три отдельных запуска, объединять их нельзя. - ⛔ Прогон основной конфигурации и двух разных расширений на настоящих хранилищах — не выполнялся (лицензия). Конвейер до платформы доведён: CLI дошёл до запуска конфигуратора по всем трём источникам и честно остановился на отказе, не тронув соседей и не сделав ни одного повтора.
- ⛔ Сверка с независимой ручной выгрузкой — не выполнялась (лицензия).
- ✅ Сделано, и нашлась поломка.
docker stopчерезxvfb-runдо работы не доходил (код 143, обработчик не срабатывал). Исправлено собственной обёрткой сexec; после исправления сигнал доходит, код 0. Xvfb нужен — без него конфигуратор не стартует вовсе. - ⛔ Сравнение с прогоном на Windows той же версии — не выполнялось (лицензия).
Пункты 4–7 и 9 нельзя закрыть ничем, кроме настоящей лицензии. Учебная фикстура их не заменяет и не может.
- Пошаговое руководство по установке — установка без контейнеров, разбор неполадок, запуск по расписанию.
- Один репозиторий базы — раскладка «конфигурация + расширения».
- Матрица совместимости — что перенесено из оригинального gitsync, а что нет.
docker/Dockerfile.core,docker/Dockerfile.native,docker/compose.yaml,docker/entrypoint.sh— в них много поясняющих комментариев по делу.