С чего начать Первые шаги трек · 15 ступеней
Linux Labs

Dockerfile — рецепт образа и docker build

12 мин Docker 29.1 · Ubuntu 24.04

Dockerfile — рецепт образа: основа, файлы, команды сборки и запуска; docker build собирает по нему образ.

Шпаргалка

6 команд Версия для печати
  • 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 собирает образ. Каждая строка — инструкция: взять основу, скопировать файлы, выполнить команду, задать команду запуска. Инструкции выполняются сверху вниз, каждая, что меняет файлы, даёт слой.

text
student@ubuntu:~/app$ sudo docker build -t app:1.1 .
#0 building with "default" instance using docker driver
…
#4 [1/4] FROM docker.io/library/busybox:latest@sha256:27be924c62a0…
#4 DONE 0.1s
#5 [internal] load build context
#5 transferring context: 31B done
#6 [2/4] WORKDIR /www
#6 DONE 0.1s
#7 [3/4] COPY index.html .
#7 DONE 0.1s
…
#9 naming to docker.io/library/app:1.1 done

-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 внутри контекста и падает:

text
ERROR: failed to build: failed to solve: failed to compute cache key:
failed to calculate checksum of ref …: "/secret.txt": not found

Нужный файл лежит снаружи — скопируй его в каталог сборки или собирай из каталога выше: docker build -f app/Dockerfile ..

Лишнее из контекста убирает .dockerignore рядом с Dockerfile — по шаблону на строку:

text
*.log
.env
.git

Так в образ не попадут журналы и секреты, а сборка не будет гонять гигабайты, если в каталоге лежат данные.

Кэш сборки

Повторная сборка без изменений не делает ничего заново: каждый шаг берётся из кэша сборки.

text
#6 [2/4] WORKDIR /www
#6 CACHED
#7 [3/4] COPY index.html .
#7 CACHED

Шаг берётся из кэша, если не изменились инструкция, слой под ней и — для COPY — содержимое копируемых файлов. Изменился один шаг — он и все шаги ниже собираются заново. После правки index.html из кэша идёт только WORKDIR, а COPY и шаги ниже выполняются заново.

Отсюда правило порядка: то, что меняется редко (основа, пользователь, установка зависимостей), — выше; файлы приложения, которые правишь часто, — ниже. --no-cache собирает всё заново.

RUN кэш сравнивает только по тексту команды. RUN date > /built в кэше сохранит дату первой сборки.

CMD и ENTRYPOINT

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, эти слова дописываются к нему:

text
student@ubuntu:~$ cat Dockerfile.ep
FROM busybox
ENTRYPOINT ["echo", "hello"]
CMD ["world"]
student@ubuntu:~$ sudo docker run --rm ep:1
hello world
student@ubuntu:~$ sudo docker run --rm ep:1 student
hello student

Главный процесс должен работать на переднем плане: контейнер живёт, пока он жив («Главный процесс»). httpd без -f уходит в фон, и контейнер сразу выходит с Exited (0).

Рецепты

4

Сайт на nginx:alpine

dockerfile
FROM nginx:alpine
COPY site/ /usr/share/nginx/html/

CMD наследуется от основы: nginx уже настроен работать на переднем плане. Отдаёт он файлы из /usr/share/nginx/html.

Процесс не от root

dockerfile
FROM busybox
RUN adduser -D -u 1001 web
USER web
ENV GREETING=hi
CMD ["sh", "-c", "id; echo $GREETING"]
text
student@ubuntu:~$ sudo docker run --rm user:1
uid=1001(web) gid=1001(web) groups=1001(web)
hi
student@ubuntu:~$ sudo docker run --rm -e GREETING=bye user:1
uid=1001(web) gid=1001(web) groups=1001(web)
bye

adduser -D — busybox-версия: пользователь без пароля. Файлы из COPY принадлежат root, где бы ни стояла USER: процесс прочитает их, но писать в них не сможет. Отдать файлы пользователю — COPY --chown=web:web data/ /data/.

Проверить образ до запуска службы

bash
sudo docker run --rm app:1.1 ls -l /www          # файлы на месте?
sudo docker image inspect -f '{{json .Config.Cmd}}' app:1.1
sudo docker history app:1.1                      # шаги и размеры

Пересобрать и подменить контейнер

bash
sudo docker build -t app:1.2 .
sudo docker rm -f app
sudo docker run -d --name app -p 8080:80 app:1.2

Новый образ сам в работающий контейнер не попадает — контейнер нужно пересоздать.

Неполадки

8

DEPRECATED: The legacy builder is deprecated

Сборку ведёт старый сборщик: нет компонента buildx (пакет docker-buildx) или он выключен переменной DOCKER_BUILDKIT=0. Это предупреждение, сборка идёт дальше. Ошибка, если она есть, — в последних строках.

failed to resolve reference на шаге FROM

Образа из FROM нет на машине, и Docker пошёл в реестр. Сверь имя и тег с docker images: FROM busybox — это busybox:latest. Без сети загрузи основу из файла: docker load -i /opt/images/busybox.tar.

COPY с путём выше контекста

В COPY путь выше контекста: ../file. BuildKit считает его от корня контекста и отвечает "/file": not found, старый сборщик — COPY failed: forbidden path outside the build context. Положи файл в каталог сборки — см. «Контекст сборки».

failed to compute cache key: not found

text
ERROR: failed to build: failed to solve: failed to compute cache key:
failed to calculate checksum of ref …: "/nosuch.html": not found

Старый сборщик пишет COPY failed: file not found in build context.

Файла нет в контексте: опечатка в имени, сборка запущена не из того каталога или файл исключён в .dockerignore. Сверь ls в каталоге, который стоит последним аргументом docker build.

failed to read dockerfile

failed to read dockerfile: open Dockerfile: no such file or directory (у старого сборщика — unable to prepare context) — в каталоге контекста нет файла Dockerfile. Перейди в каталог приложения или укажи файл ключом -f. path "…" not found — нет самого каталога контекста.

executable file not found in $PATH

text
docker: Error response from daemon: … exec: "httpd -f -p 80":
executable file not found in $PATH

Образ собрался, контейнер создан, но не запустился (статус Created): программы с таким именем в образе нет. Чаще всего это CMD в форме exec одной строкой — раздели слова: ["httpd", "-f", "-p", "80"]. Или программы нет в образе основы: в busybox нет bash и python.

Контейнер из нового образа сразу Exited (0)

Команда запуска отработала и закончилась или ушла в фон. Проверь docker image inspect -f '{{json .Config.Cmd}}': служба должна работать на переднем плане (httpd -f, nginx -g 'daemon off;').

Правка не попала в контейнер

Проверь три места по порядку. Образ пересобран после правки? Контейнер создан из нового образа (docker inspect -f '{{.Image}}' и ID образа)? Правленый файл тот, что копирует COPY, и он не в .dockerignore? Последнее видно по шагу COPY в выводе сборки: CACHED при изменённом файле значит, что копируется другой файл.

Опасно

2

Секреты в слоях

COPY . . без .dockerignore унесёт в образ .env, ключи и .git. RUN rm .env ниже не помогает: файл остаётся в слое COPY, и любой, у кого есть образ, достанет его. Исключай секреты в .dockerignore и передавай при запуске: -e, --env-file.

FROM без тега

FROM nginx — это nginx:latest, то есть образ, который окажется под этим именем в день сборки. Две сборки одного Dockerfile дадут разные образы. Пиши версию: FROM nginx:1.27-alpine.

Термины

2
контекст сборки build context
Каталог, который docker build отправляет демону: точка в команде «docker build -t app .». COPY берёт файлы только из него, лишнее отсекает файл .dockerignore.
кэш сборки build cache
Слои прошлых сборок, которые docker build берёт повторно, если инструкция и её файлы не изменились (Using cache). Изменённый шаг пересобирается вместе со всеми шагами ниже.

См. также

  • container-images — слои, теги, digest и что делает docker load.
  • docker — запуск контейнера из образа: порты, тома, перезапуск.
  • docker-compose — сборка и запуск нескольких сервисов из одного файла.
  • env-vars — переменные окружения и ENV.
  • users-groups — пользователи и UID для USER.
  • exit-codes — что значат коды 0, 1, 127 и 137.
Загрузка…