Image: неизменяемый снимок файловой системы + метаданные (CMD, ENV, порты). Состоит из слоёв (layers). Каждая инструкция в Dockerfile = слой.
Container: запущенный экземпляр image. Изолированный процесс со своей FS, сетью, PID-пространством. Эфемерный, можно удалить и поднять заново из того же image.
Volume: постоянное хранилище данных, живёт независимо от контейнера.
Network: виртуальная сеть, контейнеры в одной сети видят друг друга по имени.
Registry: хранилище image-ей (Docker Hub, GitHub Container Registry, self-hosted).
Registry (Docker Hub)
└── Repository (nginx)
└── Tag (1.27, latest, alpine)
└── Image (sha256:abc...)
└── Layers (слои, переиспользуются между image-ами)
Установка
# Archsudo pacman -S docker docker-compose
sudo systemctl enable --now docker
sudo usermod -aG docker $USER # чтобы не писать sudo (перелогиниться)# Ubuntucurl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# macOSbrew install --cask docker # Docker Desktop
Основные команды
Image
docker build -t myapp:latest . # собрать image из Dockerfiledocker build -t myapp:latest -f prod.Dockerfile . # указать файлdocker images # список image-ейdocker image ls # то же самоеdocker rmi <image> # удалить imagedocker image prune # удалить неиспользуемые imagedocker pull nginx:1.27-alpine # скачать imagedocker tag myapp:latest registry/myapp:v1 # добавить тегdocker push registry/myapp:v1 # отправить в registrydocker history <image> # слои image-аdocker inspect <image> # полная информация (JSON)
Container
docker run nginx # запустить контейнерdocker run -d nginx # в фоне (detached)docker run -d --name web nginx # с именемdocker run -d -p 8080:80 nginx # проброс порта (хост:контейнер)docker run -d -p 127.0.0.1:8080:80 nginx # только localhostdocker run --rm nginx # удалить после остановкиdocker run -it ubuntu bash # интерактивный режим с терминаломdocker run -e DB_HOST=localhost myapp # переменные окруженияdocker run --env-file .env myapp # ENV из файлаdocker run -v ./data:/app/data myapp # bind mount (хост:контейнер)docker run -v pgdata:/var/lib/postgresql/data postgres # named volumedocker ps # запущенные контейнерыdocker ps -a # все контейнеры (включая остановленные)docker stop <container> # остановитьdocker start <container> # запустить остановленныйdocker restart <container> # перезапуститьdocker rm <container> # удалитьdocker rm -f <container> # остановить + удалитьdocker logs <container> # логиdocker logs -f <container> # логи в реальном времениdocker logs --tail 100 <container> # последние 100 строкdocker exec -it <container> bash # зайти внутрь контейнераdocker exec -it <container> sh # если bash нет (alpine)docker cp <container>:/path/file ./local # скопировать файл из контейнераdocker stats # мониторинг ресурсов (CPU, RAM)docker top <container> # процессы внутри контейнераdocker inspect <container> # полная информация (JSON)
docker network ls # список сетейdocker network create mynet # создать сетьdocker network inspect mynet # информацияdocker run -d --network mynet --name api myapp # запустить в сетиdocker run -d --network mynet --name db postgres # другой контейнер в той же сети# api может обращаться к db по имени: postgres://db:5432
Очистка
docker system df # сколько места занятоdocker system prune # удалить остановленные контейнеры, неиспользуемые сети, dangling imagesdocker system prune -a --volumes # полная очистка (осторожно)
Dockerfile
Базовый пример
FROMnode:22-alpineWORKDIR/app# Сначала зависимости (кешируется, если package.json не менялся)COPY package.json package-lock.json ./RUN npm ci# Потом код (меняется часто)COPY . .EXPOSE3000CMD ["node", "server.js"]
Инструкции
Инструкция
Что делает
FROM
Базовый image
WORKDIR
Рабочая директория (создаётся автоматически)
COPY
Копировать файлы из контекста в image
ADD
Как COPY, но умеет распаковывать архивы и качать URL
RUN
Выполнить команду при сборке (создаёт слой)
CMD
Команда по умолчанию при запуске контейнера
ENTRYPOINT
Главный процесс контейнера (CMD становится аргументами)
ENV
Переменная окружения
ARG
Переменная времени сборки (docker build --build-arg)
EXPOSE
Документация порта (не пробрасывает, только метаданные)
VOLUME
Объявить точку монтирования
USER
Переключить пользователя
HEALTHCHECK
Проверка здоровья контейнера
Multi-stage build
Уменьшает размер итогового image, поскольку сборочные инструменты не попадают в production:
# --- Этап сборки ---FROMgolang:1.23-alpineASbuilderWORKDIR/srcCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 go build -o /app ./cmd/server# --- Production image ---FROMalpine:3.20RUN apk add --no-cache ca-certificatesCOPY --from=builder /app /appUSERnobodyEXPOSE8080ENTRYPOINT ["/app"]
Результат: вместо ~500MB (golang image) получается ~15MB (alpine + бинарник).
Оптимизация кеширования слоёв
Слои кешируются. Если слой не изменился, Docker использует кеш. Порядок инструкций критически важен:
# ПЛОХО — любое изменение кода сбрасывает кеш зависимостейCOPY . .RUN npm ci# ХОРОШО — зависимости кешируются отдельно от кодаCOPY package.json package-lock.json ./RUN npm ciCOPY . .
Правило: то, что меняется редко, размещай выше, а то, что часто, ниже.
docker compose up # поднять всёdocker compose up -d # в фонеdocker compose up --build # пересобрать image перед запускомdocker compose down # остановить и удалить контейнеры, сетиdocker compose down -v # + удалить volumes (данные!)docker compose ps # статус сервисовdocker compose logs -f api # логи конкретного сервисаdocker compose exec api sh # зайти внутрь сервисаdocker compose run --rm api npm test # запустить одноразовую командуdocker compose build # только собратьdocker compose pull # обновить image-иdocker compose restart api # перезапустить сервисdocker compose up --profile debug -d # запустить с профилемdocker compose config # валидация и вывод итогового yaml
Dev vs Prod
compose.yaml (базовый), compose.override.yaml (автоматически подхватывается для dev):
# compose.override.yaml (dev)services:
api:
build:
target: devvolumes:
- ./src:/app/srcenvironment:
- DEBUG=truecommand: npm run dev
Для prod нужно явно указать файлы:
docker compose -f compose.yaml -f compose.prod.yaml up -d
# Утро: поднять проектdocker compose up -d
docker compose logs -f api # в отдельном терминале / tmux-панели# Разработка: код меняется — hot reload через volume mount# Ничего перезапускать не надо# Зайти в контейнер (отладка, REPL, миграции)docker compose exec api sh
docker compose exec db psql -U user myapp
# Запустить тестыdocker compose run --rm api npm test
# Пересобрать после изменения Dockerfile или зависимостейdocker compose up --build -d
# Вечер: остановитьdocker compose down
# или оставить работать — ресурсы минимальны
Паттерн: одноразовые команды
# Миграции БДdocker compose run --rm api npx prisma migrate dev
# Генерация кодаdocker compose run --rm api go generate ./...
# Lintdocker compose run --rm api npm run lint
--rm: контейнер удалится после завершения команды.
Паттерн: отладка зависимостей
# Проверить, доступна ли БД из контейнера apidocker compose exec api sh -c 'nc -zv db 5432'# DNS-резолвdocker compose exec api nslookup db
# Посмотреть переменные окруженияdocker compose exec api env
# Проверить сетьdocker network inspect myapp_default
Паттерн: чистый старт
# Всё пересобрать и пересоздать с нуляdocker compose down -v
docker compose build --no-cache
docker compose up -d
FROMgolang:1.23-alpineASbuilderWORKDIR/srcCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/serverFROMscratchCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/COPY --from=builder /app /appUSER65534ENTRYPOINT ["/app"]
scratch: пустой image. Итоговый размер = размер бинарника (~10MB).
Безопасность
# 1. Минимальный базовый imageFROMnode:22-alpine# alpine вместо full image# 2. Непривилегированный пользовательUSERnode# не root# 3. Не хранить секреты в image# ПЛОХО:ENV API_KEY=secret123
# ХОРОШО — передавать при запуске:# docker run -e API_KEY=secret123 myapp# или через secrets в compose# 4. Read-only файловая система (в compose)# services:# api:# read_only: true# tmpfs:# - /tmp# 5. Ограничение ресурсов (в compose)# services:# api:# deploy:# resources:# limits:# memory: 512M# cpus: "0.5"
# ~/.bashrc или ~/.zshrcalias dc='docker compose'alias dcu='docker compose up -d'alias dcd='docker compose down'alias dcl='docker compose logs -f'alias dce='docker compose exec'alias dcr='docker compose run --rm'alias dcb='docker compose up --build -d'alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'alias dprune='docker system prune -af --volumes'
Частые проблемы
Контейнер сразу останавливается:
docker logs <container> # смотреть логи — там будет ошибкаdocker run -it <image> sh # зайти и проверить руками
Частая причина: CMD/ENTRYPOINT завершается. Процесс должен работать на foreground (не демонизироваться).
Порт уже занят:
# Найти, кто занял портss -tlnp | grep 8080# илиlsof -i :8080
# Сменить порт в compose: "8081:80"
Нет доступа к файлам в volume (permission denied):
UID/GID внутри контейнера не совпадает с хостовым. Решения:
# 1. Указать UID при запускеdocker run -u $(id -u):$(id -g) myapp
# 2. В Dockerfile создать пользователя с нужным UIDRUN adduser -u 1000 -S app
USER app
Image раздулся:
docker history <image> # какой слой сколько весит# Объединять RUN-команды в один слой:RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
# Использовать multi-stage build# Использовать alpine/slim базовые image
Compose: сервис не видит другой сервис:
Сервисы видят друг друга по имени сервиса в compose (не по имени контейнера). Убедись, что оба в одной сети (по умолчанию создаётся одна сеть на compose-проект).
Кеш сборки не работает:
docker compose build --no-cache # сборка без кеша# Или проверь порядок COPY в Dockerfile — файл изменился → все слои ниже пересобираются