Easy Авторский

Dev Containers и devcontainer.json

Стандартизация dev-окружений через контейнеры: как это работает

Первоисточник

Development Containers Specification

Microsoft, 2022

Проблема «у меня всё работает»

У каждого инженера есть этот разговор в памяти: кто-то из команды открывает пулл-реквест, CI падает, а автор разводит руками — «у меня локально всё работает». Дальше выясняется, что у него Node.js 18, а в CI — Node.js 20, или что у него в системе стоит libvips определённой версии, которую он поставил полгода назад для другого проекта и забыл об этом. Эта проблема — расхождение между локальным окружением разработчика и средой, в которой код запускается — стара как сама профессия, и контейнеры должны были её решить.

Docker решил проблему для продакшена: вы описываете образ в Dockerfile, собираете его, и он работает одинаково везде. Но для разработки Docker в чистом виде подходит плохо, потому что разработчику нужны вещи, которые продакшен-контейнеру не нужны: редактор с автодополнением и навигацией по коду, дебаггер, линтер, git-хуки, SSH-ключи, шрифты для терминала, расширения IDE. И когда каждый разработчик начинает лепить свою обвязку вокруг Docker Compose — с маунтами томов, пробросом портов, кастомными скриптами запуска — вся идея стандартизации разрушается.

Что такое Dev Containers

Dev Containers — это открытая спецификация, которая описывает, как использовать контейнер в качестве полноценного окружения для разработки. Спецификацию изначально создал Microsoft для VS Code, но к 2025 году её поддерживают JetBrains IDE, GitHub Codespaces, DevPod, Gitpod и другие инструменты.

Ключевой файл — devcontainer.json, который вы кладёте в директорию .devcontainer в корне репозитория. Этот файл описывает всё, что нужно для работы: базовый образ контейнера, установленные инструменты, расширения редактора, переменные окружения, проброшенные порты, скрипты инициализации. Когда разработчик открывает репозиторий в поддерживающем инструменте, тот читает devcontainer.json и создаёт контейнер с полностью настроенным окружением — без ручной установки зависимостей, без README с двадцатью шагами, без «а у тебя какая версия Python?».

Анатомия devcontainer.json

Минимальный devcontainer.json выглядит так:

{
  "name": "My Project",
  "image": "mcr.microsoft.com/devcontainers/typescript-node:20",
  "customizations": {
    "vscode": {
      "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
    }
  },
  "postCreateCommand": "npm install",
  "forwardPorts": [3000]
}

Пять строк конфигурации, и каждый новый разработчик получает одинаковое окружение: Node.js 20, TypeScript, ESLint, Prettier, установленные зависимости и проброшенный порт для dev-сервера. Вместо image можно указать dockerFile или dockerComposeFile для более сложных сценариев — когда вам нужна база данных, Redis, или другие сервисы рядом с основным контейнером.

Спецификация поддерживает lifecycle commands — хуки, которые запускаются на разных этапах жизни контейнера: initializeCommand (до создания контейнера, на хосте), onCreateCommand (после создания), postCreateCommand (после создания и клонирования репозитория), postStartCommand (каждый раз при запуске), postAttachCommand (каждый раз при подключении). Эти хуки позволяют автоматизировать весь процесс настройки: от миграции базы данных до генерации SSL-сертификатов для локального HTTPS.

Features: модульность вместо монолитных образов

Одно из самых удачных решений в спецификации Dev Containers — механизм Features. Features — это переиспользуемые модули, каждый из которых добавляет к контейнеру один инструмент или возможность. Вместо того чтобы собирать монолитный Dockerfile с десятками RUN apt-get install, вы перечисляете нужные features в devcontainer.json:

{
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "features": {
    "ghcr.io/devcontainers/features/node:1": { "version": "20" },
    "ghcr.io/devcontainers/features/python:1": { "version": "3.12" },
    "ghcr.io/devcontainers/features/docker-in-docker:2": {},
    "ghcr.io/devcontainers/features/github-cli:1": {}
  }
}

Каждый feature — это OCI-артефакт с установочным скриптом и метаданными. Microsoft поддерживает коллекцию из десятков готовых features, а вы можете создавать свои и публиковать их в container registry вашей компании. Это решает проблему, с которой сталкиваются большие организации: у вас может быть стандартный набор внутренних инструментов (CLI для деплоя, конфигурация VPN, корпоративные сертификаты), который вы оформляете как feature и подключаете ко всем репозиториям.

Поддержка IDE

Когда Microsoft создавал спецификацию Dev Containers, главная цель была интеграция с VS Code — и она работает лучше всего. VS Code обнаруживает devcontainer.json в репозитории, предлагает открыть проект в контейнере, и через пару минут вы работаете в полноценной IDE, где все расширения, линтеры и дебаггеры настроены и подключены к контейнерному окружению. Редактор при этом работает на хосте (или в браузере), а все процессы — внутри контейнера.

JetBrains добавил поддержку Dev Containers в свои IDE в 2024 году. Реализация пока менее зрелая, чем у VS Code, — есть ограничения по поддержке некоторых свойств спецификации и features, — но она активно развивается, и для большинства сценариев уже работает.

Trade-offs

Dev Containers решают проблему воспроизводимости окружения, но приносят с собой свои сложности.

Производительность файловой системы — главная боль на macOS. Docker на Mac использует виртуализацию, и маунт файловой системы хоста в контейнер работает заметно медленнее, чем нативный доступ. Для проектов с тысячами файлов (типичный node_modules) это может превратить npm install или запуск тестов в мучение. Частичные решения существуют — named volumes для node_modules, VirtioFS в новых версиях Docker Desktop — но проблема до конца не ушла.

Кривая обучения — разработчикам, которые никогда не работали с Docker, придётся разбираться с новыми концепциями: образы, контейнеры, volumes, сеть. Для команды, где все и так работают с контейнерами, это не проблема; для фронтенд-команды, которая до сих пор разрабатывала на голом macOS, — серьёзный порог.

Отладка проблем — когда что-то ломается внутри dev container (а ломается всегда что-нибудь), дебаг становится сложнее: нужно разбираться, где проблема — в образе, в features, в конфигурации маунтов, в сети контейнера. Это требует знаний, которых у среднего разработчика может не быть.

Рекомендации

Если вы думаете о внедрении Dev Containers в своей команде, начните с одного репозитория — желательно того, у которого самый сложный процесс настройки окружения (обычно это и есть главный кандидат). Создайте devcontainer.json, протестируйте с новым сотрудником или коллегой из другой команды, соберите обратную связь. Не пытайтесь покрыть все edge cases сразу — начните с базового образа и постепенно добавляйте features и скрипты по мере обнаружения проблем. Dev Containers — это не серебряная пуля, но для команд, где настройка окружения регулярно съедает дни инженерного времени, это один из самых эффективных инструментов стандартизации.