Docker и Docker Compose — професионално ръководство
Част 3: Docker Images
Image е основата на контейнера. Тук разглеждам как се създава, от какво е изграден и защо начинът, по който пиша Dockerfile, има пряко значение за размера, скоростта и поддръжката.
ℹ Бележка Ако container-ът е runtime обектът, Docker Image е неговата основа. Не го разглеждам като „един файл, който Docker тегли и стартира“. Image е неизменяем набор от layers и metadata, от който Docker създава container.
1. Какво всъщност е Docker Image
Docker Image е неизменяем шаблон, от който се създават containers. Той съдържа filesystem съдържанието, необходимо за приложението, както и metadata, описваща как трябва да бъде стартирано то.
Това е първото разграничение, което държа да е ясно: image не е container. Един image може да бъде използван за създаването на много различни containers.
Dockerfile описание
→
docker build изграждане
→
Docker Image готов шаблон
↓
Container 1Container 2Container 3
2. Layers – истинската структура на Image
Docker Image не е един голям монолитен блок. Той е изграден от layers. Всеки layer представлява промяна спрямо предишното състояние на filesystem-а.
Dockerfile
FROM debian:bookworm
RUN apt-get update && apt-get install -y nginx
COPY nginx.conf /etc/nginx/nginx.conf
CMD ["nginx", "-g", "daemon off;"]
Тук FROM задава базовия image, а filesystem промените от build инструкциите формират слоевете на image-а. CMD описва default command и не трябва да се бърка с filesystem layer.
Container writable layer runtime промени
Application layer COPY / ADD
Package layer RUN …
Base image layers FROM
⚠ Предупреждение Важно: не използвам writable layer на container като място за постоянни данни. За persistent data използвам volumes или bind mounts.
3. Dockerfile – рецептата за Image
Dockerfile е текстов файл, в който описвам стъпките за изграждане на image. Това е build инструкцията, която превръща source кода и зависимостите в повторяем артефакт.
Dockerfile
FROM nginx:alpine
COPY ./html /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Изграждането е:
TERMINAL
docker build -t my-nginx:1.0 .
Точката в края определя build context – директорията, чието съдържание е достъпно за build процеса.
ℹ Бележка Практически момент: ако build context-ът е огромен, build процесът може да обработва много повече данни, отколкото реално са необходими. Затова .dockerignore не е дреболия.
4. Основни Dockerfile инструкции
| Инструкция | Предназначение | Пример |
|---|---|---|
FROM | Задава базовия image. | FROM alpine:3.22 |
RUN | Изпълнява команда по време на build. | RUN apk add --no-cache curl |
COPY | Копира файлове от context-а. | COPY app /app |
WORKDIR | Задава работната директория. | WORKDIR /app |
ENV | Задава environment variable. | ENV APP_ENV=production |
EXPOSE | Документира използван порт. | EXPOSE 8080 |
ENTRYPOINT | Определя основната команда. | ENTRYPOINT ["./app"] |
CMD | Задава default command/arguments. | CMD ["--config", "/etc/app.conf"] |
5. Build Cache – мястото, където Docker печели време
Build cache е една от най-практичните причини да обръщам внимание на реда на инструкциите в Dockerfile.
НЕОПТИМАЛНО
FROM node:22-alpine
COPY . .
RUN npm install
RUN npm run build
Ако променя един малък source файл, COPY . . може да направи cache слоя невалиден и да принуди dependency installation да се изпълни отново.
ПО-ДОБРА СТРУКТУРА
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
✓ Успех Моят принцип: най-рядко променящите се инструкции поставям по-рано, а най-често променящите се – по-късно, когато това е логично за build процеса.
6. .dockerignore – малък файл с голям ефект
.dockerignore определя какво да не влиза в build context. Не искам локалните зависимости, Git историята, логове и временни файлове да се изпращат към build процеса.
.dockerignore
.git
.gitignore
node_modules
vendor
*.log
.env
.env.*
tmp
cache
Това има значение и за сигурността. Не искам случайно да попаднат чувствителни или ненужни файлове в image-а.
7. Размерът на Image има значение
„Работи“ не е достатъчен критерий за добър image. Размерът му влияе върху времето за изтегляне, storage потреблението, cache-а и deployment-а.
ДИАГНОСТИКА
docker image ls
docker system df
docker system df -v
Не гоня минимален размер на всяка цена. По-малък image е полезен, но прекомерното оптимизиране може да направи Dockerfile-а труден за поддръжка.
7.1. Multi-stage build
При multi-stage build компилирам приложението в един stage, а в крайния image копирам само необходимото за runtime.
Dockerfile
FROM golang:1.24 AS builder
WORKDIR /src
COPY . .
RUN go build -o /out/app ./cmd/app
FROM alpine:3.22
COPY --from=builder /out/app /usr/local/bin/app
ENTRYPOINT ["/usr/local/bin/app"]
8. Как проверявам Image
Когато искам да разбера какво реално използвам, не разчитам само на името и tag-а.
ОСНОВНИ ПРОВЕРКИ
docker image ls
docker image inspect nginx:alpine
docker image history nginx:alpine
docker image inspect дава подробна metadata, а docker image history е особено полезна, когато търся откъде идва размерът на image-а.
9. Tags, Image ID и Digest
Tag-ът е удобен за човека, но не трябва да го приемам като вечна идентичност на image.
ПРИМЕР
nginx:alpine
nginx:1.29
nginx:latest
Tag може да бъде преместен към друг image. При deployment, където е нужна точно определена версия, digest-ът дава по-прецизна идентификация.
⚠ Предупреждение Практически съвет: в production не разчитам сляпо на latest . Искам да знам точно какъв артефакт ще бъде стартиран.
10. Registry – откъде идват Images
Image може да бъде локално изграден, но в реална инфраструктура често идва от registry. Registry е хранилище, от което images могат да бъдат push-вани и pull-вани.
Dockerfile
→
docker build
→
Registry
↓
Server A docker pull
Server B docker pull
Server C docker pull
ПРИМЕР
docker login registry.example.com
docker tag myapp:1.0 registry.example.com/myapp:1.0
docker push registry.example.com/myapp:1.0
11. Какво се случва при docker pull
При docker pull nginx:alpine Docker получава metadata и необходимите layers от registry. Ако даден layer вече съществува локално, той може да бъде използван повторно.
TERMINAL
docker pull nginx:alpine
Точно затова layer моделът е важен: общите layers могат да бъдат използвани от множество images.
12. BuildKit и съвременният build процес
Съвременният Docker build процес използва BuildKit. Той добавя възможности за по-ефективно изграждане, cache, parallelism и multi-platform builds.
MULTI-PLATFORM BUILD
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myapp:1.0 \
--push .
Не приемам автоматично, че image, изграден на една архитектура, е подходящ за всеки друг host.
13. Security започва още при Image
Ако image съдържа ненужни пакети, инструменти и права, проблемът вече съществува още преди container да бъде стартиран.
- избирам поддържан base image;
- ограничавам ненужните пакети;
- не записвам secrets в Dockerfile;
- проверявам дали приложението трябва да работи като root;
- поддържам dependencies актуални.
⚠ Предупреждение Никога не слагам secrets в Dockerfile. Password, API key или private key не трябва да бъдат записвани като обикновен текст в build context-а или в инструкции, които могат да останат в историята на image-а.
14. Когато Images започнат да пълнят диска
Docker натрупва images, stopped containers, volumes и build cache. При host, който се използва дълго време, това може да се превърне в реален проблем.
ДИАГНОСТИКА
docker system df
docker image ls
docker ps -a
ℹ Бележка Моето правило: диагностика преди cleanup. Особено при production host не приемам prune като универсално решение.
15. Практики, които използвам при Docker Images
- Избирам подходящ и поддържан base image.
- Използвам
.dockerignore. - Подреждам Dockerfile така, че cache-ът да се използва ефективно.
- Използвам multi-stage build, когато приложението го позволява.
- Не записвам secrets в image.
- Не разчитам сляпо на
latestза production deployment. - Проверявам размера и layer историята.
- Поддържам base images и dependencies актуални.
- Разделям build средата от runtime средата.
16. Моето мислене при диагностика на Image
Когато image стане прекалено голям, build-ът стане бавен или container-ът не стартира както очаквам, не започвам да променям произволни инструкции.
1. Какъв е base image?2. Какви layers са създадени?3. Къде се губи cache?4. Какво влиза от build context?5. Какво реално трябва да има в runtime image?
Този подход е по-надежден от „пробвам още една команда и гледам дали ще стане“. Image е артефакт от build процес. Ако разбера как е построен, много по-лесно разбирам и поведението му.
17. Най-важното от тази част
✓ Успех Docker Image е неизменяем шаблон, от който се създават containers. Image е изграден от layers . Dockerfile описва как се изгражда image. Build cache ускорява повторните builds. .dockerignore контролира build context. Multi-stage builds отделят build средата от runtime. Tags са имена, а не гаранция за неизменна идентичност. Registry разпространява images между системи. Размерът на image-а има значение за storage и deployment. Добрата image архитектура е част от security модела.
Заключение
Container-ът е runtime инстанцията. Image е артефактът, от който тя се създава. Ако image-ът е лошо структуриран, прекалено голям, неясен или съдържа ненужни зависимости, проблемът няма да изчезне, когато го стартирам.
За мен добрият Docker Image не е просто image, който работи. Той трябва да бъде предвидим, повторяем, поддържаем и достатъчно малък, без да жертвам надеждността заради няколко мегабайта.
В следващата част ще премина към Docker Containers – как се създават от image, какво се случва с writable layer-а, как се управлява жизненият им цикъл и защо docker run, docker create, docker start и docker exec не са едно и също нещо.