Как устроена организация образа Docker
Что проверяют
Понимаете ли вы, из чего физически состоит образ и почему от порядка инструкций в Dockerfile зависят скорость сборки и размер. Следом почти всегда просят набросать Dockerfile для ASP.NET Core и объяснить, зачем в нём две стадии.
Как отвечать
Образ это стопка слоёв только для чтения плюс конфигурация и манифест, который их перечисляет:
- слои: каждая
RUN,COPY,ADDдаёт новый слой, то есть архив изменений файловой системы. Слои адресуются хэшем содержимого, поэтому общий базовый слой хранится и скачивается один раз; - конфигурация:
ENTRYPOINT,CMD,ENV,WORKDIR,USER. Файлов эти инструкции не добавляют, только метаданные; - манифест и тег: тег вроде
aspnet:8.0это подвижная ссылка, неизменяемая ссылка это digestsha256:.... Мультиархитектурный образ хранит список манифестов под разные платформы; - контейнер: поверх слоёв образа накладывается тонкий записываемый слой через
overlay2. Удалили контейнер, пропал и он.
Для .NET это выливается в многоэтапную сборку. Стадия на образе dotnet/sdk сначала копирует только *.csproj и делает dotnet restore, затем копирует исходники и выполняет dotnet publish. Финальная стадия на dotnet/aspnet забирает результат через COPY --from=build, и SDK в итоговый образ не попадает. Копировать csproj отдельно от кода нужно ради кэша: пока зависимости не менялись, слой с restore берётся готовым.
Плюсом будет знать, что официальные образы начиная с .NET 8 слушают порт 8080, а не 80, и содержат непривилегированного пользователя app, которого включают строкой USER app.
Где спотыкаются
Думают, что RUN rm уменьшает образ. Файл остаётся в предыдущем слое: удалять надо в той же инструкции, где он появился, или не тащить его в финальную стадию вовсе.
Пишут COPY . . первой строкой. Любая правка кода сбрасывает кэш, restore гоняется на каждой сборке, а без .dockerignore в образ уезжают ещё bin, obj и .git.
Путают образ и контейнер. Образ неизменяем, все изменения во время работы живут только в записываемом слое конкретного контейнера.