Сайт на nginx:alpine
CMD наследуется от основы: nginx уже настроен работать на переднем
плане. Отдаёт он файлы из /usr/share/nginx/html.
Dockerfile — рецепт образа: основа, файлы, команды сборки и запуска; docker build собирает по нему образ.
sudo docker build -t app:1.1 .
собрать из ./Dockerfile
sudo docker build -t app:1.1 -f Dockerfile.dev .
другой файл
sudo docker build --no-cache -t app:1.1 .
всё заново, без кэша
sudo docker history app:1.1
слои образа
sudo docker image inspect -f '{{json .Config.Cmd}}' app:1.1
sudo docker run --rm app:1.1 ls /www
что попало в образ
Dockerfile — текстовый файл, по которому
docker build собирает образ. Каждая строка —
инструкция: взять основу, скопировать файлы, выполнить команду, задать
команду запуска. Инструкции выполняются сверху вниз, каждая, что меняет
файлы, даёт слой.
-t app:1.1 даёт образу имя и тег, точка в конце —
каталог, откуда брать файлы. Строки сборки пронумерованы #1, #2…:
сначала служебные шаги [internal], потом инструкции по порядку —
[2/4] значит вторая из четырёх. Последняя строка шага — чем он кончился:
DONE и время или CACHED. CMD, EXPOSE и ENV своих шагов не
получают: они записывают настройки образа.
На машине тренажёра сборку ведёт BuildKit (компонент buildx, пакет
docker-buildx).
| Инструкция | Что делает | Слой с файлами |
|---|---|---|
FROM busybox |
основа: образ, поверх которого строим | слои основы |
WORKDIR /www |
рабочий каталог дальше и при запуске; создаёт его | да |
COPY index.html . |
файл из контекста сборки в образ | да |
RUN adduser -D web |
выполнить команду при сборке | да |
ENV MODE=prod |
переменная окружения при сборке и запуске | нет |
EXPOSE 80 |
пометка «служба слушает 80»; порт не публикует | нет |
USER web |
от кого идут RUN ниже и главный процесс |
нет |
VOLUME /data |
каталог для тома: без -v там будет новый безымянный том |
нет |
CMD ["httpd", "-f"] |
команда запуска по умолчанию | нет |
ENTRYPOINT ["echo"] |
неизменная часть команды запуска | нет |
FROM — первая инструкция. Основу Docker ищет среди образов на машине,
а если её нет — идёт в реестр. На машине тренажёра сети нет, поэтому
FROM — только из загруженных: alpine, nginx:alpine, busybox.
RUN выполняется при сборке, CMD — при каждом запуске контейнера.
RUN apk add … без сети падает: пакеты эта команда берёт из интернета.
EXPOSE ничего не открывает: порт на хосте публикует docker run -p
(«Порты»). ENV из образа можно переопределить при
запуске: docker run -e MODE=dev. VOLUME /data в образе значит, что
каждый docker run без -v подключит в /data новый пустой том, — см.
«Тома».
Контекст сборки — каталог, который docker build
отправляет службе Docker целиком: последний аргумент команды, обычно
точка. Сколько отправлено, говорит шаг [internal] load build context:
transferring context: 31B done.
COPY берёт файлы только из контекста. Путь в COPY считается от его
корня, выйти выше нельзя: COPY ../secret.txt . ищет /secret.txt
внутри контекста и падает:
Нужный файл лежит снаружи — скопируй его в каталог сборки или собирай из
каталога выше: docker build -f app/Dockerfile ..
Лишнее из контекста убирает .dockerignore рядом с Dockerfile — по
шаблону на строку:
Так в образ не попадут журналы и секреты, а сборка не будет гонять гигабайты, если в каталоге лежат данные.
Повторная сборка без изменений не делает ничего заново: каждый шаг берётся из кэша сборки.
Шаг берётся из кэша, если не изменились инструкция, слой под ней и — для
COPY — содержимое копируемых файлов. Изменился один шаг — он и все
шаги ниже собираются заново. После правки index.html из кэша идёт
только WORKDIR, а COPY и шаги ниже выполняются заново.
Отсюда правило порядка: то, что меняется редко (основа, пользователь,
установка зависимостей), — выше; файлы приложения, которые правишь
часто, — ниже. --no-cache собирает всё заново.
RUN кэш сравнивает только по тексту команды. RUN date > /built в
кэше сохранит дату первой сборки.
CMD пишут в двух формах:
| Форма | Пример | Что запускается |
|---|---|---|
| exec | CMD ["httpd", "-f", "-p", "80"] |
сама программа, PID 1 |
| shell | CMD httpd -f -p 80 |
/bin/sh -c "httpd -f -p 80" |
В форме exec каждое слово — отдельная строка в кавычках. Одна строка на
всю команду — CMD ["httpd -f -p 80"] — значит «запусти программу с
именем httpd -f -p 80». Сборка пройдёт, а контейнер не стартует:
exec: "httpd -f -p 80": executable file not found in $PATH.
В форме shell главный процесс — оболочка, и переменные в строке
раскрываются. sh из busybox с одной командой заменяет себя ею: в
docker top виден только httpd.
Слова после имени образа в docker run заменяют CMD целиком:
docker run --rm app:1.1 ls /www. Если в образе есть ENTRYPOINT, эти
слова дописываются к нему:
Главный процесс должен работать на переднем плане: контейнер живёт, пока
он жив («Главный процесс»). httpd без -f
уходит в фон, и контейнер сразу выходит с Exited (0).
CMD наследуется от основы: nginx уже настроен работать на переднем
плане. Отдаёт он файлы из /usr/share/nginx/html.
adduser -D — busybox-версия: пользователь без пароля. Файлы из COPY
принадлежат root, где бы ни стояла USER: процесс прочитает их, но
писать в них не сможет. Отдать файлы пользователю —
COPY --chown=web:web data/ /data/.
Новый образ сам в работающий контейнер не попадает — контейнер нужно пересоздать.
Сборку ведёт старый сборщик: нет компонента buildx (пакет docker-buildx)
или он выключен переменной DOCKER_BUILDKIT=0. Это предупреждение,
сборка идёт дальше. Ошибка, если она есть, — в последних строках.
Образа из FROM нет на машине, и Docker пошёл в реестр. Сверь имя и тег
с docker images: FROM busybox — это busybox:latest. Без сети
загрузи основу из файла: docker load -i /opt/images/busybox.tar.
В COPY путь выше контекста: ../file. BuildKit считает его от корня
контекста и отвечает "/file": not found, старый сборщик — COPY failed:
forbidden path outside the build context. Положи файл в каталог сборки —
см. «Контекст сборки».
Старый сборщик пишет COPY failed: file not found in build context.
Файла нет в контексте: опечатка в имени, сборка запущена не из того
каталога или файл исключён в .dockerignore. Сверь ls в каталоге,
который стоит последним аргументом docker build.
failed to read dockerfile: open Dockerfile: no such file or directory
(у старого сборщика — unable to prepare context) — в каталоге контекста
нет файла Dockerfile. Перейди в каталог приложения или укажи файл ключом
-f. path "…" not found — нет самого каталога контекста.
Образ собрался, контейнер создан, но не запустился (статус Created):
программы с таким именем в образе нет. Чаще всего это CMD в форме exec
одной строкой — раздели слова: ["httpd", "-f", "-p", "80"]. Или
программы нет в образе основы: в busybox нет bash и python.
Команда запуска отработала и закончилась или ушла в фон. Проверь
docker image inspect -f '{{json .Config.Cmd}}': служба должна работать
на переднем плане (httpd -f, nginx -g 'daemon off;').
Проверь три места по порядку. Образ пересобран после правки? Контейнер
создан из нового образа (docker inspect -f '{{.Image}}' и ID образа)?
Правленый файл тот, что копирует COPY, и он не в .dockerignore?
Последнее видно по шагу COPY в выводе сборки: CACHED при
изменённом файле значит, что копируется другой файл.
COPY . . без .dockerignore унесёт в образ .env, ключи и .git.
RUN rm .env ниже не помогает: файл остаётся в слое COPY, и любой, у
кого есть образ, достанет его. Исключай секреты в .dockerignore и
передавай при запуске: -e, --env-file.
FROM nginx — это nginx:latest, то есть образ, который окажется под
этим именем в день сборки. Две сборки одного Dockerfile дадут разные
образы. Пиши версию: FROM nginx:1.27-alpine.
docker load.ENV.USER.Есть неточность? Сообщить об ошибке в статье