Как использовать переменные в ENTRYPOINT Dockerfile в формате JSON?
Приветствую! У меня есть несколько приложений, которые собираются по одинаковой структуре Dockerfile, но различаются только именем каталога при компиляции и именем исполняемого файла. Поэтому приходится использовать несколько Dockerfile.
Я хочу создать один параметризованный шаблонный Dockerfile. С именами каталогов проблем нет, но я столкнулся с трудностями на слое ENTRYPOINT.
Если ENTRYPOINT описан в формате JSON (exec), то переменная не подставляется:
ENTRYPOINT ["dotnet", "${APPLICATION}.dll"]
Если использовать shell-форму, то параметризация работает:
ENTRYPOINT ["sh","-c","exec dotnet ${APPLICATION}.dll"]
Но что делать, если принципиально важно, чтобы ENTRYPOINT оставался в формате JSON?
Я нашел временное решение через создание символической ссылки, но оно требует дополнительного слоя и выглядит не очень элегантно:
RUN ln -s "${APPLICATION}.dll" /app/app.dll
ENTRYPOINT ["dotnet","/app/app.dll"]
Вот пример моего Dockerfile (APP - имя сервиса):
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
ENTRYPOINT ["dotnet", "__APP__.dll"]
Как правильно использовать переменные в ENTRYPOINT Dockerfile в формате JSON без создания дополнительных слоев и символических ссылок?
Docker ENTRYPOINT в формате JSON не поддерживает подстановку переменных на этапе сборки, так как эта форма выполняет команду напрямую через exec(), а не через shell, где происходит обработка переменных. Однако есть несколько элегантных решений для параметризации ENTRYPOINT без создания дополнительных слоев.
Содержание
- Основная проблема JSON формата ENTRYPOINT
- Решение с использованием шаблонизации
- Решение через стартовый скрипт
- Решение с использованием ARG переменных
- Решение с помощью инструментов сборки
- Сравнение подходов
- Рекомендуемый подход
Основная проблема JSON формата ENTRYPOINT
Как объясняется в документации Docker, когда ENTRYPOINT указан в формате JSON (exec form), команда выполняется напрямую через exec(), а не через shell. Это означает, что переменные окружения не подставляются на этапе сборки:
# ❌ Не работает - переменная не подстанавливается
ENTRYPOINT ["dotnet", "${APPLICATION}.dll"]
В отличие от shell-формы, где переменные обрабатываются:
# ✅ Работает, но не JSON формат
ENTRYPOINT ["sh","-c","exec dotnet ${APPLICATION}.dll"]
Согласно Joe Yates’ блогу, это важное ограничение JSON формата, которое часто вызывает путаницу.
Решение с использованием шаблонизации
Самый чистый способ - использовать шаблонизацию на этапе сборки. Создайте шаблон ENTRYPOINT и замените переменные во время сборки:
Шаг 1: Создайте шаблон entrypoint.template
#!/bin/sh
exec dotnet ${APPLICATION}.dll
Шаг 2: В Dockerfile используйте шаблон
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
# Создаем шаблон с переменными
RUN echo "#!/bin/sh\nexec dotnet \${APPLICATION}.dll" > /entrypoint.template
# Заменяем переменные и создаем исполняемый скрипт
RUN sed -i "s/__APP__/${APPLICATION}/g" /entrypoint.template && \
chmod +x /entrypoint.template
# Используем скрипт в ENTRYPOINT JSON формате
ENTRYPOINT ["/entrypoint.template"]
Этот подход позволяет сохранить JSON формат ENTRYPOINT и избегать символических ссылок.
Решение через стартовый скрипт
Создайте универсальный стартовый скрипт, который может работать с разными приложениями:
Стартовый скрипт start.sh:
#!/bin/sh
# Определяем имя приложения из переменной или по умолчанию
APP_NAME=${APPLICATION:-app}
# Запускаем приложение
exec dotnet "${APP_NAME}.dll"
Dockerfile:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
COPY start.sh /app/
# Устанавливаем переменную приложения
ARG APPLICATION=app
ENV APPLICATION=${APPLICATION}
# Делаем скрипт исполняемым и используем его
RUN chmod +x /app/start.sh
ENTRYPOINT ["/app/start.sh"]
Этот подход очень гибкий и позволяет легко добавлять новую логику запуска.
Решение с использованием ARG переменных
Хотя ARG переменные не подставляются напрямую в ENTRYPOINT JSON, можно использовать их для создания динамического Dockerfile:
ARG APPLICATION=app
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd ${APPLICATION} && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/${APPLICATION}/publish .
# Создаем файл с правильным именем приложения
RUN echo "${APPLICATION}.dll" > /app/appname.txt
# Используем скрипт для чтения имени файла
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
APP_NAME=$(cat /app/appname.txt)
exec dotnet "${APP_NAME}"
Решение с помощью инструментов сборки
Используйте инструменты сборки для генерации Dockerfile:
С помощью Makefile:
build:
@sed "s/__APP__/$(APPLICATION)/g" Dockerfile.template > Dockerfile
docker build -t myapp:$(APPLICATION) .
run:
docker run -e APPLICATION=$(APPLICATION) myapp:$(APPLICATION)
Dockerfile.template:
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd __APP__ && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/__APP__/publish .
ENTRYPOINT ["dotnet", "__APP__.dll"]
Сравнение подходов
| Подход | Преимущества | Недостатки |
|---|---|---|
| Шаблонизация | Чистое решение, сохраняет JSON формат | Требует дополнительных шагов сборки |
| Стартовый скрипт | Гибкий, легко расширяемый | Может добавлять небольшой оверхед |
| ARG + файл | Использует стандартные Docker механизмы | Требует создания дополнительного файла |
| Инструменты сборки | Максимальная гибкость | Требует внешних инструментов |
Рекомендуемый подход
Для большинства случаев я рекомендую решение через стартовый скрипт, так как оно:
- Сохраняет JSON формат ENTRYPOINT
- Не требует дополнительных слоев Docker
- Легко поддерживается и расширяется
- Работает с любыми переменными окружения
- Позволяет добавлять логику обработки ошибок, логирования и т.д.
Пример финального Dockerfile:
ARG APPLICATION=app
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build-env
WORKDIR /app
ENV DOTNET_NUGET_SIGNATURE_VERIFICATION=false
COPY . .
RUN cd ${APPLICATION} && dotnet publish -c Release -o publish /p:DebugType=None /p:DebugSymbols=false
FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app
COPY --from=build-env /app/${APPLICATION}/publish .
COPY start.sh /app/
# Устанавливаем переменную приложения
ENV APPLICATION=${APPLICATION}
# Делаем скрипт исполняемым и используем его
RUN chmod +x /app/start.sh
ENTRYPOINT ["/app/start.sh"]
#!/bin/sh
# Обрабатываем переменные окружения
APP_NAME=${APPLICATION:-app}
# Добавляем логирование
echo "Starting application: $APP_NAME"
# Запускаем приложение
exec dotnet "${APP_NAME}.dll"
Этот подход решает вашу проблему элегантно и без создания дополнительных слоев или символических ссылок.
Источники
- Dockerfile reference | Docker Docs
- Docker ENTRYPOINT, CMD and
runArguments - Joe Yates’ Blog - Integrating Docker Environment Variables in ENTRYPOINT Array | Baeldung on Ops
- How do I use Docker environment variable in ENTRYPOINT array? - Stack Overflow
- JSONArgsRecommended | Docker Docs
- Docker ARG, ENV and .env - a Complete Guide · vsupalov.com