--- title: MSSQL контейнер с восстановленными production data — паттерн type: concept tags: [mssql, docker, recovery, named-volume, chown] sources: [../sources/nas-recovery-session-2026-05-18.md] updated: 2026-05-19 --- # MSSQL container с production data ## Проблема Восстановить MSSQL базу в Docker-контейнере на Windows-хосте из: - Готовых `.mdf/.ldf` файлов production (взяты из tar `/var/opt/mssql/` source-контейнера) - + 5 user-databases + системные (master/model/msdb/tempdb) ## Что НЕ работает: bind-mount production data ```yaml volumes: - ./production-data/mssql:/var/opt/mssql ``` **Не работает** на Windows Docker Desktop. MSSQL-контейнер требует: - `master.mdf` owned by `mssql` user (UID 10001) или root - `chmod` на файлах внутри `/var/opt/mssql/log/` (логи, .xel, .trc) На Windows DD bind-mount через WSL2-слой не позволяет: - Файлы видятся как owned by root or other UID — MSSQL отказывается: `Your master database file is owned by root.` - `chmod` внутри контейнера фейлится: `Operation not permitted` Симптом: MSSQL стартует, в логах ругается на chmod, потом стартует ещё раз и зависает. ## Что работает: named volume + chown через temp container Идея: создать named volume, скопировать данные внутрь с правильным chown, потом примонтировать к MSSQL. ```yaml # docker-compose.yml — финальная версия services: mssql: image: mcr.microsoft.com/mssql/server:2019-latest container_name: mssql environment: ACCEPT_EULA: "Y" MSSQL_SA_PASSWORD: ${SA_PASSWORD} MSSQL_PID: Developer ports: - "1433:1433" volumes: - mssql_data:/var/opt/mssql networks: - proxy restart: unless-stopped volumes: mssql_data: networks: proxy: external: true ``` Заполнение volume (один раз перед `docker compose up -d`): ```powershell docker volume create mssql_mssql_data docker run --rm \ -v mssql_mssql_data:/dest \ -v /mssql:/src:ro \ alpine cp -a /src/. /dest/ docker run --rm -v mssql_mssql_data:/dest \ alpine chown -R 10001:0 /dest docker compose up -d ``` После этого MSSQL стартует с production data, делает crash recovery в master.mdf, поднимается за 30-60 секунд (если CU контейнера == CU source) или 5-30 минут (если CU source старше — script upgrade mode). ## sqlcmd "$( var-substitution" pitfall Альтернативный путь — restore из `.sql` script (export через "Generate Scripts" в SSMS). Файл 547 MB с CREATE+INSERT'ами всей БД. **Не запустить через `sqlcmd -i file.sql`** без флага `-x`. Потому что: - В данных встречаются jQuery JS-сниппеты типа `INSERT ... VALUES (N'$(function(){ ... })')` - sqlcmd по умолчанию интерпретирует `$(varname)` как переменную для подстановки. - На `$(function(){` парсер ломается → "Syntax error near command '('" в середине файла. Фикс: `sqlcmd -x` (disable variable substitution). ``` docker exec mssql /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P '' -C -x \ -i /var/opt/mssql/backup/MoreThenCms.sql ``` ## SA-аккаунт disabled в production Production SQL Server конфигурации часто **отключают `sa`** (security best practice). Пароль может быть прав, но account disabled → `Login failed for user 'sa'. Reason: The account is disabled.` Фикс: **`mssql-conf set-sa-password`** в offline-mode (server stopped) под root: ``` # Stop running container docker compose stop # Run offline mssql-conf in temp container с тем же volume docker run --rm --user 0:0 \ -e ACCEPT_EULA=Y \ -e MSSQL_SA_PASSWORD='' \ -v mssql_mssql_data:/var/opt/mssql \ mcr.microsoft.com/mssql/server:2019-latest \ /opt/mssql/bin/mssql-conf set-sa-password # Запуск нормального container docker compose start ``` `mssql-conf set-sa-password` сбрасывает пароль И **enables sa**. Если env-pw совпадает с тем что ожидает приложение — приложение продолжает работать. ## Production passwords reuse Замеченный паттерн: у пользователя один пароль `fXkH4@8O%3pc` используется как: - SA password (MSSQL) - Connection string password для user `snolla` (CMS DB user) - Возможно где-то ещё Лучше: rotate after recovery, разделить. ## CU mismatch warning Если master.mdf был из старой CU (например 2019 CU14), а контейнер `2019-latest` (например CU32-GDR), MSSQL после старта войдёт в **script upgrade mode** на 10-30 минут: ``` Server is in script upgrade mode. Only administrator can connect at this time. ``` Видно в логах сотни строк `spid9s ... Deleting AlwaysOnAgReplicas...` — это нормальный msdb-upgrade. Подождать. Один раз. После завершения — никаких задержек на следующих стартах. ## Healthcheck path для 2019-latest image В современных image SQL Server инструменты лежат в `/opt/mssql-tools18/bin/sqlcmd` (с TLS-флагом `-C`), а не `/opt/mssql-tools/bin/sqlcmd`: ```yaml healthcheck: test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' -b -o /dev/null || exit 1"] ``` Связано: [[snolla-recovery-vm]] (Web.config указывает на `10.0.2.2:1433` = host из VM-NAT), [[windows-recovery-host]].