При разработке Spring Boot-приложений в Docker часто возникает необходимость подключить отладчик из IDE. Это позволяет ставить точки останова, анализировать выполнение кода и быстрее находить ошибки.
Но здесь есть важный нюанс. Отладка должна работать только в среде разработки. В production debug-порт необходимо полностью отключать.
Разберём простой и безопасный способ организации удалённой отладки через Docker Compose и NetBeans.
Почему не стоит включать Debug в Dockerfile
Многие разработчики добавляют параметры JDWP прямо в Dockerfile. На первый взгляд это удобно: контейнер всегда готов к подключению отладчика.
Однако такой подход имеет недостаток. Если тот же образ попадёт в production, вместе с ним будет запущен и debug-порт.
Гораздо правильнее использовать разные конфигурации для разных сред:
- в разработке отладка включена;
- в production полностью отключена.
Поэтому Dockerfile должен оставаться универсальным.
Dockerfile без настроек отладки
Базовый Dockerfile выглядит максимально просто:
FROM eclipse-temurin:21-jdk
WORKDIR /app
COPY build/libs/app-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
Здесь нет никаких параметров JDWP. Контейнер просто запускает Spring Boot приложение.
Основной Docker Compose для Production
Создаём стандартный файл docker-compose.yml:
services:
app:
build: .
ports:
- "8080:8080"
В этом режиме наружу публикуется только HTTP-порт приложения.
Никакой удалённой отладки нет.
Отдельная конфигурация для разработки
Теперь создадим файл docker-compose.dev.yml, который будет использоваться только локально.
services:
app:
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_TOOL_OPTIONS: >
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
Переменная JAVA_TOOL_OPTIONS передаёт JVM параметры запуска и включает JDWP-сервер на порту 5005.
Именно к этому порту будет подключаться NetBeans.
Запуск в режиме разработки
Для локальной работы запускаем сразу два compose-файла:
docker compose \
-f docker-compose.yml \
-f docker-compose.dev.yml \
up -d --build
После старта контейнера JVM начнёт слушать порт 5005 и будет готова принимать подключения отладчика.
Запуск в Production
На сервере используется только основной compose-файл:
docker compose -f docker-compose.yml up -d
Поскольку dev-конфигурация не подключается, порт отладки отсутствует.
Это именно то поведение, которое требуется в production.
Подключаем NetBeans
Откройте меню:
Debug → Attach Debugger
В настройках укажите:
- Debugger: JPDA Debugger
- Host: localhost
- Port: 5005
После нажатия кнопки Attach IDE подключится к JVM внутри контейнера.
Теперь можно использовать breakpoints так же, как при обычном запуске приложения.
Проверяем, что всё работает
Самый простой способ проверки:
- Поставить breakpoint в коде.
- Отправить HTTP-запрос в приложение.
- Убедиться, что выполнение остановилось на точке останова.
Если код остановился в нужном месте, значит удалённая отладка настроена корректно.
Проверка через логи контейнера
Также полезно проверить логи:
docker logs <container>
Если JDWP успешно запустился, вы увидите сообщение:
Listening for transport dt_socket at address: 5005
Это означает, что JVM ожидает подключения отладчика.
Что означают suspend=n и suspend=y
Параметр suspend определяет поведение JVM при старте.
suspend=n
В этом режиме приложение запускается сразу.
Даже если отладчик ещё не подключён, Spring Boot начнёт работу как обычно.
Такой вариант используется чаще всего.
suspend=y
В этом режиме JVM останавливается на старте и ждёт подключения отладчика.
Приложение не начнёт запускаться, пока IDE не подключится к процессу.
Этот режим полезен, если необходимо исследовать:
- проблемы во время запуска приложения;
- ошибки создания Spring Bean;
- исключения, возникающие на ранних этапах инициализации.
Пример настройки:
JAVA_TOOL_OPTIONS: >
-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005
Если отладчик не подключается
Чаще всего проблема связана с одной из трёх причин:
- порт 5005 не проброшен наружу;
- контейнер не был пересобран;
- используется старый Docker-образ.
Проверьте список контейнеров:
docker ps
В выводе должна присутствовать строка примерно такого вида:
0.0.0.0:5005->5005/tcp
Если её нет, значит порт не опубликован.
Если breakpoint не срабатывает
Обычно это происходит тогда, когда код в IDE отличается от кода, который находится внутри JAR-файла.
Также причиной может быть запуск старой версии контейнера.
В большинстве случаев помогает полная пересборка:
docker compose down
docker compose up -d --build
После пересборки точки останова начинают работать корректно.
Почему нельзя держать Debug включённым в Production
JDWP открывает специальный управляющий порт JVM.
Через него можно подключаться к процессу и выполнять удалённую отладку.
Если такой порт случайно окажется доступен извне, это создаёт серьёзный риск безопасности.
Поэтому хорошей практикой считается:
- не публиковать порт 5005 наружу;
- не включать JDWP постоянно;
- хранить настройки отладки только в dev-конфигурации.
Вывод
Безопасная схема выглядит очень просто.
Dockerfile содержит только запуск приложения и ничего не знает об отладке.
Основной файл docker-compose.yml используется для production и не открывает debug-порт.
Файл docker-compose.dev.yml подключается только во время разработки и включает JDWP на порту 5005.
В результате разработчик получает удобную удалённую отладку через NetBeans, а production остаётся защищённым и не содержит лишних открытых сервисов.