惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Как проверять CI-джобы локально с помощью Docker
EvgeneKopylo · 2026-04-28 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение6 мин

Охват и читатели10

Туториал

Каждый, кто активно пользуется GitLab CI/CD, знаком с ситуацией: вы пушите изменения, ждёте минуту-другую, а пайплайн падает из-за мелкой ошибки или отсутствующей зависимости. Исправляете, снова пушите, снова ждёте… Цикл может быть утомительным.

Но ведь GitLab Runner сам запускает ваши джобы в Docker-контейнерах. Почему бы не сделать то же самое локально? Это сокращает время обратной связи с нескольких минут до десятков секунд.

В этой статье я покажу, как в точности воспроизвести выполнение любой CI-джобы у себя на машине, отладить её и только потом отправлять изменения в репозиторий.

Содержание

Что нам понадобится

  • Установленный Docker

  • Ваш проект, для которого настроен .gitlab-ci.yml

  • Терминал и немного свободного времени

Общая идея

Каждая джоба в .gitlab-ci.yml содержит:

  • image — Docker-образ, в котором она выполняется

  • before_script и script — команды, которые нужно запустить

  • variables — переменные окружения

  • cache — кеши (обычно можно игнорировать при локальном запуске)

Задача: запустить контейнер, дать ему доступ к вашему репозиторию (без копирования файлов), выполнить те же команды, что и в CI, и дать ему после этого бесследно исчезнуть.

Благодаря bind mount (-v "$(pwd):/app") контейнер видит указанную локальную папку как /app внутри себя.

Любые изменения, сделанные контейнером, произойдут на хосте в этой папке. Сам контейнер после выполнения (--rm) уничтожается полностью – никаких «следов» в Docker.

Кстати, подход можно использовать для билда проектов со сложной настройкой среды, но сейчас не об этом.

Базовый шаблон команды:

docker run --rm -it \
  -v "$(pwd):/app" \
  -w /app \
  <image> \
  sh -c "команда1 && команда2 && ..."
  • --rm — контейнер удалится после выхода, не засоряя систему

  • -it — интерактивный режим, вы видите вывод и можете прервать выполнение

  • -v "$(pwd):/app" — монтирование текущей папки (корня репозитория) в /app внутри контейнера

  • -w /app — сделать /app рабочей директорией

  • sh -c "..." — запустить оболочку и выполнить указанные команды

Примеры

🐍 Python-проект с uv (линтер ruff)

Предположим, в .gitlab-ci.yml есть такая джоба:

ruff:
  stage: lint
  image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
  script:
    - uv sync --frozen
    - uv run ruff check src tests

Чтобы запустить её локально, просто повторим те же команды:

docker run --rm -it \
  -v "$(pwd):/app" \
  -w /app \
  ghcr.io/astral-sh/uv:python3.12-bookworm-slim \
  sh -c "uv sync --frozen && uv run ruff check src tests"

Если в образе не хватает git (может понадобиться для зависимостей, устанавливаемых из git-репозиториев), добавим его установку:

docker run --rm -it \
  -v "$(pwd):/app" \
  -w /app \
  ghcr.io/astral-sh/uv:python3.12-bookworm-slim \
  sh -c "apt-get update && apt-get install -y git && uv sync --frozen && uv run ruff check src tests"

Обратите внимание: образ bookworm-slim уже содержит компиляторы (gcc, g++), поэтому сборка пакетов с C-расширениями (например, scikit-learn, chonkie) пройдёт без ошибок.

🟢 Node.js-проект (TypeScript)

Для Node.js часто используется образ node:24-alpine. Он лёгкий, но в нём нет bash (только sh), что обычно не мешает.

frontend-typecheck:
  stage: lint
  image: node:24-alpine
  script:
    - cd front
    - npm ci --prefer-offline
    - npx tsc --noEmit --skipLibCheck

Локальная команда:

docker run --rm -it \
  -v "$(pwd):/app" \
  -w /app \
  node:24-alpine \
  sh -c "cd front && npm ci --prefer-offline && npx tsc --noEmit --skipLibCheck"

🦀 Rust-проект с компиляцией в WASM

Более сложный случай — джоба, которая собирает Rust-крейт в WebAssembly. Здесь образ тяжёлый (rust:latest), а одной из команд требуется установка wasm-pack:

build_wasm:
  stage: build-wasm
  image: rust:latest
  script:
    - cargo install wasm-pack
    - wasm-pack build wasm-lib --target web --out-dir pkg --out-name wasm_lib

Этот пример интересен тем, что cargo install wasm-pack компилирует wasm-pack из исходников — 5 минут. Но wasm-pack распространяется и в виде готовых бинарников.

Локально разумнее заменить эту строку на скачивание:

docker run --rm -it \
  -v "$(pwd):/app" \
  -w /app \
  rust:latest \
  sh -c "git config --global --add safe.directory /app && \
    curl -fsSL https://github.com/wasm-bindgen/wasm-pack/releases/download/v0.14.0/wasm-pack-v0.14.0-x86_64-unknown-linux-musl.tar.gz \
      | tar -xz -C /usr/local/bin --strip-components=1 && \
    wasm-pack build wasm-lib --target web --out-dir pkg --out-name wasm_lib"

Установка занимает 30 вместо 300 секунд.

Типичные проблемы и их решение

1. Отсутствует git

Ошибка: git: not found

Причина: В минималистичных образах (Alpine, slim) git не установлен.

Решение:

  • Для Alpine: apk add --no-cache git

  • Для Debian/Ubuntu: apt-get update && apt-get install -y git

2. Отсутствуют компиляторы (gcc, g++)

Ошибка: error: command 'gcc' failed: No such file or directory

Причина: Некоторые Python-пакеты (например, scikit-learn, chonkie) требуют компиляции C/C++ кода.

Решение: Используйте образы, где компиляторы уже есть, например ghcr.io/astral-sh/uv:python3.12-bookworm-slim (на базе Debian). Если вы вынуждены использовать Alpine, добавьте apk add gcc g++ musl-dev python3-dev.

3. fatal: detected dubious ownership in repository

Ошибка: Git внутри контейнера отказывается работать с папкой, владельцем которой на хосте является ваш пользователь.

Решение: Выполните в контейнере (до git-команд):

git config --global --add safe.directory /app

В командной строке это можно встроить так:

sh -c "git config --global --add safe.directory /app && ..."

Оптимизация: выбирайте правильные образы

Один из главных выводов, который я сделал за время экспериментов:

  • Alpine-образы маленькие, но часто требуют ручной установки git, gcc, musl-dev, python3-dev и т.д. Это замедляет запуск джобы и создаёт лишние точки отказа.

  • Debian-образы чуть больше, но уже содержат компиляторы и большинство утилит. Достаточно добавить только git (и то не всегда).

Для Python-проектов я рекомендую ghcr.io/astral-sh/uv:python3.12-bookworm-slim – он отлично подходит и для CI, и для локальных запусков. Для Node.js node:24-alpine обычно достаточен, но если ваш проект собирает нативные аддоны, рассмотрите node:24 (на Debian).

Оптимизация: заменяйте сборку из исходников на готовые бинарники

Пример с wasm-pack выше — не частный случай. Если CI-джоба устанавливает тяжелые инструменты, проверьте, не распространяется ли он в виде готового бинарника.

Интерактивная отладка

Иногда проще войти внутрь контейнера и выполнять команды по одной, чтобы понять, на каком шаге всё ломается.

docker run --rm -it -v "$(pwd):/app" -w /app ghcr.io/astral-sh/uv:python3.12-bookworm-slim bash

Теперь вы можете вручную прописывать cd front, npm ci, npx tsc и смотреть на вывод. Выйти – exit.

Переменные окружения

Если в CI используются пользовательские переменные (например, $GITHUB_TOKEN), передайте их через флаг -e:

export GITHUB_TOKEN="ваш_токен"
docker run ... -e GITHUB_TOKEN=$GITHUB_TOKEN ...

Заключение

Локальный запуск CI-джоб через Docker – простой эффективный способ ускорить разработку. Вы получаете мгновенную обратную связь, не засоряете историю коммитов и можете отлаживать сложные сценарии сборки в изолированной среде.

Достаточно один раз запомнить шаблон команды:

docker run --rm -it -v "$(pwd):/app" -w /app <image> sh -c "команды"

И дополнять его установкой недостающих пакетов (git, компиляторы) при необходимости. Попробуйте – и вы заметите, насколько быстрее станет работа с CI/CD.

📋 SKILL.md самая мякатка

---
name: docker-ci-local-run
description: Запуск CI-джоб через Docker для отладки локально. Использовать, если пользователь попросил написать, проверить либо отладить CI-джобу. Агент должен оценить образ и команды, предложить оптимизации (замена Alpine на bookworm-slim, бинарник вместо cargo install), но продолжать выполнение.
---

# ИНСТРУКЦИЯ: Локальная проверка CI-джобы через Docker

## 1. Взять из `.gitlab-ci.yml`

- `image` (образ)
- `before_script` и `script` (все команды)
- переменные окружения (если есть)

## 2. Собрать команду

```bash
# прогон
docker run --rm \
  -v "$(pwd):/app" -w /app \
  -e VAR1=value1 -e VAR2=value2 \
  <IMAGE> sh -c "установка_пакетов && git safe.directory && команды"
```

```bash
# отладка
docker run --rm -it -v "$(pwd):/app" -w /app <IMAGE> sh
```

## 3. Установка пакетов (в начало `sh -c`)

| Образ | Команда |
|-------|---------|
| Debian/Ubuntu | `apt-get update && apt-get install -y git` |
| Alpine | `apk add --no-cache git gcc g++ musl-dev python3-dev` |

## 4. Типовые ошибки

| Ошибка | Исправление |
|--------|-------------|
| `git not found` | установить git (п.3) |
| `dubious ownership` | после git: `git config --global --add safe.directory /app &&` |
| `gcc/g++ not found` | добавить компиляторы (п.3) или взять `bookworm-slim` |
| `npm ci` без `package-lock.json` | заменить на `npm install` |
| `the input device is not a TTY` | убрать флаг `-t` (оставить `-i` или ничего) |

## 5. Успех

Код возврата `0` = джоба пройдена. Ошибки линтеров/типов считаются ошибками.

Как вы отлаживаете пайплайны? Делитесь своими приёмами в комментариях.