1. Защо изобщо да създавам собствен Image?
Готовите Images от Docker Hub са чудесни за много задачи. Ако ми трябва Nginx, MariaDB или Redis, няма смисъл да преоткривам колелото. Но в реална система почти винаги идва моментът, в който трябва да добавя собствен код, configuration, dependencies или специфични настройки.
Тогава вече не искам да променям ръчно работещ container. Искам да мога да кажа: ето source кода, ето Dockerfile-а — изгради ми абсолютно същия Image отново.
- получавам възпроизводим build;
- версионирам Dockerfile заедно с проекта;
- мога да изграждам Image в CI/CD;
- мога да тествам Image-а преди deployment;
- мога да върна стара версия, ако новата създава проблем.
2. Какво представлява Dockerfile?
Dockerfile е обикновен текстов файл с име Dockerfile, без задължително файлово разширение. В него описвам базовия Image, файловете, които ще добавя, командите за изпълнение при build и поведението на container-а при стартиране.
FROM nginx:alpine
COPY ./site /usr/share/nginx/html
EXPOSE 80Този Dockerfile започва от nginx:alpine, копира локалната директория site в директорията на Nginx и документира, че приложението използва порт 80.
рецепта
build процес
готов артефакт
работещ процес
3. FROM — откъде започва Image-ът
Почти всеки Dockerfile започва с FROM. Тази инструкция определя базовия Image, върху който ще изграждам моя Image.
FROM ubuntu:24.04
FROM debian:bookworm-slim
FROM python:3.13-slim
FROM nginx:alpineИзборът на base Image не е дреболия. Той влияе върху размера, наличните инструменти, security surface и начина, по който ще поддържам системата.
:latest. Tag-ът latest не означава „най-добрата и най-сигурната версия“. При следващ build съдържанието му може да бъде различно. За предвидими builds използвам конкретен tag и при по-строги среди обмислям pin-ване към digest.4. RUN — инсталиране и подготовка
RUN изпълнява команда по време на build процеса. Това е мястото, където инсталирам packages, създавам директории или подготвям приложението.
FROM debian:bookworm-slim
RUN apt-get update \\
&& apt-get install -y --no-install-recommends curl ca-certificates \\
&& rm -rf /var/lib/apt/lists/*Почиствам временните package lists в същия layer, в който ги създавам. Така не оставям ненужни данни в Image-а.
5. COPY — добавяне на файлове
COPY копира файлове от build context-а в Image-а. Това е основният механизъм, с който добавям source code, configuration и други необходими файлове.
FROM nginx:alpine
COPY ./html/ /usr/share/nginx/html/Тук съдържанието на локалната директория html става част от Image-а. При стартиране на container-а Nginx вече разполага с тези файлове.
6. ADD — защо обикновено предпочитам COPY
Docker има и инструкция ADD. Тя може да прави повече неща от COPY, включително автоматично разопаковане на някои локални архиви. Именно затова предпочитам COPY, когато просто трябва да копирам файлове.
COPY по подразбиране. Използвам ADD само когато конкретна негова функционалност действително ми е необходима.7. WORKDIR — работната директория
WORKDIR определя текущата директория за следващите инструкции като RUN, CMD, ENTRYPOINT и COPY.
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .Освен по-чиста структура, това помага и за доброто използване на build cache-а.
8. Docker build context
Когато изпълня:
docker build -t myapp:1.0 .последната точка означава текущата директория и тя е build context. Docker може да използва файлове от този context, но не и произволни файлове извън него.
.git, логове и други ненужни файлове, build context-ът става излишно голям. Затова използвам .dockerignore.9. .dockerignore
Файлът .dockerignore определя какво да не влиза в build context-а.
.git
.gitignore
node_modules
__pycache__
*.log
.env
.env.*
README.mdТова има значение не само за скоростта. Ако случайно попаднат secrets в build context-а и бъдат копирани в Image-а, вече имам security проблем.
10. CMD и ENTRYPOINT
Тук описвам поведението при стартиране на container-а, а не при build.
FROM python:3.13-slim
WORKDIR /app
COPY . .
CMD ["python", "app.py"]CMD задава default command. Тя може да бъде заменена при стартиране на container-а.
ENTRYPOINT ["python", "app.py"]ENTRYPOINT е подходяща, когато Image-ът има ясно определено основно приложение и искам аргументите да се подават към него.
11. EXPOSE — какво означава всъщност?
EXPOSE 8080EXPOSE не публикува порта към host системата. Той документира кой порт използва приложението. Реалното публикуване става при стартиране:
docker run -d -p 8080:8080 myapp:1.012. ENV и ARG
ARG се използва по време на build. ENV задава environment variable, която съществува и при изпълнение на container-а.
ARG APP_VERSION=1.0
ENV APP_ENV=production
RUN echo "Building version ${APP_VERSION}"ARG или ENV, за да ги „скрия“. Secrets не трябва да се вграждат в Docker Image. Те трябва да се подават по подходящ механизъм при deployment/build.13. Layer-и и Docker build cache
Dockerfile създава поредица от layers. Docker използва build cache, за да не изпълнява отново стъпки, които не са се променили.
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]Ако променя само server.js, Docker може да използва cache за npm ci. Това е малка промяна в структурата, която при големи проекти може да спести много време.
14. Multi-stage builds
При много приложения ми трябва един Image за build, но не ми трябват всички build инструменти в production container-а.
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80Първият stage съдържа Node.js и dependencies за build. Вторият съдържа само Nginx и готовия резултат. Така production Image-ът може да бъде значително по-малък и с по-малко ненужни компоненти.
15. USER — не изпълнявам всичко като root
Ако приложението позволява, предпочитам процесът в container-а да не работи като root.
FROM python:3.13-slim
RUN useradd --create-home --uid 10001 appuser
WORKDIR /app
COPY --chown=appuser:appuser . .
USER appuser
CMD ["python", "app.py"]Това не е универсално правило за всеки Image, но когато приложението може да работи като непривилегирован user, това е предпочитаният вариант.
16. HEALTHCHECK
Процесът може да съществува, а приложението вътре да е неизползваемо. HEALTHCHECK позволява да опиша проверка за health състоянието.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \\
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/ || exit 117. Как изграждам Image-а
docker build -t myapp:1.0 .След това проверявам получения Image:
docker image ls
docker image inspect myapp:1.0И го стартирам:
docker run -d --name myapp -p 8080:8080 myapp:1.018. Dockerfile + Docker Compose
Dockerfile и Compose не са конкуриращи се технологии. Dockerfile описва как се изгражда Image, а Compose описва как се стартират services и как са свързани помежду си.
services:
web:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
db:
image: mariadb:11
environment:
MARIADB_DATABASE: app
MARIADB_USER: app
MARIADB_PASSWORD: change-me
MARIADB_ROOT_PASSWORD: change-me-tooТук web се изгражда от моя Dockerfile, докато database service използва готов Image. Именно така изглеждат много реални Compose stacks.
19. Практически правила, които следвам
| Правило | Защо |
|---|---|
| Използвам малък и подходящ base Image | По-малък размер и по-малка attack surface. |
Избягвам :latest при важни builds | Получавам предвидими версии. |
Поддържам .dockerignore | Не изпращам ненужни файлове и secrets в context-а. |
| Подреждам инструкциите според cache-а | Ускорявам повторните builds. |
| Използвам multi-stage builds | Отделям build средата от runtime средата. |
| Не вграждам secrets в Image | Image-ът може да бъде копиран, публикуван или сканиран. |
| Работя като non-root, когато е възможно | Намалявам привилегиите на приложението. |
20. Най-честите грешки
- един огромен Dockerfile с десетки ненужни инструменти;
- използване на
latestнавсякъде; - копиране на целия проект без
.dockerignore; - вграждане на passwords, tokens и API keys в Image-а;
- стартиране на всичко като root без причина;
- смесване на build dependencies с production runtime;
- използване на shell команди без разбиране как работят layer-ите и cache-ът.
21. Заключение
С Docker Compose вече мога да описвам цял stack. С Dockerfile правя следващата крачка — описвам самия Image, от който този stack ще работи.
Това е фундаменталната разлика между „пускам някакъв container“ и изграждам контролирана container среда. Когато Dockerfile, Image и Compose конфигурацията са правилно подредени, deployment-ът престава да зависи от това какво съм правил ръчно на конкретния server.
В следващата част ще мина към Docker Registry и управлението на Images — къде ги съхранявам, как ги version-вам и как ги прехвърлям между машини.