Docker, Docker Compose, Linux #
1. В чём различие между контейнеризацией и виртуализацией? #
“Виртуализация представляет собой эмуляцию на уровне железа. Каждая виртуальная машина представляет собой самостоятельную машину со своей ОС
Контейнеризация - разделение приложений на уровне операционной системы. Контейнеры используют одно ядро ОС и изолируются друг от друга "
2. Является ли контейнер полноценной операционной системой? #
Нет, контейнер не является полноценной операционной системой.
Контейнер — это изолированный процесс или группа процессов, которые используют ядро операционной системы хоста.
Контейнер ≠ полноценная ОС
Контейнер ≠ виртуальная машина
Контейнер = изолированное окружение для запуска процесса
Docker в документации по безопасности отдельно указывает, что изоляция контейнеров опирается на возможности ядра: namespaces и cgroups.
Что есть внутри контейнера #
В контейнере обычно есть пользовательское окружение:
файловая система
библиотеки
зависимости
переменные окружения
конфиги
запускаемый процесс
часть системных утилит
Например, образ python:3.12-slim содержит:
Python
pip
часть Linux userland
библиотеки
файловую структуру
Но внутри контейнера нет собственного отдельного ядра ОС.
Чего нет внутри контейнера #
В контейнере обычно нет полноценной ОС в смысле виртуальной машины:
нет отдельного kernel
нет отдельной загрузки ОС
нет своего init/systemd в классическом смысле
нет полноценного hardware virtualization слоя
Контейнер использует ядро хостовой системы.
Схема:
Host OS kernel
├── container 1: nginx process
├── container 2: postgres process
└── container 3: python app process
Все эти контейнеры работают через одно ядро хоста.
Чем контейнер отличается от виртуальной машины #
Виртуальная машина обычно содержит полноценную гостевую ОС:
VM
├── guest kernel
├── guest OS
├── system libraries
└── application
Контейнер содержит только окружение приложения:
Container
├── libraries
├── dependencies
├── filesystem
└── application process
Сравнение:
Критерий Контейнер Виртуальная машина
---------------------------------------------------------------------
Ядро ОС общее с хостом своё guest kernel
Запуск быстро медленнее
Вес легче тяжелее
Изоляция на уровне процессов на уровне VM/hypervisor
Ресурсы меньше overhead больше overhead
Полноценная ОС нет да
Docker прямо описывает контейнеры не как VM-подход, а как механизм доставки и запуска приложений; контейнеры не являются полноценными виртуальными машинами.
Почему тогда внутри контейнера видна «ОС» #
Например, можно зайти в контейнер:
docker exec -it app bash
И увидеть:
cat /etc/os-release
Вывод может быть:
Debian GNU/Linux
Это не значит, что внутри запущена полноценная Debian OS с отдельным ядром.
Это значит, что файловая система контейнера основана на Debian userland:
пакеты
утилиты
директории
библиотеки
/etc/os-release
Но kernel всё равно берётся от хоста.
За счёт чего контейнер изолируется #
Контейнеры используют механизмы ядра Linux:
namespaces → изоляция процессов, сети, mount, PID, users
cgroups → ограничения CPU, RAM, IO и других ресурсов
capabilities/seccomp/AppArmor/SELinux → дополнительные ограничения безопасности
Docker в документации по security отдельно выделяет поддержку kernel namespaces и cgroups как одну из ключевых областей безопасности контейнеров.
Примерно так:
namespace создаёт иллюзию отдельной системы
cgroup ограничивает ресурсы
filesystem image даёт отдельное окружение файлов
Важный нюанс про Docker Desktop #
На Linux Docker-контейнеры обычно используют ядро Linux-хоста напрямую.
На Windows/macOS Docker Desktop часто запускает контейнеры внутри специальной Linux VM, потому что Linux-контейнерам нужно Linux-ядро. Docker указывает, что Docker Desktop запускает контейнеры внутри кастомизированной Linux VM, кроме native Windows containers.
Но даже там сам контейнер всё равно не является полноценной ОС. Полноценная ОС/ядро находится в VM, а контейнеры уже работают внутри неё.
Итог #
Контейнер — это не полноценная операционная система, а изолированное окружение для запуска процесса.
Правильная формулировка:
Контейнер содержит пользовательское окружение приложения,
но не содержит собственного ядра ОС.
Он использует ядро хостовой системы и изолируется через механизмы ядра.
3. Где находится ядро ОС при использовании контейнеров? #
При использовании контейнеров ядро ОС находится не внутри контейнера, а на хостовой системе.
Host OS kernel
├── container 1
├── container 2
└── container 3
Все контейнеры используют одно и то же ядро хоста.
На Linux #
Если Docker запущен на Linux, контейнеры используют ядро Linux-хоста напрямую.
Linux host
├── Linux kernel
│ ├── container: nginx
│ ├── container: postgres
│ └── container: python app
Контейнер содержит:
файловую систему
библиотеки
зависимости
конфиги
запускаемый процесс
Но не содержит собственного ядра.
Docker в документации по безопасности указывает, что контейнерная изоляция опирается на возможности ядра: namespaces и cgroups. То есть именно ядро хоста создаёт изоляцию процессов и ограничение ресурсов.
На Windows и macOS #
Для Linux-контейнеров на Windows/macOS ситуация чуть сложнее.
Там Linux-контейнеры обычно запускаются не напрямую на ядре Windows/macOS, а внутри Linux VM, которую использует Docker Desktop.
Windows / macOS
└── Docker Desktop Linux VM
└── Linux kernel
├── container 1
├── container 2
└── container 3
То есть ядро всё равно не внутри контейнера. Оно находится либо:
на Linux-хосте
либо:
в Linux VM, которую запускает Docker Desktop
Docker Desktop в настройках прямо упоминает ресурсы, доступные для Docker Linux VM, что отражает эту архитектуру на Desktop-платформах.
Почему контейнер видит «свою систему» #
В контейнере можно выполнить:
cat /etc/os-release
И увидеть, например:
Debian GNU/Linux
Но это не значит, что внутри контейнера запущено отдельное ядро Debian.
Это означает только, что файловая система контейнера основана на Debian userland:
/etc
/bin
/usr
libc
bash
apt
python
nginx
А ядро будет хостовое:
uname -r
Команда покажет версию ядра, которое реально обслуживает контейнер. Обычно это ядро хоста или ядро Linux VM в Docker Desktop.
Сравнение с виртуальной машиной #
Контейнер:
app + libs + filesystem
использует kernel хоста
Виртуальная машина:
guest OS + guest kernel + app
имеет своё отдельное ядро
Поэтому контейнер легче виртуальной машины: он не загружает отдельную ОС и не держит отдельное ядро.
Итог #
Правильная формулировка:
Ядро ОС находится на хостовой системе, а не внутри контейнера.
Контейнер использует это общее ядро и изолируется с помощью namespaces, cgroups и других механизмов ядра.
4. Что такое загрузчик системы (GRUB) и какую роль он выполняет? #
Что такое GRUB #
GRUB — это загрузчик системы.
Полное название:
GRUB = GRand Unified Bootloader
Его задача — запустить операционную систему после включения компьютера.
Официальная документация GNU описывает GRUB как гибкий и мощный boot loader, который может загружать разные операционные системы на разных архитектурах.
Где GRUB находится в цепочке загрузки #
Упрощённая схема загрузки компьютера:
1. Нажали кнопку питания
2. BIOS/UEFI инициализирует железо
3. BIOS/UEFI ищет загрузочное устройство
4. Запускается загрузчик GRUB
5. GRUB показывает меню загрузки
6. GRUB загружает ядро ОС
7. Ядро запускает операционную систему
То есть GRUB находится между прошивкой компьютера и операционной системой:
BIOS/UEFI → GRUB → Linux kernel → systemd/init → user space
Какую роль выполняет GRUB #
Главная роль GRUB — найти и загрузить ядро операционной системы.
Для Linux это обычно:
vmlinuz → ядро Linux
initramfs → временная начальная файловая система
grub.cfg → конфигурация меню загрузки
GRUB передаёт управление ядру Linux, а уже ядро запускает остальную систему.
Что делает GRUB на практике #
GRUB может:
показать меню выбора ОС
загрузить нужное ядро Linux
передать ядру параметры запуска
загрузить initramfs
запустить систему в обычном режиме
запустить recovery mode
выбрать другую версию ядра
загрузить другую ОС через chainloading
Chainloading — это когда GRUB не загружает ОС напрямую, а передаёт управление другому загрузчику. В документации GRUB отдельно указано, что для некоторых ОС используется chain-loading.
Пример меню GRUB #
При запуске компьютера можно увидеть примерно такое меню:
Ubuntu
Advanced options for Ubuntu
Windows Boot Manager
UEFI Firmware Settings
Это значит, что GRUB позволяет выбрать, что именно загружать.
Например:
Ubuntu → обычная загрузка Linux
Advanced options for Ubuntu → выбор старого ядра или recovery mode
Windows Boot Manager → передача управления загрузчику Windows
UEFI Firmware Settings → вход в настройки UEFI
Зачем нужен GRUB, если есть BIOS/UEFI #
BIOS/UEFI не запускает полноценную ОС напрямую в привычном смысле. Его задача — найти загрузчик и передать ему управление.
BIOS/UEFI знает:
с какого диска загружаться
какой EFI-файл запустить
какое устройство первое в boot order
Но GRUB уже знает больше про операционную систему:
где лежит ядро Linux
какие параметры передать ядру
какой initramfs загрузить
какие пункты меню показать
какие ОС доступны
То есть:
BIOS/UEFI запускает загрузчик
GRUB запускает ОС
GRUB и ядро Linux #
GRUB не является ядром Linux.
Это разные вещи:
GRUB → загрузчик
Linux kernel → ядро операционной системы
GRUB работает до запуска ядра. После того как ядро загружено и ему передано управление, основная работа GRUB заканчивается.
Схема:
GRUB загрузил kernel + initramfs
↓
передал управление kernel
↓
Linux kernel продолжил запуск системы
Где обычно находится GRUB #
Зависит от режима загрузки.
BIOS / Legacy #
В старой BIOS-схеме часть GRUB может находиться:
в MBR диска
в специальной области после MBR
в /boot/grub
UEFI #
В современной UEFI-схеме GRUB обычно находится на EFI System Partition.
Пример пути:
/boot/efi/EFI/ubuntu/grubx64.efi
Или для другого дистрибутива:
/boot/efi/EFI/fedora/grubx64.efi
/boot/efi/EFI/debian/grubx64.efi
UEFI запускает .efi-файл загрузчика, а дальше GRUB открывает своё меню и загружает выбранную ОС.
Конфигурация GRUB #
Основной конфигурационный файл обычно:
/boot/grub/grub.cfg
Но вручную его обычно не редактируют.
Чаще меняют:
/etc/default/grub
А потом пересобирают конфигурацию командой вроде:
sudo update-grub
или:
sudo grub-mkconfig -o /boot/grub/grub.cfg
Название команды зависит от дистрибутива.
Примеры параметров ядра #
GRUB может передавать ядру Linux параметры запуска.
Например:
quiet
splash
nomodeset
single
root=/dev/sda2
Они влияют на то, как будет запускаться система.
Например:
quiet splash → скрыть подробный вывод загрузки
nomodeset → часто используют при проблемах с видеодрайвером
single → однопользовательский/recovery-подобный режим
Когда GRUB особенно важен #
GRUB полезен, когда:
на компьютере несколько ОС
нужно выбрать старую версию ядра
сломался новый kernel после обновления
нужно загрузиться в recovery mode
нужно изменить параметры запуска ядра
нужно восстановить загрузку Linux
Например, после неудачного обновления ядра можно выбрать старое ядро через:
Advanced options for Linux
и загрузить систему.
Коротко #
GRUB — это загрузчик операционной системы. Он запускается после BIOS/UEFI, показывает меню загрузки, выбирает нужную ОС или ядро, загружает kernel и initramfs в память, передаёт ядру параметры запуска и затем отдаёт ему управление.
Главная мысль:
BIOS/UEFI запускает GRUB,
GRUB запускает ядро ОС,
ядро запускает остальную систему.
5. Какие этапы происходят после того, как GRUB передаёт управление ядру? #
После того как GRUB передал управление ядру Linux, GRUB уже почти не участвует в процессе. Дальше загрузкой занимается само ядро, затем initramfs, затем первый пользовательский процесс — обычно systemd.
Общая цепочка:
BIOS/UEFI
↓
GRUB
↓
Linux kernel
↓
initramfs / initrd
↓
real root filesystem
↓
PID 1: systemd / init
↓
services, network, login
1. Ядро получает управление #
GRUB загружает в память:
kernel image → ядро Linux
initramfs → временная начальная файловая система
cmdline → параметры запуска ядра
После этого GRUB передаёт управление ядру. Ядро начинает собственную инициализацию: распаковку, настройку памяти, CPU, таблиц, прерываний и базовых подсистем. Параметры запуска ядра передаются через kernel command line; документация ядра отдельно описывает параметры командной строки как механизм настройки поведения kernel при boot.
2. Инициализация ядра #
На этом этапе ядро подготавливает низкоуровневую часть системы:
инициализация CPU
инициализация памяти
настройка scheduler
инициализация interrupt handling
обнаружение оборудования
загрузка встроенных драйверов
инициализация VFS
подготовка rootfs
То есть ядро начинает управлять железом и создаёт базовую среду, в которой дальше смогут запускаться пользовательские процессы.
3. Работа с initramfs / initrd #
Обычно GRUB вместе с ядром загружает ещё и initramfs.
initramfs — это временная файловая система в памяти. Она нужна, чтобы подготовить систему к монтированию настоящего корневого раздела.
Документация Linux kernel описывает initramfs как cpio-архив, который распаковывается в rootfs во время загрузки ядра.
Зачем нужен initramfs:
загрузить нужные kernel modules
найти диск с root filesystem
расшифровать LUKS-раздел
активировать LVM
подключить RAID
подготовить драйверы файловой системы
смонтировать настоящий root
Например, если корневая система лежит на LVM, encrypted disk или RAID, ядро не всегда может сразу смонтировать её без дополнительной подготовки.
4. Запускается ранний userspace #
После распаковки initramfs ядро запускает первый временный userspace-процесс из initramfs.
Обычно это:
/init
или скрипт/бинарник, который выполняет раннюю подготовку.
Схема:
kernel
↓
initramfs
↓
/init внутри initramfs
Этот /init ещё не является полноценным systemd основной системы. Это временная стадия, задача которой — подготовить настоящий root filesystem.
Документация kernel по initrd указывает, что initrd позволяет загрузчику загрузить RAM disk, смонтировать его как root filesystem и запускать из него программы.
5. Монтируется настоящий root filesystem #
После подготовки initramfs находит и монтирует настоящий корневой раздел.
Например:
/dev/sda2
/dev/nvme0n1p2
UUID=...
LVM volume
encrypted volume
network root
После этого происходит переключение:
temporary root из initramfs
↓
real root filesystem
Обычно этот этап называют:
switch_root
или похожим механизмом в зависимости от реализации initramfs.
Смысл:
сначала система жила во временной ФС в памяти
потом перешла на настоящий root-раздел диска
6. Запускается PID 1 #
После монтирования настоящего root filesystem ядро запускает первый настоящий пользовательский процесс.
Обычно это:
/sbin/init
В современных Linux-дистрибутивах чаще всего это systemd.
Первый процесс получает PID 1:
PID 1 = systemd / init
Это особый процесс. Он становится родительским процессом для остальных системных сервисов и управляет дальнейшей загрузкой системы.
Документация systemd описывает его как system and service manager, который запускается как первый userspace-процесс и управляет службами.
7. systemd поднимает систему #
После запуска systemd начинается уже привычная инициализация userspace:
монтирование файловых систем
запуск udev
настройка hostname
запуск journald
запуск dbus
запуск network services
запуск cron/timers
запуск ssh
запуск display manager
запуск пользовательских сервисов
systemd использует units:
.service
.target
.mount
.socket
.timer
.device
Документация systemd bootup описывает, что на этапе boot системный менеджер инициализирует файловые системы, сервисы и драйверы, необходимые для работы системы.
8. Система выходит на нужный target #
В systemd вместо старых runlevel обычно используются targets.
Примеры:
basic.target
multi-user.target
graphical.target
rescue.target
emergency.target
Упрощённо:
multi-user.target → серверный режим, сеть, сервисы, без GUI
graphical.target → графический режим с display manager
rescue.target → восстановительный режим
Если это сервер, система обычно доходит до multi-user.target.
Если это desktop Linux, система доходит до graphical.target, после чего появляется экран входа.
Полная схема после GRUB #
GRUB передал управление kernel
↓
kernel распаковывается и стартует
↓
kernel инициализирует CPU, RAM, драйверы, подсистемы
↓
kernel использует параметры командной строки
↓
kernel распаковывает/initramfs
↓
запускается /init из initramfs
↓
initramfs находит и монтирует настоящий root filesystem
↓
происходит switch_root
↓
kernel запускает /sbin/init
↓
стартует PID 1: systemd
↓
systemd запускает units/services/targets
↓
система готова к работе
Коротко #
После передачи управления от GRUB ядро Linux распаковывается, инициализирует память, процессор, драйверы и базовые подсистемы. Затем оно использует initramfs/initrd для подготовки настоящего корневого раздела, монтирует root filesystem и запускает первый пользовательский процесс — PID 1, обычно systemd. После этого systemd поднимает сервисы, монтирует файловые системы, настраивает окружение и доводит систему до нужного target, например multi-user или graphical.
Главная мысль:
GRUB только запускает ядро.
Дальше ядро запускает initramfs,
initramfs подготавливает root filesystem,
а systemd поднимает пользовательскую часть системы.
6. Какие особенности работы контейнеров на Windows? #
Контейнеры на Windows работают в двух разных режимах:
1. Linux containers → через WSL2 / Linux VM
2. Windows containers → на Windows-ядре или через Hyper-V isolation
Главная особенность: Windows не запускает Linux-контейнеры напрямую на Windows-ядре. Linux-контейнеру нужно Linux-ядро, поэтому Docker Desktop на Windows обычно использует WSL2 backend. Docker официально указывает, что Docker Desktop может работать на Windows через WSL2, а WSL2 backend включается в настройках Docker Desktop.
1. Linux-контейнеры на Windows #
Если ты запускаешь обычные образы вроде:
docker run python:3.12
docker run postgres:16
docker run redis:7
docker run nginx
то это обычно Linux-контейнеры.
На Windows они работают так:
Windows
└── WSL2 / Docker Linux VM
└── Linux kernel
├── container: python
├── container: postgres
└── container: redis
То есть контейнер не использует Windows kernel. Он использует Linux kernel внутри WSL2/Docker VM. Docker Desktop также указывает, что на Windows можно работать «нативно» через WSL2.
2. Windows-контейнеры #
Windows-контейнеры — это отдельный тип контейнеров, которые запускают Windows-приложения.
Примеры образов:
mcr.microsoft.com/windows/nanoserver
mcr.microsoft.com/windows/servercore
Они нужны, если приложение зависит от Windows API, .NET Framework, IIS или другого Windows-окружения.
Windows-контейнеры имеют два режима изоляции:
process isolation
Hyper-V isolation
Microsoft официально описывает два режима runtime isolation для Windows containers: process isolation и Hyper-V isolation.
3. Process isolation #
В режиме process isolation контейнеры используют ядро Windows-хоста.
Windows host kernel
├── Windows container 1
├── Windows container 2
└── Windows container 3
Это ближе к классической модели контейнеров: контейнеры изолированы как процессы, но используют общее ядро хоста.
Плюсы:
быстрее запуск
меньше overhead
выше плотность контейнеров
Минусы:
жёстче требования к совместимости версий Windows host/image
изоляция слабее, чем у Hyper-V isolation
Microsoft указывает, что при process isolation контейнеры используют общее ядро с хостом, а Hyper-V isolation даёт более сильную изоляцию.
4. Hyper-V isolation #
В Hyper-V isolation каждый Windows-контейнер запускается внутри оптимизированной виртуальной машины.
Windows host
├── optimized VM → Windows container 1
├── optimized VM → Windows container 2
└── optimized VM → Windows container 3
В этом режиме контейнер фактически получает собственный экземпляр Windows kernel. Microsoft прямо указывает, что Hyper-V isolation создаёт безопасную границу вокруг контейнера с помощью оптимизированной VM, а каждый такой контейнер имеет собственный instance Windows kernel.
Плюсы:
сильнее изоляция
лучше совместимость между версиями host и container image
безопаснее для недоверенного кода
Минусы:
больше overhead
медленнее запуск
больше потребление ресурсов
нужен Hyper-V
5. Нельзя путать Linux и Windows images #
Linux-образ и Windows-образ — это разные вещи.
python:3.12-slim → Linux image
postgres:16 → Linux image
mcr.microsoft.com/windows/servercore → Windows image
Linux-контейнеру нужно Linux-ядро. Windows-контейнеру нужно Windows-ядро или Hyper-V isolation с Windows kernel.
Поэтому на Windows обычно переключаются между режимами:
Linux containers
Windows containers
Docker Desktop для Windows поддерживает переключение между Linux и Windows containers; в документации CLI есть команда docker desktop engine use, которая предназначена именно для switch to Windows or Linux containers.
6. Особенности файловой системы #
На Windows важно, где лежит проект.
Если работаешь с Linux-контейнерами через WSL2, лучше держать проект внутри Linux filesystem WSL, например:
/home/user/project
а не в:
C:\Users\user\project
Причина — производительность файловых операций. Docker отдельно даёт рекомендации по WSL2 best practices, включая оптимизацию filesystem performance при bind mounts.
На практике это особенно заметно в проектах с большим количеством файлов:
node_modules
Python venv
Django/FastAPI проект
много тестов
много миграций
большие vendor/dependency директории
7. Особенности путей #
На Windows и Linux разные форматы путей.
Windows:
C:\Users\Alfob\project
Linux/WSL:
/home/alfob/project
/mnt/c/Users/Alfob/project
В Docker Compose на Windows из-за этого иногда возникают проблемы с volumes:
volumes:
- ./app:/app
или с абсолютными путями:
volumes:
- C:\Users\Alfob\project:/app
Лучше для Linux-контейнеров работать из WSL-терминала и хранить проект в Linux-разделе WSL.
8. Особенности сети #
Контейнеры на Docker Desktop работают не совсем так, как на чистом Linux-хосте, потому что между Windows-хостом, VM/WSL2 и контейнерами есть дополнительный сетевой слой.
Схема:
Windows host
↕
Docker Desktop / WSL2 VM
↕
Docker network
↕
containers
Docker Desktop отдельно описывает, что он маршрутизирует network traffic и file I/O между контейнерами, VM и host.
Обычно проброс порта работает привычно:
docker run -p 8000:8000 my-app
После этого приложение доступно с Windows-хоста:
http://localhost:8000
Но при сложной сетевой настройке могут быть нюансы с firewall, VPN, proxy, корпоративной сетью и доступом контейнера к сервисам на Windows-хосте.
9. Совместимость версий Windows-контейнеров #
Для Windows-контейнеров важна версия Windows host и версия container image.
Например, образ, собранный под одну версию Windows Server, может не запуститься в process isolation на несовместимой версии Windows-хоста.
Microsoft отдельно ведёт документацию по Windows container version compatibility и указывает, что Hyper-V isolation помогает запускать контейнеры при различиях между версией host и image.
Практически:
Linux containers → обычно меньше проблем с версией Windows
Windows containers → нужно смотреть совместимость host/image
10. Docker Desktop требует включённых компонентов Windows #
Для Linux-контейнеров обычно нужен WSL2.
Docker указывает, что WSL2 должен быть включён на машине перед запуском Docker Desktop с WSL2 backend.
Для Windows containers с Hyper-V isolation нужен Hyper-V. Microsoft указывает, что Hyper-V role должна быть установлена перед запуском Hyper-V isolation.
То есть могут понадобиться:
WSL2
Virtual Machine Platform
Hyper-V
Windows Containers feature
аппаратная виртуализация в BIOS/UEFI
11. Образы Windows обычно тяжелее #
Windows base images обычно крупнее Linux slim/alpine образов.
Например:
Linux image: python:3.12-slim
Windows image: windows/servercore
Windows-контейнеры часто тяжелее, потому что им нужен Windows userland: системные библиотеки, API и компоненты совместимости.
Поэтому для backend-разработки на Python, Node.js, PostgreSQL, Redis, Nginx чаще используют Linux-контейнеры даже на Windows.
12. Что выбирать на практике #
Для обычного Python backend-проекта:
FastAPI
Django
PostgreSQL
Redis
Celery
Nginx
лучше использовать:
Docker Desktop + WSL2 backend + Linux containers
То есть:
код хранить внутри WSL
команды запускать из WSL terminal
docker compose использовать оттуда же
образы брать Linux-based
Windows containers нужны, если приложение реально Windows-specific:
.NET Framework
IIS
Windows Services
COM-компоненты
Windows API
legacy Windows-приложения
Итог #
Особенности контейнеров на Windows:
Linux-контейнеры работают через WSL2/Docker Linux VM
Windows-контейнеры работают на Windows kernel или через Hyper-V isolation
Linux и Windows images несовместимы между собой
для Windows-контейнеров важна версия host и image
файлы проекта лучше хранить внутри WSL для Linux-контейнеров
сеть идёт через дополнительный слой Docker Desktop/WSL2
для работы нужны WSL2, Hyper-V или Windows Containers features
7. Как выбирается базовый образ для контейнера? #
Базовый образ выбирается по тому, что нужно приложению для запуска:
язык / runtime
версия ОС и библиотек
размер образа
безопасность
совместимость зависимостей
стабильность сборки
production-окружение
Главное правило:
базовый образ должен содержать минимум необходимого,
но при этом не ломать сборку и runtime приложения
Docker в best practices рекомендует по возможности использовать актуальные official images как основу для своих образов.
1. Сначала выбирают runtime #
Для Python-проекта обычно берут Python-образ:
FROM python:3.12-slim
Для Node.js:
FROM node:22-slim
Для Go-приложения часто используют multi-stage build:
FROM golang:1.23 AS builder
А потом минимальный runtime-образ:
FROM debian:bookworm-slim
Идея:
если приложению нужен Python → берем python image
если нужен Node.js → берем node image
если нужен Java → берем eclipse-temurin/openjdk image
если нужен только бинарник → можно взять debian-slim, alpine или distroless
2. Лучше брать official image #
Предпочтительно использовать official images:
python
node
postgres
redis
nginx
debian
ubuntu
alpine
Причина: они лучше поддерживаются, документированы и чаще обновляются. Docker Hub отдельно выделяет Docker Official Images и trusted content как более надёжную основу для контейнеров.
Плохой вариант:
FROM random-user/python-custom:latest
Лучше:
FROM python:3.12-slim
3. Версию нужно фиксировать #
Плохо:
FROM python:latest
Почему плохо:
сегодня latest может быть Python 3.12
позже latest станет Python 3.13
сборка может неожиданно сломаться
Лучше:
FROM python:3.12-slim
Ещё строже — фиксировать digest:
FROM python:3.12-slim@sha256:...
Но на практике для большинства проектов обычно достаточно фиксированной версии 3.12-slim, а digest используют там, где нужна максимальная воспроизводимость сборки.
4. slim часто лучше полного образа
#
Например:
FROM python:3.12
Это полный образ. В нём больше системных пакетов.
Чаще для production лучше:
FROM python:3.12-slim
Плюсы slim:
меньше размер
меньше лишних пакетов
меньше attack surface
быстрее pull/push
Минусы:
может не хватать build tools
может не хватать системных библиотек
нужно явно ставить зависимости через apt
Пример:
FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
5. alpine не всегда лучший выбор
#
alpine очень маленький:
FROM python:3.12-alpine
Docker указывает, что Alpine-variants обычно меньше slim-вариантов и ставят только необходимые пакеты.
Но Alpine использует musl libc, а не glibc. Из-за этого могут быть проблемы с Python-пакетами, которые имеют нативные зависимости:
psycopg2
numpy
pandas
cryptography
pillow
lxml
uvloop
grpcio
В итоге alpine может дать:
меньший образ
но более сложную сборку
дольше установку зависимостей
проблемы совместимости
Для Python backend чаще безопаснее:
FROM python:3.12-slim
А не:
FROM python:3.12-alpine
6. Для production лучше не тащить build-зависимости #
Плохой вариант:
FROM python:3.12-slim
RUN apt-get update && apt-get install -y gcc build-essential
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
Проблема: gcc, build-essential и прочие инструменты остаются в финальном образе.
Лучше использовать multi-stage build:
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY . .
CMD ["python", "main.py"]
Смысл:
builder image → собирает зависимости
runtime image → содержит только то, что нужно для запуска
Docker в best practices отдельно рекомендует multi-stage builds для уменьшения размера финального образа и отделения build-зависимостей от runtime.
7. Нужно учитывать production-окружение #
Базовый образ должен быть близок к тому, где приложение реально будет работать.
Например, если в production Linux-контейнеры на Debian-based окружении, нормальный выбор:
FROM python:3.12-slim
Если компания использует Ubuntu-based images:
FROM ubuntu:24.04
Если нужен минимальный security-focused runtime:
FROM gcr.io/distroless/python3-debian12
Главная идея:
чем ближе образ к production-окружению,
тем меньше риск, что локально всё работает, а в deploy ломается
8. Нужно смотреть на безопасность #
Базовый образ приносит в контейнер свои системные пакеты и уязвимости.
Поэтому перед выбором стоит проверять:
как часто образ обновляется
есть ли known vulnerabilities
не устарела ли версия ОС
не используется ли EOL-дистрибутив
нет ли лишних пакетов
Docker Scout предназначен для анализа состава образов и уязвимостей внутри них.
Пример проверки:
docker scout cves python:3.12-slim
или через альтернативы:
trivy image python:3.12-slim
grype python:3.12-slim
9. Не нужно ставить всё подряд в базовый образ #
Плохая практика:
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
git \
curl \
vim \
nano \
gcc \
make \
build-essential \
postgresql-client \
redis-tools
Так образ становится:
большим
медленным
сложным
менее безопасным
труднее воспроизводимым
Лучше брать готовый runtime-образ:
FROM python:3.12-slim
И ставить только реально нужное:
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
10. Для разработки и production могут быть разные образы #
Для dev можно иметь больше инструментов:
debug tools
watch reload
test dependencies
linters
shell utilities
Для production — минимум:
runtime
приложение
production dependencies
Пример:
Dockerfile → production
Dockerfile.dev → development
Или один Dockerfile с разными stages:
FROM python:3.12-slim AS base
FROM base AS dev
RUN pip install pytest ruff
FROM base AS prod
COPY . .
CMD ["gunicorn", "app.main:app"]
11. Практический выбор для Python backend #
Для FastAPI/Django обычно хороший выбор:
FROM python:3.12-slim
Почему:
официальный Python image
фиксированная версия Python
меньше полного python:3.12
обычно меньше проблем, чем с alpine
подходит для PostgreSQL/Redis/backend-проектов
Для локальной разработки:
FROM python:3.12-slim
Для production:
FROM python:3.12-slim
или multi-stage:
FROM python:3.12-slim AS builder
...
FROM python:3.12-slim AS runtime
...
Сравнение вариантов #
Образ Когда использовать
------------------------------------------------------------
python:3.12 Когда нужен полный образ, проще debug
python:3.12-slim Хороший default для production Python
python:3.12-alpine Когда важен минимальный размер и нет проблем с musl
debian:bookworm-slim Когда runtime собираешь сам
ubuntu:24.04 Когда нужна Ubuntu-совместимость
scratch Только для статически собранных бинарников
distroless Для минимального production runtime
Итоговый алгоритм выбора #
1. Определить runtime
Python, Node.js, Java, Go, бинарник
2. Выбрать official image
python, node, debian, alpine, nginx и т.д.
3. Зафиксировать версию
не latest, а конкретный tag
4. Выбрать размер
full, slim, alpine, distroless
5. Проверить совместимость зависимостей
особенно native packages
6. Проверить безопасность
vulnerabilities, обновления, EOL
7. Убрать build tools из финального образа
multi-stage build
8. Сделать образ воспроизводимым
фиксированные версии, lock-файлы, минимальные зависимости
Коротко #
Базовый образ выбирают исходя из runtime приложения, совместимости зависимостей, размера, безопасности и близости к production-окружению. Обычно лучше брать официальный образ с фиксированной версией, например python:3.12-slim, а не latest. Для production желательно использовать минимальный образ, не тащить лишние build-зависимости и при необходимости применять multi-stage build.
Главная мысль:
лучший базовый образ — не самый маленький,
а самый минимальный из тех, которые стабильно и безопасно запускают приложение
8. Какими способами передаются параметры в контейнер при запуске? #
Параметры в контейнер при запуске передаются несколькими основными способами:
1. Environment variables
2. Аргументы команды CMD/ENTRYPOINT
3. Файлы конфигурации через volume
4. .env / env_file
5. Docker Compose параметры
6. Docker secrets / configs
7. Build args — только на этапе сборки, не запуска
Главное различие:
ENV / environment → параметры окружения внутри контейнера
CMD arguments → аргументы запускаемого процесса
volume config → конфиги как файлы
ports/volumes → параметры самого контейнера
ARG → параметры сборки образа
1. Через переменные окружения #
Самый частый способ — передать переменные окружения через -e или --env.
docker run -e DEBUG=true -e DB_HOST=postgres my_app
Внутри контейнера приложение сможет прочитать:
import os
debug = os.getenv("DEBUG")
db_host = os.getenv("DB_HOST")
Docker CLI официально поддерживает флаги -e, --env для установки environment variables и --env-file для чтения переменных из файла.
Пример для backend-приложения:
docker run \
-e POSTGRES_HOST=postgres \
-e POSTGRES_PORT=5432 \
-e POSTGRES_DB=main_db \
-e POSTGRES_USER=admin \
-e POSTGRES_PASSWORD=secret \
my_app
2. Через .env / --env-file
#
Если переменных много, их обычно выносят в файл:
DEBUG=true
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=main_db
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret
Запуск:
docker run --env-file .env my_app
Это удобнее, чем писать много -e вручную.
Но важно: .env не должен попадать в публичный репозиторий, если там есть пароли, токены или секретные ключи.
3. Через аргументы команды после имени образа #
Можно передать аргументы команде, которая запускается внутри контейнера.
Пример:
docker run python:3.12 python script.py --debug --limit=100
Здесь:
python:3.12 → образ
python script.py --debug... → команда внутри контейнера
Docker позволяет переопределять default command and options при запуске контейнера.
Например, если в образе прописано:
CMD ["python", "app.py"]
то можно переопределить запуск:
docker run my_app python app.py --debug
4. Через CMD и ENTRYPOINT
#
В Dockerfile можно заранее задать команду запуска:
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8000"]
Тогда при запуске:
docker run my_app
будет выполнено примерно:
python app.py --host 0.0.0.0 --port 8000
А если передать аргументы:
docker run my_app --port 9000
они могут заменить или дополнить CMD, в зависимости от того, как настроены ENTRYPOINT и CMD.
Dockerfile reference описывает CMD, ENTRYPOINT, ARG и другие инструкции Dockerfile.
5. Через --entrypoint
#
Можно полностью заменить entrypoint контейнера.
Например, вместо запуска приложения открыть shell:
docker run --entrypoint /bin/bash -it my_app
Или запустить другую команду:
docker run --entrypoint python my_app --version
Docker CLI поддерживает --entrypoint как способ переопределить default ENTRYPOINT образа.
6. Через Docker Compose environment
#
В docker-compose.yml параметры часто передают так:
services:
app:
image: my_app
environment:
DEBUG: "true"
POSTGRES_HOST: postgres
POSTGRES_PORT: "5432"
POSTGRES_DB: main_db
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
Docker Compose services reference показывает настройку сервисов, портов и environment variables в compose.yaml.
7. Через Docker Compose env_file
#
Можно подключить файл с переменными:
services:
app:
image: my_app
env_file:
- .env
Файл .env:
DEBUG=true
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=main_db
Docker Compose поддерживает env_file, в том числе несколько .env-файлов для одного приложения.
8. Через command в Docker Compose
#
Можно передать команду запуска контейнера через command.
services:
app:
image: my_app
command: python app.py --debug --port 8000
Или в exec-форме:
services:
app:
image: my_app
command: ["python", "app.py", "--debug", "--port", "8000"]
Это аналог передачи аргументов после имени образа в docker run.
9. Через entrypoint в Docker Compose
#
Можно переопределить entrypoint:
services:
app:
image: my_app
entrypoint: ["python", "manage.py"]
command: ["runserver", "0.0.0.0:8000"]
Итоговая команда будет примерно:
python manage.py runserver 0.0.0.0:8000
10. Через volume с конфигурационным файлом #
Иногда параметры лучше передавать не env-переменными, а файлом конфигурации.
Например:
docker run \
-v ./config.yml:/app/config.yml \
my_app
Внутри контейнера приложение читает:
/app/config.yml
Пример config.yml:
database:
host: postgres
port: 5432
name: main_db
app:
debug: true
workers: 4
Такой способ удобен для:
больших конфигов
nginx.conf
prometheus.yml
redis.conf
настроек приложения
сертификатов
11. Через Docker secrets #
Для чувствительных данных лучше использовать secrets, особенно в Docker Swarm/Kubernetes-подобных окружениях.
Идея:
пароль не передаётся как обычный ENV
секрет монтируется внутрь контейнера как файл
приложение читает секрет из файла
Примерно:
/run/secrets/db_password
Почему это лучше, чем ENV:
env-переменные легче случайно вывести в лог
их можно увидеть через inspect
секреты лучше отделены от обычной конфигурации
Для локальной разработки часто используют .env, но для production секреты лучше хранить в отдельном secret manager.
12. Через build arguments ARG
#
ARG — это параметры не запуска контейнера, а сборки образа.
Пример:
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
Сборка:
docker build --build-arg APP_VERSION=1.2.3 -t my_app .
Docker документация отдельно различает build arguments ARG и environment variables ENV: оба могут параметризовать сборку, но ARG относится именно к build-time.
Важно:
ARG доступен во время docker build
ENV доступен внутри запущенного контейнера
13. Через параметры самого контейнера #
Не все параметры передаются приложению напрямую. Некоторые настраивают контейнер как среду запуска.
Например:
docker run \
-p 8000:8000 \
-v ./data:/app/data \
--name my_app \
--restart unless-stopped \
--memory 512m \
my_app
Здесь:
-p → проброс портов
-v → volume
--name → имя контейнера
--restart → политика рестарта
--memory → ограничение памяти
Это параметры контейнера, а не аргументы приложения.
Главное различие #
Способ Для чего используется
------------------------------------------------------------
-e / --env Передать переменную окружения
--env-file Передать много env-переменных из файла
command args Передать аргументы запускаемому процессу
--entrypoint Заменить ENTRYPOINT образа
volume Передать конфигурацию как файл
compose environment Env-переменные в Docker Compose
compose env_file Env-файл в Docker Compose
compose command Команда запуска в Docker Compose
ARG / --build-arg Параметры сборки образа
ports/volumes/options Настройки контейнера
secrets Безопасная передача секретов
Пример для FastAPI #
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENTRYPOINT ["uvicorn", "main:app"]
CMD ["--host", "0.0.0.0", "--port", "8000"]
Запуск с env:
docker run \
-e DATABASE_URL=postgresql://user:pass@postgres:5432/app \
-e REDIS_HOST=redis \
-p 8000:8000 \
my_fastapi_app
Переопределить порт приложения:
docker run my_fastapi_app --host 0.0.0.0 --port 9000
Через Compose:
services:
app:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://user:pass@postgres:5432/app
REDIS_HOST: redis
command: ["--host", "0.0.0.0", "--port", "8000"]
postgres:
image: postgres:16
environment:
POSTGRES_DB: app
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
redis:
image: redis:7
Коротко #
Параметры в контейнер можно передавать через переменные окружения `-e/--env`, через файл `--env-file`, через аргументы команды после имени образа, через переопределение `CMD` или `ENTRYPOINT`, через Docker Compose `environment`, `env_file`, `command`, `entrypoint`, а также через volume с конфигурационным файлом. Для build-time параметров используется `ARG` и `--build-arg`, но это относится к сборке образа, а не к запуску контейнера.
Главное:
ENV — для конфигурации приложения.
CMD/command — для аргументов запуска.
Volume — для конфигов и файлов.
ARG — для сборки образа.
Secrets — для чувствительных данных.
9. Как в контейнерной среде организуют хранение данных вне контейнера? #
Данные, которые должны переживать удаление или пересоздание контейнера, выносят за пределы контейнера.
Основные способы:
1. Docker volumes
2. Bind mounts
3. tmpfs mounts
4. Внешнее хранилище: S3/MinIO/NFS/сетевые диски
5. Отдельная внешняя БД вместо БД внутри контейнера
Главная идея:
контейнер можно удалить и пересоздать,
но данные должны остаться
Docker прямо разделяет варианты хранения данных: volumes, bind mounts и tmpfs mounts. Volumes и bind mounts позволяют хранить данные вне lifecycle контейнера.
Почему нельзя хранить важные данные только внутри контейнера #
У контейнера есть свой writable layer. Если приложение пишет файл внутрь контейнера, эти данные живут вместе с конкретным контейнером.
Например:
/app/uploads/avatar.png
/var/lib/postgresql/data
/app/logs/app.log
Если контейнер удалить:
docker rm app
данные из его writable layer можно потерять.
Поэтому для stateful-данных используют внешнее хранение:
БД-данные → volume
пользовательские файлы → volume / object storage
конфиги → bind mount / config
логи → stdout/stderr или внешний лог-сборщик
1. Docker volume #
Docker volume — основной способ хранить persistent data.
Volume создаётся и управляется Docker отдельно от контейнера. Docker docs указывают, что volume можно создавать и управлять им вне конкретного контейнера.
Создание volume:
docker volume create postgres_data
Запуск PostgreSQL с volume:
docker run \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
Схема:
postgres_data volume
↓ mounted to
/var/lib/postgresql/data внутри контейнера
Теперь контейнер можно удалить:
docker rm -f postgres
и потом создать заново с тем же volume:
docker run \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
Данные останутся.
Docker в отдельном guide показывает такой же принцип на PostgreSQL: данные Postgres пишутся в /var/lib/postgresql, а подключённый volume позволяет перезапускать контейнер с сохранением данных.
2. Named volume в Docker Compose #
В docker-compose.yml обычно делают так:
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: app
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Здесь:
postgres_data → named volume
/var/lib/postgresql/data → путь внутри контейнера
Это хороший вариант для БД в локальной разработке.
3. Bind mount #
Bind mount — это когда конкретная папка с хоста подключается внутрь контейнера.
Пример:
docker run \
-v ./uploads:/app/uploads \
my_app
Схема:
./uploads на хосте
↓
/app/uploads внутри контейнера
Если приложение внутри контейнера запишет файл:
/app/uploads/avatar.png
на хосте появится:
./uploads/avatar.png
Docker docs описывает bind mount как прямую связь между путём на хостовой системе и путём внутри контейнера.
Volume vs bind mount #
Критерий Volume Bind mount
-----------------------------------------------------------------------
Кто управляет Docker пользователь/ОС хоста
Где лежит в Docker storage area в конкретной папке хоста
Удобно для БД, persistent data исходный код, конфиги, uploads
Переносимость выше зависит от путей хоста
Контроль пути меньше полный контроль
Production чаще volume осторожно, зависит от задачи
Development volume или bind часто bind
Для БД обычно лучше named volume.
Для разработки удобно bind mount:
services:
app:
build: .
volumes:
- ./src:/app/src
Так можно менять код на хосте, а контейнер сразу видит изменения.
4. tmpfs mount #
tmpfs хранит данные в памяти, а не на диске.
Пример:
docker run \
--tmpfs /tmp \
my_app
Данные в /tmp исчезнут после остановки контейнера.
Это не persistent storage.
Используют для:
временных файлов
кэша
секретов на время выполнения
данных, которые не должны сохраняться на диск
Docker docs отдельно указывает, что tmpfs mounts хранят данные только в памяти хоста и не сохраняются на диск.
5. Внешняя БД вместо БД в контейнере #
В production часто не хранят основную БД внутри Docker volume на том же сервере, а используют отдельную управляемую БД:
PostgreSQL managed service
RDS / Cloud SQL / Azure Database
отдельный DB-сервер
кластер PostgreSQL
Тогда контейнер приложения stateless:
app container
↓
DATABASE_URL
↓
external PostgreSQL
Пример:
DATABASE_URL=postgresql://user:pass@db.example.com:5432/app
Плюсы:
контейнер приложения можно пересоздавать без риска для данных
проще масштабировать app
бэкапы БД отдельно
репликация и мониторинг отдельно
6. Object storage для файлов #
Для пользовательских файлов часто лучше использовать не локальный volume, а object storage:
S3
MinIO
Google Cloud Storage
Azure Blob Storage
Например:
аватары
загруженные документы
изображения
видео
экспорты
Схема:
container app
↓
S3/MinIO client
↓
bucket
Это лучше, чем хранить uploads внутри контейнера, потому что несколько контейнеров приложения смогут работать с одним общим хранилищем.
7. Логи лучше не писать в файл внутри контейнера #
В контейнерной среде обычно приложение пишет логи в:
stdout
stderr
А дальше их забирает Docker / Kubernetes / logging agent.
Плохо:
/app/logs/app.log внутри контейнера
Лучше:
import logging
logging.basicConfig(level=logging.INFO)
logging.info("Application started")
Потом смотреть:
docker logs app
Для production:
stdout/stderr
↓
Docker logging driver / Fluent Bit / Vector / Logstash
↓
Elasticsearch / Loki / Cloud logging
8. Конфиги как файлы через mount #
Конфигурацию можно передавать не только через env, но и как файл:
docker run \
-v ./nginx.conf:/etc/nginx/nginx.conf:ro \
nginx
:ro означает read-only.
Для Compose:
services:
nginx:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
Так контейнер получает конфиг извне, но не может его менять.
9. Бэкапы volume #
Если данные лежат в volume, нужно отдельно думать о backup.
Например, PostgreSQL лучше бэкапить не копированием volume «на горячую», а штатными средствами:
pg_dump
pg_dumpall
Для файлового volume можно делать архив:
docker run --rm \
-v postgres_data:/data \
-v $(pwd):/backup \
alpine \
tar czf /backup/postgres_data.tar.gz /data
Но для БД безопаснее использовать инструменты самой БД, потому что простое копирование файлов может дать неконсистентное состояние.
Практический пример для Django/FastAPI + PostgreSQL + MinIO #
services:
app:
build: .
env_file:
- .env
depends_on:
- postgres
- minio
volumes:
- ./app:/app
postgres:
image: postgres:16
environment:
POSTGRES_DB: app
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
minio:
image: minio/minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: password
volumes:
- minio_data:/data
volumes:
postgres_data:
minio_data:
Здесь:
app-контейнер → можно пересоздать без потери данных
postgres_data → хранит данные PostgreSQL
minio_data → хранит объекты MinIO
./app:/app → bind mount для разработки
Что выбирать #
Задача Лучший вариант
------------------------------------------------------------
Данные PostgreSQL/MySQL named volume или внешняя БД
Redis persistence named volume, если включён AOF/RDB
Пользовательские uploads S3/MinIO или volume
Код в dev-режиме bind mount
Конфиги bind mount read-only / configs
Временные файлы tmpfs
Логи stdout/stderr + log collector
Секреты Docker secrets / secret manager
Коротко #
В контейнерной среде данные выносят за пределы контейнера, потому что сам контейнер считается временным и может быть удалён или пересоздан.
Для хранения используют Docker volumes, bind mounts, tmpfs для временных данных, а в production часто внешние сервисы: отдельную БД, S3/MinIO, сетевые хранилища. Для БД обычно используют named volumes или внешнюю managed database, для конфигов — bind mount/configs, для пользовательских файлов — object storage.
Главная мысль:
контейнер должен быть заменяемым,
а важные данные должны жить отдельно от него.
10. Что такое volume? #
Что такое volume #
Volume в Docker — это отдельное хранилище данных, которое подключается внутрь контейнера как папка.
Главная задача volume — сохранить данные вне жизненного цикла контейнера.
контейнер удалили → данные в volume остались
контейнер пересоздали → подключили тот же volume → данные снова доступны
Docker описывает volumes как persistent data stores для контейнеров, которые создаются и управляются Docker отдельно от конкретного контейнера.
Зачем нужен volume #
Контейнер считается временным объектом. Его можно остановить, удалить, пересобрать и создать заново.
Но данные, например:
данные PostgreSQL
загруженные файлы
логи
кэш
данные MinIO
не должны исчезать вместе с контейнером.
Без volume:
PostgreSQL пишет данные внутрь контейнера
↓
контейнер удалили
↓
данные потерялись
С volume:
PostgreSQL пишет данные в /var/lib/postgresql/data
↓
этот путь подключён к volume
↓
контейнер удалили
↓
volume остался
Docker прямо указывает, что volumes позволяют сохранять данные за пределами lifecycle отдельного контейнера.
Как это выглядит #
Например, создаём volume:
docker volume create postgres_data
Запускаем PostgreSQL и подключаем volume:
docker run \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
Здесь:
postgres_data → volume на стороне Docker
/var/lib/postgresql/data → путь внутри контейнера
То есть PostgreSQL думает, что пишет данные в обычную папку внутри контейнера, но фактически данные сохраняются во внешнем Docker volume.
Схема #
Docker volume: postgres_data
↓
монтируется внутрь контейнера
↓
/var/lib/postgresql/data
↓
PostgreSQL пишет туда файлы БД
Контейнер можно удалить:
docker rm -f postgres
А потом создать новый контейнер с тем же volume:
docker run \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
Данные останутся.
Volume в Docker Compose #
В docker-compose.yml это обычно выглядит так:
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: app
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Здесь внизу объявлен named volume:
volumes:
postgres_data:
А внутри сервиса он подключается сюда:
- postgres_data:/var/lib/postgresql/data
Где физически лежит volume #
На Linux Docker volumes обычно хранятся в Docker-managed area, например:
/var/lib/docker/volumes/
Но напрямую туда обычно не лезут. Правильнее управлять volume через Docker CLI:
docker volume ls
docker volume inspect postgres_data
docker volume rm postgres_data
Docker подчёркивает, что volume создаётся и управляется Docker, а не вручную как обычная папка проекта.
Volume и bind mount — разница #
Volume часто путают с bind mount.
Volume #
docker run -v postgres_data:/var/lib/postgresql/data postgres:16
postgres_data → управляется Docker
Используется для:
данных БД
persistent storage
данных сервисов
production-like окружения
Bind mount #
docker run -v ./app:/app my_app
./app → конкретная папка на хосте
Используется для:
исходного кода в dev-режиме
конфигов
локальных файлов
быстрой разработки
Docker описывает bind mount как подключение конкретного host path в container path.
Кратко:
volume → Docker сам управляет местом хранения
bind mount → ты сам указываешь папку на хосте
Volume не равен папке внутри контейнера #
Важно:
volume — это не просто директория в контейнере
Это внешнее хранилище, которое монтируется внутрь контейнера по определённому пути.
Например:
postgres_data volume
может быть подключён как:
/var/lib/postgresql/data
А для другого контейнера — как:
/backup/postgres
То есть volume существует отдельно, а контейнер только получает к нему доступ.
Можно ли подключить один volume к нескольким контейнерам #
Да, один volume можно подключить к нескольким контейнерам.
Например:
app container
worker container
могут читать общие файлы из одного volume.
Но с БД нужно быть осторожным. Нельзя просто подключить один и тот же PostgreSQL data volume к двум PostgreSQL-контейнерам, которые одновременно пишут в него. Это может повредить данные.
Нормальный вариант:
один PostgreSQL container → один data volume
несколько app containers → подключаются к PostgreSQL по сети
Когда использовать volume #
Volume используют, когда данные должны сохраняться:
PostgreSQL/MySQL data
Redis persistence
MinIO data
загруженные пользователем файлы
данные очередей
локальное persistent-хранилище сервиса
Пример для MinIO:
services:
minio:
image: minio/minio
command: server /data --console-address ":9001"
volumes:
- minio_data:/data
volumes:
minio_data:
Когда volume не нужен #
Volume обычно не нужен для:
кода, который уже находится в image
временных файлов
данных, которые можно пересоздать
логов, если они идут в stdout/stderr
секретов, которые лучше передавать через secrets
Для временных данных можно использовать tmpfs, который хранит данные в памяти и не сохраняет их после остановки контейнера. Docker отдельно выделяет tmpfs mounts как отдельный тип mount, отличный от volumes и bind mounts.
Коротко #
Volume — это Docker-managed хранилище данных, которое монтируется внутрь контейнера как директория и позволяет сохранять данные независимо от жизненного цикла контейнера.
Контейнер можно удалить или пересоздать, а данные в volume останутся. Обычно volume используют для баз данных, файловых хранилищ, Redis persistence, MinIO и других stateful-данных.
Главная мысль:
контейнер временный,
volume постоянный.
11. Какие основные сущности используются в Docker? #
Основные сущности Docker:
image → шаблон для запуска контейнера
container → запущенный экземпляр image
Dockerfile → инструкция для сборки image
volume → постоянное хранилище данных
network → сеть для связи контейнеров
registry → хранилище images
daemon → Docker-сервер, который управляет объектами
client/CLI → команда docker, через которую мы управляем Docker
Compose → описание multi-container приложения в YAML
Docker в официальном overview прямо перечисляет основные Docker objects: images, containers, networks, volumes, plugins и другие объекты.
1. Image #
Image — это неизменяемый шаблон, из которого создаётся контейнер.
В образе обычно находятся:
операционная userland-база
runtime: Python / Node / Java
библиотеки
зависимости
код приложения
команда запуска
Пример:
docker pull python:3.12-slim
или:
docker build -t my_app .
Образ сам по себе не является запущенным приложением. Это как «слепок» окружения.
image → используется для создания container
Docker указывает, что docker run создаёт контейнер на основе image reference, например ubuntu:24.04.
2. Container #
Container — это запущенный экземпляр образа.
Если image — это шаблон, то container — это уже работающий процесс или группа процессов.
image: python:3.12-slim
↓
container: запущенное Python-приложение
Пример:
docker run python:3.12-slim python --version
Или для приложения:
docker run -p 8000:8000 my_app
Контейнер можно:
docker start app
docker stop app
docker restart app
docker rm app
Важно:
контейнер можно удалить,
но image при этом останется
3. Dockerfile #
Dockerfile — это текстовый файл с инструкциями для сборки образа.
Пример:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
Docker описывает Dockerfile как text-based document, который используется для создания container image и содержит инструкции: какие команды выполнить, какие файлы скопировать и какую команду запуска задать.
Схема:
Dockerfile
↓ docker build
image
↓ docker run
container
4. Layer #
Layer — это слой образа.
Каждая инструкция в Dockerfile может создавать новый слой:
FROM python:3.12-slim # слой
WORKDIR /app # слой
COPY requirements.txt . # слой
RUN pip install ... # слой
COPY . . # слой
Docker использует слои для кэширования и экономии места.
Например, если requirements.txt не изменился, Docker может переиспользовать слой с установленными зависимостями.
image = набор read-only layers
container = image layers + writable layer
Docker storage driver хранит image layers и writable layer контейнера; writable layer подходит для временных runtime-данных, но не сохраняется после удаления контейнера.
5. Volume #
Volume — это постоянное хранилище данных, управляемое Docker.
Используется, чтобы данные не исчезали при удалении контейнера.
Пример:
docker volume create postgres_data
Запуск PostgreSQL с volume:
docker run \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16
Здесь:
postgres_data → Docker volume
/var/lib/postgresql/data → путь внутри контейнера
Volumes — это persistent data stores для контейнеров, которые создаются и управляются Docker.
6. Network #
Network — это Docker-сеть, через которую контейнеры общаются друг с другом и с внешним миром.
Пример:
docker network create app_network
Запуск контейнера в сети:
docker run --network app_network --name redis redis:7
docker run --network app_network --name app my_app
Теперь контейнер app может обращаться к Redis по имени:
redis:6379
Docker networking отвечает за возможность контейнеров подключаться и общаться друг с другом, а также с внешними non-Docker сервисами.
7. Registry #
Registry — это хранилище Docker-образов.
Самый известный registry:
Docker Hub
Пример:
docker pull nginx
docker push my_user/my_app:1.0
То есть:
registry хранит images
docker pull скачивает image
docker push отправляет image
Docker Hub — это публичный registry service, где пользователи могут хранить, публиковать и управлять container images.
8. Repository и tag #
Внутри registry образы организованы по repositories и tags.
Пример:
python:3.12-slim
Здесь:
python → repository
3.12-slim → tag
Другой пример:
postgres:16
redis:7
nginx:1.27
Tag обычно обозначает версию или вариант образа.
Плохо для production:
FROM python:latest
Лучше:
FROM python:3.12-slim
Потому что latest может измениться и сломать воспроизводимость сборки.
9. Docker daemon #
Docker daemon — это фоновый процесс Docker Engine, который реально управляет Docker-объектами.
Он создаёт и управляет:
images
containers
networks
volumes
Docker Engine docs указывают, что daemon создаёт и управляет Docker objects, включая images, containers, networks и volumes.
Схема:
docker CLI
↓
Docker API
↓
Docker daemon
↓
containers/images/networks/volumes
10. Docker CLI / Client #
Docker CLI — это команда docker, которую запускает пользователь.
Примеры:
docker build -t my_app .
docker run my_app
docker ps
docker logs app
docker exec -it app bash
docker stop app
CLI сам не запускает контейнеры напрямую. Он отправляет команды Docker daemon через Docker API.
ты пишешь docker run
↓
CLI отправляет запрос daemon
↓
daemon создаёт контейнер
11. Docker Compose #
Docker Compose — это инструмент для описания и запуска multi-container приложения через YAML-файл.
Пример:
services:
app:
build: .
ports:
- "8000:8000"
depends_on:
- postgres
- redis
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
postgres_data:
Запуск:
docker compose up -d
Compose позволяет управлять всем application stack: services, networks и volumes в одном YAML-файле.
12. Service в Docker Compose #
Service в Compose — это описание одного компонента приложения.
Например:
services:
app:
build: .
postgres:
image: postgres:16
redis:
image: redis:7
Здесь три сервиса:
app
postgres
redis
Каждый service обычно создаёт один или несколько контейнеров.
service → описание
container → конкретный запущенный экземпляр service
Compose file используется для настройки services, networks, volumes и других частей Docker-приложения.
Общая схема Docker #
Dockerfile
↓ docker build
Image
↓ docker run
Container
Дополнительно:
Volume → хранит данные контейнера
Network → связывает контейнеры
Registry → хранит images
Daemon → управляет Docker-объектами
CLI → отправляет команды daemon
Compose → описывает несколько контейнеров в YAML
Пример на backend-проекте #
Для проекта:
FastAPI / Django
PostgreSQL
Redis
Docker-сущности будут такими:
Dockerfile
→ описывает сборку app image
image my_app
→ образ приложения
container app
→ запущенное приложение
image postgres:16
→ образ PostgreSQL
container postgres
→ запущенная БД
volume postgres_data
→ хранит данные PostgreSQL
network backend_network
→ сеть между app, postgres и redis
compose.yml
→ описывает все сервисы проекта
Коротко #
Основные сущности Docker — это image, container, Dockerfile, volume, network, registry, Docker daemon, Docker CLI и Docker Compose.
Image — шаблон для запуска контейнера.
Container — запущенный экземпляр image.
Dockerfile — инструкция для сборки image.
Volume — постоянное хранилище данных.
Network — сеть для связи контейнеров.
Registry — хранилище образов.
Daemon — процесс, который управляет Docker-объектами.
CLI — клиент для отправки команд Docker daemon.
Compose — инструмент для описания multi-container приложения.
Главная цепочка:
Dockerfile → image → container
А вокруг неё:
volume → данные
network → связь
registry → хранение образов
compose → управление несколькими сервисами
12. Как подключиться к запущенному Docker-контейнеру через терминал? (docker excec ...) #
Команда #
Чтобы подключиться к уже запущенному контейнеру через терминал, используется:
docker exec -it <container_name_or_id> bash
Например:
docker exec -it my_app bash
docker exec запускает новую команду внутри уже работающего контейнера. В официальной документации Docker указано, что docker exec выполняет команду в running container.
Если bash нет
#
В минимальных образах bash может отсутствовать. Тогда используют sh:
docker exec -it <container_name_or_id> sh
Например:
docker exec -it redis sh
Для Alpine-образов чаще всего:
docker exec -it <container_name_or_id> /bin/sh
Как узнать имя контейнера #
Сначала посмотреть запущенные контейнеры:
docker ps
Пример вывода:
CONTAINER ID IMAGE NAMES
a1b2c3d4e5f6 postgres:16 postgres
f7g8h9i0j1k2 my_app app
После этого можно подключиться по имени:
docker exec -it app bash
или по CONTAINER ID:
docker exec -it a1b2c3d4e5f6 bash
Что означают флаги -it
#
-i → interactive, оставить stdin открытым
-t → выделить pseudo-TTY, чтобы терминал работал нормально
Обычно для входа в контейнер используют их вместе:
docker exec -it app bash
Через Docker Compose #
Если контейнер запущен через Compose, можно использовать:
docker compose exec <service_name> bash
Например:
docker compose exec app bash
или:
docker compose exec postgres bash
В Docker Compose exec по умолчанию запускается в интерактивном режиме и выделяет TTY, тогда как для обычного docker exec нужно явно указывать -it.
Отличие от docker attach
#
docker exec создаёт новый процесс внутри контейнера:
docker exec -it app bash
А docker attach подключает терминал к уже запущенному основному процессу контейнера. Docker описывает attach как подключение stdin/stdout/stderr терминала к running container.
Для обычного входа внутрь контейнера почти всегда лучше использовать:
docker exec -it <container> bash
Коротко #
docker ps
docker exec -it <container_name_or_id> bash
Если bash нет:
docker exec -it <container_name_or_id> sh
Для Compose:
docker compose exec <service_name> bash
13. Способы оптимизации Docker-образов и повышения эффективности их использования? #
Оптимизация Docker-образов обычно решает 4 задачи:
1. Уменьшить размер image
2. Ускорить docker build
3. Ускорить pull/push/deploy
4. Уменьшить количество уязвимостей и лишних зависимостей
Главная идея:
в финальном образе должно быть только то,
что реально нужно приложению для запуска
Docker в best practices отдельно рекомендует использовать multi-stage builds, держать build context небольшим и эффективно использовать cache.
1. Использовать минимальный базовый образ #
Плохо:
FROM ubuntu:latest
Для Python backend чаще лучше:
FROM python:3.12-slim
Почему:
меньше размер
меньше лишних пакетов
меньше attack surface
быстрее скачивание образа
Но минимальный образ не должен ломать зависимости. Например, alpine маленький, но из-за musl libc может создавать проблемы с Python-пакетами с нативными расширениями: cryptography, psycopg2, numpy, pillow, lxml.
Практический default для Python backend:
FROM python:3.12-slim
2. Не использовать latest
#
Плохо:
FROM python:latest
Лучше:
FROM python:3.12-slim
latest может измениться, и сборка неожиданно начнёт использовать другую версию runtime.
Для максимальной воспроизводимости можно фиксировать digest:
FROM python:3.12-slim@sha256:<digest>
Но в обычной практике чаще фиксируют конкретный tag.
3. Использовать multi-stage build #
Multi-stage build позволяет собирать зависимости в одном образе, а в финальный образ копировать только результат. Docker прямо указывает, что multi-stage builds помогают уменьшить размер финального image и отделить build-окружение от runtime-окружения.
Плохой вариант:
FROM python:3.12-slim
RUN apt-get update && apt-get install -y gcc build-essential
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]
Проблема: gcc, build-essential и build-зависимости остаются в production-образе.
Лучше:
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
FROM python:3.12-slim AS runtime
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY . .
CMD ["python", "main.py"]
Смысл:
builder → содержит инструменты сборки
runtime → содержит только runtime-зависимости и приложение
4. Использовать .dockerignore
#
Build context — это файлы, которые Docker отправляет на сборку. Чем больше context, тем медленнее сборка и хуже cache. Docker отдельно описывает .dockerignore как способ исключать файлы из build context.
Пример .dockerignore:
.git
__pycache__
*.pyc
.pytest_cache
.mypy_cache
.ruff_cache
.coverage
htmlcov
.env
.venv
venv
node_modules
dist
build
*.log
Что не надо отправлять в image:
.git
локальное venv
кэш тестов
логи
coverage-отчёты
секретные .env
node_modules, если они не нужны для сборки
5. Правильно располагать инструкции для cache #
Docker кэширует слои. Поэтому сначала нужно копировать редко изменяющиеся файлы, устанавливать зависимости, и только потом копировать весь код.
Плохо:
COPY . .
RUN pip install -r requirements.txt
Если изменится любой файл проекта, слой COPY . . изменится, и зависимости будут устанавливаться заново.
Лучше:
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
Так зависимости переустановятся только при изменении requirements.txt.
Для Poetry/uv аналогично:
COPY pyproject.toml poetry.lock .
RUN poetry install --only main --no-root
COPY . .
или:
COPY pyproject.toml uv.lock .
RUN uv sync --frozen --no-dev
COPY . .
6. Не хранить package manager cache в image #
Для Python:
RUN pip install --no-cache-dir -r requirements.txt
Для apt:
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
Важно объединять apt-get update, apt-get install и очистку cache в одном RUN, иначе мусор может остаться в предыдущем слое.
Плохо:
RUN apt-get update
RUN apt-get install -y libpq5
RUN rm -rf /var/lib/apt/lists/*
Лучше:
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
7. Использовать --no-install-recommends
#
В Debian/Ubuntu-based образах apt может поставить дополнительные рекомендованные пакеты.
Лучше:
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
libpq5 \
&& rm -rf /var/lib/apt/lists/*
Это уменьшает количество лишних пакетов.
8. Использовать BuildKit cache mounts #
BuildKit позволяет использовать cache mounts, чтобы ускорять повторные сборки без сохранения мусора в финальном image. Docker указывает cache mounts как один из способов оптимизации build cache.
Пример для pip:
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
COPY . .
Что это даёт:
pip cache используется между сборками
но не попадает в финальный слой image
9. Не запускать контейнер от root #
Это больше про безопасность, но влияет на качество production-образа.
Плохо:
FROM python:3.12-slim
COPY . .
CMD ["python", "main.py"]
По умолчанию процесс может запускаться от root.
Лучше:
FROM python:3.12-slim
WORKDIR /app
RUN useradd --create-home --shell /usr/sbin/nologin appuser
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER appuser
CMD ["python", "main.py"]
Если приложению не нужны root-права, их лучше не давать.
10. Не класть секреты в image #
Плохо:
ENV DATABASE_PASSWORD=secret
COPY .env .env
Проблема: секреты могут попасть в слои image, registry, CI logs, docker history.
Лучше:
переменные окружения при запуске
Docker secrets
Kubernetes Secrets
Vault / cloud secret manager
Пример запуска:
docker run --env-file .env my_app
Но .env не должен попадать в image и публичный репозиторий.
11. Разделять dev и production образы #
Для разработки нужны:
pytest
ruff
watch reload
debug tools
shell utilities
Для production они обычно не нужны.
Можно сделать stages:
FROM python:3.12-slim AS base
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM base AS dev
RUN pip install --no-cache-dir pytest ruff
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0"]
FROM base AS prod
CMD ["gunicorn", "main:app"]
Сборка dev:
docker build --target dev -t my_app:dev .
Сборка prod:
docker build --target prod -t my_app:prod .
12. Использовать внешнюю БД и volume для данных #
Образ не должен содержать runtime-данные:
данные БД
загруженные файлы
логи
кэш
Для этого используют:
Docker volumes
bind mounts
S3/MinIO
внешнюю PostgreSQL
stdout/stderr для логов
Плохой подход:
копировать uploads в image
писать важные данные в writable layer контейнера
Правильнее:
services:
postgres:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
13. Минимизировать количество слоёв, но не фанатично #
Каждая инструкция Dockerfile создаёт слой. Но современная оптимизация — это не просто «сделать один огромный RUN».
Плохо:
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
RUN apt-get install -y gcc
Лучше:
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
gcc \
&& rm -rf /var/lib/apt/lists/*
Но не надо превращать весь Dockerfile в одну нечитаемую строку. Важнее:
правильный cache
отсутствие мусора
понятность Dockerfile
14. Проверять image на уязвимости #
Базовый образ и системные пакеты могут содержать CVE. Docker Scout умеет анализировать состав images и показывать уязвимости.
Примеры:
docker scout cves my_app:latest
Рекомендации по обновлению базового образа:
docker scout recommendations my_app:latest
Docker Scout recommendations показывает рекомендации по обновлению base image, включая потенциальное уменьшение количества уязвимостей или размера image.
Также часто используют:
trivy image my_app:latest
grype my_app:latest
15. Использовать external cache в CI/CD #
В CI/CD runner часто запускается в чистом окружении, поэтому локальный Docker cache пропадает.
BuildKit поддерживает external cache backends, чтобы cache можно было экспортировать и импортировать между сборками. Docker указывает, что external cache почти необходим в CI/CD-средах, где нет постоянного локального cache между запусками.
Идея:
build cache сохраняется в registry/GitHub Actions cache
следующий pipeline использует его
сборка идёт быстрее
16. Использовать multi-platform аккуратно #
Если образ нужен только под одну архитектуру, не нужно без необходимости собирать всё подряд.
Например:
docker buildx build --platform linux/amd64 -t my_app .
А если реально нужен multi-arch:
docker buildx build --platform linux/amd64,linux/arm64 -t my_app .
Multi-platform сборка полезна, но может быть медленнее.
17. Удалять лишние файлы из финального image #
Не должны попадать в production image:
tests/
docs/
.git/
.env
coverage reports
local configs
IDE files
cache
Это решается через:
.dockerignore
COPY только нужных директорий
multi-stage build
Например:
COPY app ./app
COPY pyproject.toml uv.lock ./
лучше, чем бездумно:
COPY . .
18. Выбирать правильный способ запуска приложения #
Для production не стоит запускать dev-сервер.
Плохо для FastAPI production:
CMD ["uvicorn", "main:app", "--reload"]
--reload нужен для разработки, но не для production.
Лучше:
CMD ["gunicorn", "main:app", "-k", "uvicorn.workers.UvicornWorker"]
Или обычный uvicorn без reload, если архитектура это допускает:
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
19. Использовать healthcheck осознанно #
HEALTHCHECK помогает оркестратору понимать, живо ли приложение.
Пример:
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
Но healthcheck не должен быть тяжёлым:
не ходить в тяжёлые отчёты
не делать дорогие DB-запросы
не создавать нагрузку на приложение
20. Практический пример оптимизированного Dockerfile для Python #
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS builder
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip wheel --wheel-dir /wheels -r requirements.txt
FROM python:3.12-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN useradd --create-home --shell /usr/sbin/nologin appuser \
&& apt-get update \
&& apt-get install -y --no-install-recommends libpq5 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* \
&& rm -rf /wheels
COPY app ./app
USER appuser
CMD ["python", "-m", "app"]
Что здесь оптимизировано:
slim base image
multi-stage build
build tools не попали в runtime
pip cache не попал в image
apt cache очищен
зависимости кэшируются отдельно от кода
запуск не от root
копируется только нужная папка app
Чеклист оптимизации #
Базовый образ:
[ ] не latest
[ ] official image
[ ] slim/minimal, но совместимый
Dockerfile:
[ ] зависимости копируются до кода
[ ] есть .dockerignore
[ ] используется multi-stage build
[ ] build tools не остаются в runtime
[ ] apt cache очищается в том же RUN
[ ] pip/npm cache не попадает в image
Безопасность:
[ ] нет секретов в image
[ ] процесс не запускается от root
[ ] image сканируется на CVE
[ ] базовый образ обновляется
Runtime:
[ ] данные вынесены в volumes/S3/БД
[ ] логи идут в stdout/stderr
[ ] dev-зависимости не попали в prod
[ ] не используется --reload в production
Коротко #
Docker-образ оптимизируют за счёт выбора минимального и фиксированного базового образа, использования .dockerignore, правильного порядка слоёв для cache, multi-stage build, удаления build-зависимостей из финального образа, очистки package-manager cache, отказа от latest, запуска не от root, вынесения данных в volumes и регулярного сканирования образов на уязвимости.
Главная мысль:
эффективный Docker-образ должен быть маленьким, воспроизводимым, безопасным и содержать только то, что нужно приложению для запуска.
14. Использование многоэтапной сборки Docker-образов на базе Alpine. #
Многоэтапная сборка Docker-образа на Alpine — это подход, при котором образ делится минимум на 2 стадии:
builder stage
└─ ставим компиляторы, dev-зависимости, собираем приложение
runtime stage
└─ кладём только готовое приложение и runtime-зависимости
В Docker это делается через несколько FROM. Каждый FROM начинает отдельную стадию, а в финальный образ можно скопировать только нужные файлы через COPY --from=.... Это официально рекомендуемый способ уменьшать размер итогового образа и отделять build-зависимости от runtime-зависимостей.
Зачем это делают #
Главная цель — не тащить в production-образ лишнее:
gcc
make
build-base
headers
dev-библиотеки
кэш пакетных менеджеров
исходники сборки
тестовые файлы
Итоговый образ становится:
меньше по размеру;
быстрее скачивается и разворачивается;
содержит меньше лишних пакетов;
имеет меньшую поверхность атаки;
ближе к production-окружению.
Docker прямо указывает, что multi-stage builds позволяют оставить в финальном образе только то, что нужно для запуска приложения.
Пример для Python/FastAPI на Alpine #
# syntax=docker/dockerfile:1
FROM python:3.12-alpine AS builder
WORKDIR /app
RUN apk add --no-cache \
build-base \
libffi-dev \
openssl-dev
COPY requirements.txt .
RUN python -m venv /venv \
&& /venv/bin/pip install --upgrade pip \
&& /venv/bin/pip install --no-cache-dir -r requirements.txt
FROM python:3.12-alpine AS runtime
ENV PATH="/venv/bin:$PATH" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /venv /venv
COPY . .
USER app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Что здесь происходит #
FROM python:3.12-alpine AS builder
Создаётся временный образ для сборки. В нём можно ставить тяжёлые зависимости: компиляторы, headers, dev-библиотеки.
RUN apk add --no-cache build-base ...
apk — пакетный менеджер Alpine. --no-cache нужен, чтобы не сохранять индекс пакетов внутри слоя.
RUN python -m venv /venv ...
Создаётся виртуальное окружение и туда устанавливаются Python-зависимости.
FROM python:3.12-alpine AS runtime
Начинается чистый финальный образ.
COPY --from=builder /venv /venv
Из build-стадии копируется только готовое виртуальное окружение, а не весь набор компиляторов и dev-пакетов.
Почему именно Alpine #
Alpine часто используют как минимальную базу для контейнеров. Официальный Alpine-образ доступен как Docker Official Image, а Docker Official Images проходят централизованный процесс публикации и сопровождения.
Но Alpine не всегда лучший выбор.
Главная особенность Alpine — он использует musl libc, а не glibc. Это может влиять на совместимость некоторых бинарных зависимостей. Alpine Wiki прямо указывает, что Alpine использует musl как стандартную C-библиотеку.
Для Python это особенно важно: часть пакетов с C/C++-расширениями может не иметь готовых колёс под musl, из-за чего они будут собираться из исходников. PyPA для этого ввела отдельные musllinux platform tags, потому что обычные manylinux-колёса ориентированы на glibc-based Linux, а Alpine использует musl.
Когда Alpine подходит #
Alpine хорошо подходит, когда:
приложение лёгкое;
зависимости простые;
нет тяжёлых C/C++ Python-пакетов;
важен маленький размер образа;
команда понимает особенности musl;
Например:
Go-приложение
простое Python API
небольшой FastAPI-сервис
утилиты
sidecar-контейнеры
Когда Alpine может быть плохим выбором #
Alpine может создать проблемы, если в проекте есть:
numpy
pandas
opencv
cryptography
psycopg2
lxml
Pillow
grpcio
другие пакеты с native extensions
Проблемы могут быть такие:
долгая сборка зависимостей;
ошибки компиляции;
нужны дополнительные apk-пакеты;
финальный образ неожиданно становится не таким маленьким;
отличия поведения из-за musl/glibc;
Для Python-проектов с тяжёлыми зависимостями часто практичнее использовать не Alpine, а Debian-based образ:
FROM python:3.12-slim
Он обычно больше, но совместимость с Python-библиотеками часто проще, потому что glibc-based окружение лучше совпадает с большинством Linux wheels.
Более правильный вариант для Python #
Для production лучше не копировать весь проект до установки зависимостей, чтобы Docker мог кэшировать слой с зависимостями:
FROM python:3.12-alpine AS builder
WORKDIR /app
RUN apk add --no-cache build-base libffi-dev openssl-dev
COPY requirements.txt .
RUN python -m venv /venv \
&& /venv/bin/pip install --upgrade pip \
&& /venv/bin/pip install --no-cache-dir -r requirements.txt
FROM python:3.12-alpine AS runtime
ENV PATH="/venv/bin:$PATH" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /venv /venv
COPY app ./app
USER app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Структура:
.
├── Dockerfile
├── requirements.txt
└── app
├── main.py
└── ...
15. Где в Linux-системе обычно хранятся глобальные конфигурационные файлы? #
Где хранятся глобальные конфиги #
В Linux глобальные системные конфигурационные файлы обычно хранятся в каталоге:
/etc
Например:
/etc/passwd
/etc/group
/etc/hosts
/etc/fstab
/etc/ssh/sshd_config
/etc/nginx/nginx.conf
/etc/systemd/system/
Что означает /etc
#
/etc — это каталог для конфигурационных файлов, специфичных для конкретной машины. По FHS, там должны лежать статические конфигурационные файлы, а не исполняемые бинарники.
То есть /etc хранит настройки уровня всей системы:
настройки пользователей
настройки сети
настройки сервисов
настройки systemd
конфиги демонов
конфиги пакетных сервисов
Важно отличать #
Пользовательские конфиги обычно лежат не в /etc, а в домашнем каталоге пользователя:
~/.config/
~/.bashrc
~/.ssh/config
А глобальные настройки, действующие для всей системы или всех пользователей, обычно находятся именно в:
/etc
Linux man-pages также описывает /etc как каталог, содержащий конфигурационные файлы, локальные для машины.
16. Как посмотреть все контейнеры Docker, включая остановленные? (docker ps -a ...) #
Команда #
Чтобы посмотреть все Docker-контейнеры, включая остановленные:
docker ps -a
Полная современная форма той же команды:
docker container ls -a
Что означает -a
#
Флаг -a / --all показывает не только запущенные контейнеры, но и остановленные. Без него docker ps показывает только running-контейнеры. Это указано в справке Docker CLI для docker container ls.
Пример #
docker ps -a
Вывод будет примерно такой:
CONTAINER ID IMAGE COMMAND STATUS NAMES
a1b2c3d4e5f6 nginx "/docker..." Up 10 minutes web
b7c8d9e0f1a2 postgres "docker..." Exited (0) 2 hours ago db
Где:
Up ... контейнер запущен
Exited ... контейнер остановлен
Для короткого вывода только ID:
docker ps -aq
или:
docker container ls -aq
17. Что такое Docker network? #
Что такое Docker network #
Docker network — это сетевой механизм Docker, который позволяет контейнерам общаться:
контейнер ↔ контейнер
контейнер ↔ host-машина
контейнер ↔ внешний интернет
внешний клиент ↔ контейнер через опубликованный порт
В официальной документации Docker networking описывается как возможность контейнеров подключаться и обмениваться данными друг с другом и с внешними, не-Docker сетевыми сервисами.
Зачем нужен Docker network #
Контейнеры изолированы друг от друга. Docker network задаёт правила:
какие контейнеры видят друг друга;
по каким IP/именам они доступны;
какие порты открыты наружу;
может ли контейнер выходить в интернет;
изолирована ли сеть от других контейнеров.
Например, можно сделать сеть только для backend и базы данных:
backend ───── postgres
│
└──── доступ есть
frontend ──── доступа к postgres напрямую нет
Простой пример #
Создать сеть:
docker network create app-network
Запустить PostgreSQL в этой сети:
docker run -d \
--name postgres \
--network app-network \
postgres
Запустить backend в той же сети:
docker run -d \
--name backend \
--network app-network \
my-backend-image
Теперь backend может обращаться к PostgreSQL по имени контейнера:
postgres:5432
То есть внутри Docker-сети имя контейнера работает как hostname.
Основные команды #
Посмотреть сети:
docker network ls
Посмотреть детали сети:
docker network inspect app-network
Подключить контейнер к сети:
docker network connect app-network backend
Отключить контейнер от сети:
docker network disconnect app-network backend
Команда docker network используется для управления сетями: создание, просмотр, удаление, подключение и отключение контейнеров.
Основные типы Docker network #
bridge
#
Самый частый вариант для обычного Docker на одной машине.
container A ─┐
container B ─┼── docker bridge ── host ── internet
container C ─┘
bridge — это сеть через программный мост. Контейнеры в одной bridge-сети могут общаться друг с другом, а от контейнеров в других сетях они изолированы.
Пример:
docker network create --driver bridge app-network
host
#
Контейнер использует сетевое пространство host-машины напрямую.
container = сеть host-машины
То есть контейнер не получает отдельную сетевую изоляцию как при bridge.
Пример:
docker run --network host nginx
Минус: хуже изоляция, возможны конфликты портов.
none
#
Контейнер запускается без сетевого доступа.
только loopback-интерфейс внутри контейнера
Пример:
docker run --network none alpine
Docker указывает, что при --network none контейнер получает только loopback-устройство.
overlay
#
Используется, когда контейнеры должны общаться между разными Docker-хостами, например в Docker Swarm.
host 1: container A ─┐
├── overlay network
host 2: container B ─┘
Docker описывает overlay как распределённую сеть между несколькими Docker daemon hosts. (
Docker Documentation)
macvlan
#
Позволяет контейнеру выглядеть в физической сети как отдельное устройство со своим MAC-адресом.
Используется реже, обычно когда контейнер должен быть полноценным участником внешней LAN-сети.
Docker Compose и сети #
В docker-compose.yml Docker обычно сам создаёт сеть для проекта. Сервисы внутри одного Compose-проекта могут обращаться друг к другу по имени сервиса. Docker Compose по умолчанию создаёт одну сеть для приложения, а контейнеры сервисов доступны друг другу по service name.
Пример:
services:
backend:
build: .
depends_on:
- postgres
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: password
Внутри backend подключение к БД будет не через localhost, а так:
postgres:5432
Потому что postgres — это имя сервиса в Docker Compose.
Важный момент про localhost
#
Внутри контейнера:
localhost
означает сам контейнер, а не host-машину и не другой контейнер.
То есть если backend-контейнер пытается подключиться так:
localhost:5432
он ищет PostgreSQL внутри самого backend-контейнера.
Правильно в Docker-сети:
postgres:5432
или имя нужного контейнера/сервиса.
Коротко #
Docker network — это механизм сетевой изоляции и связи контейнеров. Он позволяет объединять контейнеры в отдельные сети, настраивать их взаимодействие, DNS-имена, доступ наружу и публикацию портов. Чаще всего используется bridge-сеть: контейнеры в одной сети могут обращаться друг к другу по имени контейнера или сервиса, а от других сетей они изолированы. Для нескольких Docker-хостов используется overlay, для полного отключения сети — none, а для использования сети host-машины — host.
18. Что такое Docker-Compose и зачем он нужен | Как масштабировать приложение с помощью Docker? #
Что такое Docker Compose #
Docker Compose — это инструмент для запуска и управления multi-container приложениями через один YAML-файл. Вместо того чтобы вручную запускать каждый контейнер через длинные docker run, ты описываешь сервисы в docker-compose.yml, а потом поднимаешь всё одной командой. Docker официально описывает Compose как инструмент для определения и запуска multi-container applications.
Пример без Compose:
docker network create app-network
docker run -d --name postgres \
--network app-network \
-e POSTGRES_PASSWORD=password \
postgres:16
docker run -d --name redis \
--network app-network \
redis:7
docker run -d --name backend \
--network app-network \
-p 8000:8000 \
my-backend-image
С Compose это превращается в один файл:
services:
backend:
build: .
ports:
- "8000:8000"
depends_on:
- postgres
- redis
postgres:
image: postgres:16
environment:
POSTGRES_PASSWORD: password
redis:
image: redis:7
Запуск:
docker compose up
Запуск в фоне:
docker compose up -d
Остановка:
docker compose down
Зачем нужен Docker Compose #
Compose нужен, когда приложение состоит не из одного контейнера, а из нескольких связанных сервисов:
backend
frontend
postgres
redis
celery
rabbitmq
minio
nginx
Он позволяет описать:
какие контейнеры запускать;
какие образы использовать;
какие переменные окружения передать;
какие порты открыть;
какие volume подключить;
какие сети создать;
какие сервисы зависят друг от друга.
Docker Compose умеет запускать, останавливать, пересобирать сервисы, смотреть их статус, читать логи и выполнять разовые команды внутри сервисов. Это перечислено в официальной документации Docker Compose.
Типичный docker-compose.yml
#
services:
backend:
build: .
container_name: backend
ports:
- "8000:8000"
environment:
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: app_db
DB_USER: app_user
DB_PASSWORD: password
REDIS_HOST: redis
depends_on:
- postgres
- redis
postgres:
image: postgres:16
container_name: postgres
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7
container_name: redis
volumes:
postgres_data:
Внутри backend подключение к базе будет не через localhost, а через имя сервиса:
postgres:5432
Потому что Compose создаёт общую сеть для сервисов, и контейнеры могут обращаться друг к другу по service name. Это поведение описано в документации Docker Compose networking.
Как масштабировать приложение с помощью Docker #
Масштабирование — это запуск нескольких экземпляров одного сервиса.
Например, вместо одного backend-контейнера:
backend_1
можно запустить три:
backend_1
backend_2
backend_3
В Docker Compose это можно сделать так:
docker compose up -d --scale backend=3
Флаг --scale у docker compose up масштабирует указанный сервис до нужного количества экземпляров. В документации Docker CLI указано, что --scale SERVICE=NUM задаёт количество инстансов сервиса и переопределяет настройку scale в Compose-файле.
Также есть отдельная команда:
docker compose scale backend=3
Она предназначена именно для масштабирования сервисов.
Важное ограничение при масштабировании #
Нельзя масштабировать сервис, если каждый его контейнер пытается занять один и тот же host-порт.
Проблемный пример:
services:
backend:
build: .
ports:
- "8000:8000"
Если сделать:
docker compose up -d --scale backend=3
возникнет конфликт, потому что три контейнера не могут одновременно слушать один и тот же порт 8000 на host-машине.
Правильнее поставить перед backend балансировщик, например nginx, а backend не публиковать наружу напрямую:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
depends_on:
- backend
backend:
build: .
expose:
- "8000"
postgres:
image: postgres:16
Схема:
client
↓
nginx:80
↓
backend_1:8000
backend_2:8000
backend_3:8000
Масштабирование через Docker Swarm #
Для production-подхода в Docker есть Swarm mode. Там приложение запускается как service, а Docker поддерживает нужное количество replicas.
Пример:
docker swarm init
Создать сервис с 3 репликами:
docker service create \
--name backend \
--replicas 3 \
-p 80:8000 \
my-backend-image
Изменить количество реплик:
docker service scale backend=5
Официальная документация Docker указывает, что docker service scale масштабирует replicated services до нужного количества replicas.
Также количество реплик можно описать в Compose-файле через deploy.replicas:
services:
backend:
image: my-backend-image
deploy:
mode: replicated
replicas: 3
В Compose Deploy Specification replicas задаёт количество контейнеров, которое должно быть запущено для replicated service.
Горизонтальное и вертикальное масштабирование #
В Docker чаще всего говорят о горизонтальном масштабировании:
было:
backend x1
стало:
backend x3
Это увеличивает количество экземпляров приложения.
Вертикальное масштабирование — это когда одному контейнеру дают больше ресурсов:
больше CPU
больше RAM
больше лимитов
Например:
services:
backend:
image: my-backend-image
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
Но для обработки большего количества запросов обычно эффективнее горизонтальное масштабирование: несколько backend-контейнеров + балансировщик.
Коротко #
Docker Compose — это инструмент для описания и запуска multi-container приложений через docker-compose.yml. Он позволяет одной командой поднять backend, базу данных, Redis, брокер сообщений, nginx и другие сервисы, автоматически создать сеть, подключить volume и передать переменные окружения.
Масштабировать приложение с помощью Docker можно через запуск нескольких экземпляров одного сервиса. В Docker Compose для этого используется команда:
docker compose up -d --scale backend=3
Для production-сценариев можно использовать Docker Swarm и задавать количество реплик через:
docker service scale backend=5
или через deploy.replicas в Compose-файле. При масштабировании важно не публиковать один и тот же host-порт у каждой реплики, а ставить перед ними балансировщик, например nginx, Traefik или встроенный routing-механизм оркестратора.
19. Что можно описать в docker-compose.yml файле #
Что можно описать в docker-compose.yml
#
В docker-compose.yml можно описать конфигурацию multi-container приложения: сервисы, сети, volume, переменные окружения, зависимости между контейнерами, healthcheck, порты, команды запуска и другие параметры. Официальная Compose Specification определяет Compose-файл как формат для описания multi-container приложений, а Docker Docs отдельно указывает, что Compose используется для настройки services, networks, volumes и других частей приложения.
1. Сервисы #
Главная секция — services.
Сервис — это описание контейнера или группы однотипных контейнеров:
services:
backend:
build: .
container_name: backend
ports:
- "8000:8000"
postgres:
image: postgres:16
Здесь описаны два сервиса:
backend контейнер с приложением
postgres контейнер с PostgreSQL
Внутри сервиса можно указать образ через image или сборку через build.
2. Образ или сборка #
Можно использовать готовый образ:
services:
redis:
image: redis:7
Или собрать образ из своего Dockerfile:
services:
backend:
build:
context: .
dockerfile: Dockerfile
context указывает папку, из которой Docker будет брать файлы для сборки.
3. Порты #
Через ports описывается проброс портов:
services:
backend:
ports:
- "8000:8000"
Формат:
порт_на_host:порт_внутри_контейнера
То есть:
localhost:8000 → backend-container:8000
4. Переменные окружения #
Можно передать переменные окружения:
services:
backend:
environment:
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: app_db
DB_USER: app_user
DB_PASSWORD: password
Или подключить .env-файл:
services:
backend:
env_file:
- .env
Пример .env:
DB_HOST=postgres
DB_PORT=5432
DB_NAME=app_db
5. Зависимости между сервисами #
Через depends_on можно указать порядок запуска сервисов:
services:
backend:
depends_on:
- postgres
- redis
postgres:
image: postgres:16
redis:
image: redis:7
Важно: depends_on управляет порядком запуска контейнеров, но сам по себе не гарантирует, что база данных уже полностью готова принимать подключения. Для этого обычно добавляют healthcheck.
6. Healthcheck #
healthcheck описывает проверку состояния контейнера:
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: app_user
POSTGRES_PASSWORD: password
POSTGRES_DB: app_db
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 3s
retries: 5
Для backend можно завязаться на состояние PostgreSQL:
services:
backend:
build: .
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 3s
retries: 5
7. Volumes #
Через volumes описывается хранение данных вне жизненного цикла контейнера.
Например, PostgreSQL должен сохранять данные даже после удаления контейнера:
services:
postgres:
image: postgres:16
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Здесь:
postgres_data named volume на стороне Docker
/var/lib/postgresql/data путь внутри контейнера PostgreSQL
Docker Docs указывает, что volume нужно явно подключать к сервисам через volumes внутри services.
Можно также подключить локальную папку проекта:
services:
backend:
volumes:
- ./app:/app
Это называется bind mount.
8. Networks #
Можно описывать сети:
services:
backend:
networks:
- app_network
postgres:
networks:
- app_network
networks:
app_network:
Контейнеры в одной сети могут обращаться друг к другу по имени сервиса:
backend → postgres:5432
Docker Compose по умолчанию создаёт одну сеть для приложения; каждый контейнер подключается к ней и доступен другим контейнерам по имени сервиса.
9. Команда запуска #
Можно переопределить команду запуска контейнера:
services:
backend:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
Или entrypoint:
services:
backend:
entrypoint: ["sh", "/app/entrypoint.sh"]
Разница:
entrypoint основная команда контейнера
command аргументы или команда по умолчанию
10. Restart policy #
Можно указать, что делать при падении контейнера:
services:
backend:
restart: unless-stopped
Популярные варианты:
no не перезапускать
always всегда перезапускать
on-failure перезапускать только при ошибке
unless-stopped перезапускать, кроме случая ручной остановки
11. Secrets #
Можно описать секреты:
services:
backend:
secrets:
- db_password
secrets:
db_password:
file: ./secrets/db_password.txt
Секреты используются для чувствительных данных: паролей, токенов, ключей. Compose Specification выделяет secrets отдельно как механизм для sensitive data.
Для локальной разработки часто используют .env, но для production лучше не хранить секреты прямо в docker-compose.yml.
12. Configs #
configs похожи на secrets, но предназначены для обычных конфигурационных файлов, не обязательно секретных:
services:
nginx:
image: nginx:alpine
configs:
- source: nginx_config
target: /etc/nginx/nginx.conf
configs:
nginx_config:
file: ./nginx.conf
Например, так можно передать конфиг nginx.
13. Profiles #
profiles позволяют запускать не все сервисы, а только нужные для конкретного режима:
services:
backend:
build: .
pgadmin:
image: dpage/pgadmin4
profiles:
- debug
Обычный запуск:
docker compose up
pgadmin не запустится.
Запуск с debug-профилем:
docker compose --profile debug up
14. Resource limits #
Можно описывать ограничения ресурсов:
services:
backend:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
Но важно: часть параметров из deploy исторически была ориентирована на orchestrator/Swarm-сценарии. Для обычного локального Compose нужно проверять, какие параметры реально поддерживаются текущей версией Docker Compose.
Полный пример #
services:
backend:
build:
context: .
dockerfile: Dockerfile
container_name: backend
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
ports:
- "8000:8000"
environment:
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: app_db
DB_USER: app_user
DB_PASSWORD: password
REDIS_HOST: redis
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./app:/app
networks:
- app_network
restart: unless-stopped
postgres:
image: postgres:16
container_name: postgres
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7
container_name: redis
networks:
- app_network
restart: unless-stopped
volumes:
postgres_data:
networks:
app_network:
Коротко #
В docker-compose.yml можно описать всё окружение приложения: какие сервисы запускать, из каких образов их брать или как собирать, какие порты пробрасывать, какие переменные окружения передавать, какие volume подключать для хранения данных, какие сети создавать, какие зависимости есть между сервисами, какие команды запуска использовать, какие healthcheck выполнять и как перезапускать контейнеры при сбоях.
Главные верхнеуровневые секции:
services:
volumes:
networks:
configs:
secrets:
Самая важная секция — services, потому что именно в ней описываются контейнеры приложения.
20. Отличия dokcer run и dokcer excec #
Главное отличие #
docker run создаёт и запускает новый контейнер из образа.
docker exec выполняет команду внутри уже запущенного контейнера.
Правильное написание:
docker run
docker exec
Не dokcer.
docker run
#
Команда docker run используется, когда нужно создать новый контейнер из Docker-образа и запустить его. По официальной документации Docker, docker run запускает команду в новом контейнере, при необходимости скачивая образ и стартуя контейнер.
Пример:
docker run -d --name my-nginx -p 8080:80 nginx
Что произойдёт:
Docker возьмёт образ nginx
создаст новый контейнер my-nginx
запустит его в фоне
пробросит порт 8080 host-машины на порт 80 контейнера
Другой пример:
docker run -it ubuntu bash
Это создаст новый контейнер из образа ubuntu и откроет внутри него bash.
docker exec
#
Команда docker exec используется, когда контейнер уже работает, и нужно выполнить внутри него дополнительную команду. Официальная документация Docker описывает docker exec как запуск новой команды внутри running container.
Пример:
docker exec -it my-nginx sh
Что произойдёт:
Docker найдёт уже запущенный контейнер my-nginx
выполнит внутри него sh
откроет интерактивную shell-сессию
Ещё пример:
docker exec my-nginx nginx -s reload
Это выполнит команду перезагрузки nginx внутри уже работающего контейнера.
Сравнение #
| Команда | Что делает | Нужен ли образ | Нужен ли уже запущенный контейнер |
|---|---|---|---|
docker run | Создаёт и запускает новый контейнер | Да | Нет |
docker exec | Выполняет команду внутри существующего контейнера | Нет напрямую | Да |
Важная разница #
docker run каждый раз создаёт новый контейнер:
docker run ubuntu echo "hello"
docker run ubuntu echo "hello"
docker run ubuntu echo "hello"
После этих команд будет создано три разных контейнера.
А docker exec работает внутри уже существующего контейнера:
docker exec -it my-container bash
docker exec my-container ls
docker exec my-container pwd
Все эти команды выполняются внутри одного и того же контейнера my-container.
Типичный сценарий #
Создать и запустить контейнер:
docker run -d --name app nginx
Посмотреть запущенные контейнеры:
docker ps
Зайти внутрь работающего контейнера:
docker exec -it app sh
Остановить контейнер:
docker stop app
После остановки docker exec уже не сработает, потому что exec требует работающий контейнер.
Коротко #
docker run используется для создания и запуска нового контейнера из образа.docker exec используется для выполнения команды внутри уже запущенного контейнера.
Пример:
docker run -d --name backend my-backend-image
docker exec -it backend sh
Первая команда создаёт и запускает контейнер backend, вторая — открывает shell внутри уже работающего контейнера.
21. Что происходит с ОС при запуске docker run #
При запуске:
docker run nginx
операционная система не запускает новую полноценную ОС внутри контейнера.
Docker запускает обычный процесс на host-системе, но изолирует его с помощью механизмов ядра Linux:
namespaces
cgroups
capabilities
filesystem layers
network namespace
mount namespace
Контейнер — это не виртуальная машина. У него нет собственного отдельного Linux-ядра. Он использует ядро host-системы.
Docker прямо описывает контейнер как процесс, который запускается на host-машине и изолируется собственным файловым пространством, сетью и деревом процессов.
Что происходит по шагам #
1. Docker CLI отправляет команду Docker daemon #
Когда ты пишешь:
docker run nginx
команда docker — это клиент. Он отправляет запрос Docker Engine / Docker daemon.
Схема:
docker CLI
↓
Docker daemon / dockerd
↓
containerd / runtime
↓
Linux kernel
Docker Engine работает как клиент-серверное приложение: есть CLI-клиент docker, daemon-процесс dockerd и API для управления Docker.
2. Docker проверяет образ #
Docker смотрит, есть ли образ локально:
nginx:latest
Если образа нет, Docker скачивает его из registry, обычно Docker Hub.
После этого из образа создаётся контейнер.
image nginx
↓
container from nginx
Официально docker run запускает команду в новом контейнере, при необходимости скачивает образ и стартует контейнер.
3. Создаётся файловая система контейнера #
Docker берёт слои образа и добавляет сверху writable layer — записываемый слой контейнера.
Упрощённо:
read-only image layers
nginx layer
debian/alpine layer
base layer
↓
writable container layer
↓
container filesystem
Поэтому внутри контейнера кажется, что у него есть своя файловая система:
/bin
/etc
/usr
/var
/app
Но физически это не отдельная ОС, а набор слоёв файловой системы, управляемый Docker storage driver.
4. Ядро Linux создаёт namespaces #
Docker просит ядро создать изолированные пространства имён.
Основные namespaces:
PID namespace своё дерево процессов
NET namespace своя сеть
MNT namespace свои mount points
UTS namespace свой hostname
IPC namespace своя межпроцессная коммуникация
USER namespace своя карта пользователей, если включено
Docker Docs прямо указывает: при docker run Docker создаёт набор namespaces и control groups для контейнера.
Из-за PID namespace процесс внутри контейнера может видеть себя как PID 1:
ps aux
Внутри контейнера:
PID 1 nginx
На host-системе этот же процесс будет обычным процессом с другим PID:
PID 24891 nginx
То есть процесс один и тот же, но видимость разная.
5. Создаются cgroups #
cgroups ограничивают и учитывают ресурсы контейнера:
CPU
RAM
disk I/O
process count
device access
Например:
docker run --memory=512m --cpus=1 nginx
Это означает:
контейнеру доступно до 512 MB RAM
контейнер ограничен примерно 1 CPU
Docker daemon использует OCI-compatible runtime через containerd как интерфейс к Linux kernel namespaces, cgroups и SELinux.
6. Настраивается сеть #
Обычно Docker подключает контейнер к bridge-сети.
Схема:
container eth0
↓
veth pair
↓
docker bridge
↓
host network
↓
internet
Если указан проброс портов:
docker run -p 8080:80 nginx
то Docker настраивает доступ:
host:8080 → container:80
Внутри контейнера localhost — это сам контейнер, а не host-система.
7. Запускается главный процесс контейнера #
Docker запускает команду из CMD или ENTRYPOINT образа.
Например у nginx главным процессом будет nginx:
container PID 1 → nginx
У Python-приложения это может быть:
uvicorn app.main:app --host 0.0.0.0 --port 8000
Главный процесс важен: пока он жив — контейнер считается запущенным. Если главный процесс завершился, контейнер останавливается.
Docker Docs указывает, что главным процессом контейнера является ENTRYPOINT и/или CMD из Dockerfile.
Что происходит с самой ОС #
С host-ОС происходит следующее:
новая ОС не загружается;
новое ядро не запускается;
GRUB/bootloader не участвует;
systemd внутри контейнера обычно не нужен;
ядро host-системы создаёт изолированную среду для процесса;
Docker добавляет файловую систему, сеть, лимиты и права.
То есть Docker не делает это:
BIOS/UEFI
↓
GRUB
↓
Linux kernel
↓
systemd
↓
приложение
Docker делает примерно это:
уже запущенная Linux host ОС
↓
docker run
↓
dockerd/containerd
↓
ядро Linux создаёт namespaces + cgroups
↓
запускается процесс приложения
Почему внутри контейнера кажется, что там отдельная ОС #
Потому что у контейнера есть свой root filesystem:
/etc
/bin
/lib
/usr
/var
Например, если образ основан на Alpine:
docker run -it alpine sh
внутри будут Alpine-файлы:
/etc/alpine-release
/bin/sh
/lib/
Но это не значит, что запустилось отдельное ядро Alpine. Это только пользовательское окружение Alpine, то есть user space.
Сравнение:
host kernel общий
container fs отдельный
container pid изолированный
container net изолированный
Контейнер vs виртуальная машина #
Docker container:
host kernel
└─ isolated process
Virtual machine:
hypervisor
└─ guest OS with its own kernel
└─ process
Контейнер легче, потому что не загружает отдельную ОС.
Виртуальная машина тяжелее, потому что внутри неё действительно запускается отдельная гостевая ОС со своим ядром.
Пример #
Команда:
docker run -d --name web -p 8080:80 nginx
Что произойдёт:
1. Docker CLI отправит запрос dockerd.
2. Docker проверит наличие образа nginx.
3. Если образа нет — скачает его.
4. Docker создаст контейнер из слоёв образа.
5. Ядро Linux создаст namespaces.
6. Docker настроит cgroups.
7. Docker подключит контейнер к сети.
8. Docker пробросит порт 8080 host → 80 container.
9. Docker запустит nginx как главный процесс контейнера.
Проверить процесс можно так:
docker ps
Зайти внутрь:
docker exec -it web sh
Посмотреть процессы внутри:
ps aux
Коротко #
При docker run Docker не запускает новую операционную систему. Он создаёт новый контейнер из образа и запускает внутри него процесс. Этот процесс работает на ядре host-ОС, но изолируется с помощью Linux namespaces, ограничивается через cgroups, получает свою файловую систему из слоёв образа, свою сеть и своё дерево процессов. Поэтому контейнер выглядит как отдельная Linux-среда, но технически это изолированный процесс на уже запущенной ОС.
22. Как упаковать новый микросервис в Docker? | Какие шаги нужны для контейнеризации backend-сервиса? #
Упаковать новый backend-микросервис в Docker — значит подготовить приложение так, чтобы оно запускалось внутри контейнера одинаково на любой машине: локально, на тестовом сервере, в CI/CD или production.
Обычно нужны такие файлы:
project/
├── app/
│ └── main.py
├── requirements.txt / pyproject.toml
├── Dockerfile
├── .dockerignore
├── docker-compose.yml
└── .env
Dockerfile описывает, как собрать image: какие файлы скопировать, какие зависимости установить и какую команду запускать при старте контейнера. Docker официально определяет Dockerfile как текстовый файл с инструкциями для сборки образа.
1. Подготовить backend-приложение #
Сначала приложение должно уметь запускаться обычной командой.
Например для FastAPI:
uvicorn app.main:app --host 0.0.0.0 --port 8000
Важно указывать:
--host 0.0.0.0
А не:
--host 127.0.0.1
Потому что внутри контейнера 127.0.0.1 означает сам контейнер. Чтобы приложение было доступно снаружи контейнера, оно должно слушать 0.0.0.0.
2. Вынести настройки в переменные окружения #
В коде не нужно жёстко прописывать:
DB_HOST = "localhost"
DB_PASSWORD = "password"
Лучше брать настройки из environment variables:
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT", "5432")
DB_NAME = os.getenv("DB_NAME")
Для Docker это стандартный подход, потому что один и тот же image можно запускать с разными настройками:
локально
staging
production
CI/CD
3. Написать Dockerfile
#
Пример для Python/FastAPI:
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Что здесь происходит:
FROM выбираем базовый образ
WORKDIR задаём рабочую директорию
ENV задаём переменные окружения
COPY копируем файлы проекта
RUN устанавливаем зависимости
CMD команда запуска приложения
Dockerfile-инструкции FROM, WORKDIR, COPY, RUN, CMD описаны в официальной Dockerfile reference.
4. Добавить .dockerignore
#
.dockerignore нужен, чтобы не отправлять в build context лишние файлы:
.git
.idea
.vscode
__pycache__
*.pyc
.env
.venv
venv
tests
README.md
Без .dockerignore Docker может тащить в build context лишнее: виртуальное окружение, .git, кэш, локальные файлы IDE. Docker рекомендует использовать .dockerignore, чтобы исключать файлы, не нужные для сборки.
5. Собрать Docker image #
Сборка образа:
docker build -t my-backend:1.0 .
Где:
my-backend имя image
1.0 tag/version
. build context, то есть текущая директория
Docker build использует Dockerfile и build context — набор файлов, доступных во время сборки.
6. Запустить контейнер #
Простой запуск:
docker run -d \
--name my-backend \
-p 8000:8000 \
my-backend:1.0
Здесь:
-d запуск в фоне
--name имя контейнера
-p 8000:8000 host:container порт
my-backend:1.0 image
Проверить:
docker ps
Посмотреть логи:
docker logs my-backend
Зайти внутрь контейнера:
docker exec -it my-backend sh
7. Подключить базу, Redis и другие сервисы через Docker Compose #
Для backend-сервиса почти всегда нужны внешние зависимости:
PostgreSQL
Redis
RabbitMQ
MinIO
nginx
Их удобнее описывать в docker-compose.yml. Compose-файл используется для настройки сервисов, сетей, volume и других частей multi-container приложения.
Пример:
services:
backend:
build: .
container_name: backend
ports:
- "8000:8000"
environment:
DB_HOST: postgres
DB_PORT: 5432
DB_NAME: app_db
DB_USER: app_user
DB_PASSWORD: password
REDIS_HOST: redis
depends_on:
- postgres
- redis
postgres:
image: postgres:16
container_name: postgres
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7
container_name: redis
volumes:
postgres_data:
Запуск:
docker compose up -d
Остановка:
docker compose down
Compose запускает и управляет несколькими сервисами через один YAML-файл, включая сети и volume.
8. Правильно настроить подключение к БД #
Внутри Docker Compose backend должен подключаться к PostgreSQL не так:
localhost:5432
А так:
postgres:5432
Потому что postgres — это имя сервиса в Docker Compose. Контейнеры внутри одной Compose-сети могут обращаться друг к другу по имени сервиса.
Пример строки подключения:
DATABASE_URL=postgresql://app_user:password@postgres:5432/app_db
9. Добавить healthcheck #
Для базы данных полезно добавить healthcheck:
services:
postgres:
image: postgres:16
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 3s
retries: 5
И завязать backend на готовность базы:
services:
backend:
build: .
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 3s
retries: 5
Это лучше, чем просто depends_on, потому что база может быть запущена как процесс, но ещё не готова принимать подключения.
10. Для production использовать multi-stage build #
Для production-образа лучше отделять стадию сборки от стадии запуска. Docker multi-stage builds позволяют использовать несколько FROM и копировать в финальный image только нужные артефакты, оставляя build-зависимости вне итогового образа.
Пример:
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN python -m venv /venv \
&& /venv/bin/pip install --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS runtime
WORKDIR /app
ENV PATH="/venv/bin:$PATH" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
COPY --from=builder /venv /venv
COPY app ./app
RUN useradd -m appuser
USER appuser
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Плюсы:
меньше итоговый image;
меньше лишних build-зависимостей;
чище production-среда;
меньше поверхность атаки.
11. Не хранить секреты в Dockerfile #
Плохо:
ENV DB_PASSWORD=secret_password
ENV JWT_SECRET=secret_key
Лучше передавать секреты при запуске:
docker run --env-file .env my-backend:1.0
Или через Compose:
services:
backend:
env_file:
- .env
.env обычно не коммитят в Git:
.env
12. Проверить контейнер перед публикацией #
Минимальная проверка:
docker build -t my-backend:1.0 .
docker run -d --name my-backend -p 8000:8000 my-backend:1.0
docker logs my-backend
docker ps
curl http://localhost:8000/health
Для backend-сервиса полезно иметь endpoint:
GET /health
Например:
{
"status": "ok"
}
13. Загрузить image в registry #
После проверки image обычно отправляют в registry:
docker tag my-backend:1.0 registry.example.com/my-backend:1.0
docker push registry.example.com/my-backend:1.0
После этого image можно использовать на сервере, в CI/CD или оркестраторе.
Общий порядок контейнеризации backend-сервиса #
1. Проверить, что сервис запускается локально обычной командой.
2. Вынести настройки в environment variables.
3. Написать Dockerfile.
4. Добавить .dockerignore.
5. Собрать Docker image.
6. Запустить контейнер через docker run.
7. Проверить логи и health endpoint.
8. Описать зависимости в docker-compose.yml.
9. Подключить БД, Redis, брокеры, volumes и networks.
10. Настроить миграции, healthcheck и restart policy.
11. Убрать секреты из Dockerfile и кода.
12. Собрать production-image.
13. Отправить image в registry.
14. Развернуть на сервере или в оркестраторе.
Коротко #
Чтобы контейнеризировать backend-микросервис, нужно написать Dockerfile, выбрать базовый образ, скопировать код приложения, установить зависимости, задать команду запуска через CMD или ENTRYPOINT, добавить .dockerignore, собрать image через docker build и проверить запуск через docker run.
Если сервис зависит от базы данных, Redis или других компонентов, окружение описывают в docker-compose.yml: backend, PostgreSQL, Redis, сети, volumes, переменные окружения и healthcheck. Внутри Compose сервисы обращаются друг к другу по имени сервиса, например postgres:5432, а не через localhost.
23. Чем отличаются в Docker CMD и ENTRYPOINT? #
Главное отличие #
CMD задаёт команду или аргументы по умолчанию для контейнера.
ENTRYPOINT задаёт основную неизменяемую команду, которая должна запускаться при старте контейнера.
Обычно правильная логика такая:
ENTRYPOINT ["uvicorn"]
CMD ["app.main:app", "--host", "0.0.0.0", "--port", "8000"]
То есть:
ENTRYPOINT что запускать
CMD с какими аргументами запускать по умолчанию
Docker в best practices прямо рекомендует использовать ENTRYPOINT как основную команду образа, а CMD — как аргументы по умолчанию.
CMD
#
CMD — это команда по умолчанию, которую легко заменить при docker run.
Пример:
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Запуск:
docker run my-backend
Docker выполнит:
uvicorn app.main:app --host 0.0.0.0 --port 8000
Но CMD можно полностью переопределить:
docker run my-backend python manage.py migrate
В этом случае вместо uvicorn ... выполнится:
python manage.py migrate
ENTRYPOINT
#
ENTRYPOINT задаёт основную команду контейнера.
Пример:
FROM python:3.12-slim
ENTRYPOINT ["uvicorn"]
CMD ["app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Запуск:
docker run my-backend
Фактически выполнится:
uvicorn app.main:app --host 0.0.0.0 --port 8000
А если передать аргументы:
docker run my-backend app.main:app --host 0.0.0.0 --port 9000
то они заменят CMD, но не ENTRYPOINT.
Фактически будет:
uvicorn app.main:app --host 0.0.0.0 --port 9000
Сравнение #
| Параметр | CMD | ENTRYPOINT |
|---|---|---|
| Назначение | Команда/аргументы по умолчанию | Главная команда контейнера |
Легко переопределяется через docker run | Да | Нет, обычно через --entrypoint |
| Часто используется для | Обычного запуска приложения | CLI-утилит, фиксированной команды |
| Может работать вместе с другим | Да | Да |
Как они работают вместе #
Пример:
ENTRYPOINT ["python"]
CMD ["app.py"]
Запуск:
docker run my-image
Результат:
python app.py
Запуск с другим аргументом:
docker run my-image script.py
Результат:
python script.py
То есть ENTRYPOINT остался python, а CMD заменился на script.py.
Как переопределить ENTRYPOINT
#
Если нужно заменить именно ENTRYPOINT, используется --entrypoint:
docker run --entrypoint sh my-backend
Например, это полезно для отладки, когда образ по умолчанию запускает приложение, а нужно зайти внутрь shell.
Exec form и shell form #
И CMD, и ENTRYPOINT можно писать в двух формах.
Лучше использовать exec form:
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
Хуже для production — shell form:
CMD uvicorn app.main:app --host 0.0.0.0
Docker указывает, что CMD и ENTRYPOINT поддерживают shell form и exec form, но при shell form процесс запускается как дочерний процесс shell, из-за чего могут некорректно обрабатываться сигналы вроде SIGTERM.
Для backend-сервиса лучше так:
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
А не так:
CMD uvicorn app.main:app --host 0.0.0.0 --port 8000
Важный момент #
В одном Dockerfile может быть несколько CMD или несколько ENTRYPOINT, но использоваться будет только последняя инструкция. Docker прямо указывает: если в Dockerfile несколько CMD, ENTRYPOINT или HEALTHCHECK, используется только последнее вхождение.
Пример:
CMD ["echo", "first"]
CMD ["echo", "second"]
Сработает только:
CMD ["echo", "second"]
Когда использовать CMD
#
CMD подходит, когда нужно просто задать стандартную команду запуска приложения:
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Это нормальный вариант для backend-сервиса.
Когда использовать ENTRYPOINT
#
ENTRYPOINT подходит, когда образ должен вести себя как конкретная программа.
Например:
ENTRYPOINT ["python", "manage.py"]
CMD ["runserver", "0.0.0.0:8000"]
Тогда можно запускать:
docker run django-image migrate
Фактически получится:
python manage.py migrate
Или:
docker run django-image createsuperuser
Фактически:
python manage.py createsuperuser
Коротко #
CMD задаёт команду или аргументы по умолчанию для контейнера и легко переопределяется аргументами в docker run.
ENTRYPOINT задаёт основную команду контейнера. Аргументы из CMD могут дополнять ENTRYPOINT.
Практическое правило:
CMD когда нужно просто указать стандартный запуск приложения
ENTRYPOINT когда образ должен всегда запускать конкретную программу
ENTRYPOINT + CMD когда нужна фиксированная команда и изменяемые аргументы по умолчанию
Пример:
ENTRYPOINT ["uvicorn"]
CMD ["app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Здесь uvicorn — основная команда, а CMD — параметры по умолчанию.
24. Что такое балансировка нагрузки в Docker? #
Что такое балансировка нагрузки в Docker #
Балансировка нагрузки в Docker — это распределение входящих запросов между несколькими контейнерами одного приложения.
Например, вместо одного backend-контейнера:
client → backend_1
запускают несколько одинаковых экземпляров:
client
↓
load balancer
↓
backend_1
backend_2
backend_3
Цель — чтобы один контейнер не принимал всю нагрузку, а запросы распределялись между несколькими репликами.
Зачем нужна балансировка #
Балансировка нужна, когда:
одного контейнера уже не хватает;
нужно обрабатывать больше запросов;
нужно повысить отказоустойчивость;
нужно обновлять сервис без полного простоя;
нужно распределить трафик между несколькими replicas.
Например, если backend обрабатывает 100 запросов в секунду, можно поднять 3 экземпляра backend и распределять запросы между ними.
backend_1 → часть запросов
backend_2 → часть запросов
backend_3 → часть запросов
Балансировка в обычном Docker Compose #
В docker compose можно масштабировать сервис:
docker compose up -d --scale backend=3
После этого Compose поднимет несколько контейнеров одного сервиса. В Compose Specification сервис описывается как вычислительный ресурс, который может быть масштабирован или заменён независимо от других компонентов.
Условно:
backend-1
backend-2
backend-3
Но важный момент: Docker Compose сам по себе не является полноценным HTTP-балансировщиком для внешнего трафика. Обычно перед несколькими backend-контейнерами ставят reverse proxy/load balancer:
client
↓
nginx / traefik / haproxy
↓
backend_1
backend_2
backend_3
Почему нельзя просто пробросить один порт на все replicas #
Проблемный вариант:
services:
backend:
build: .
ports:
- "8000:8000"
Если сделать:
docker compose up -d --scale backend=3
то несколько контейнеров будут пытаться занять один и тот же порт 8000 на host-машине.
backend_1 → host:8000
backend_2 → host:8000 конфликт
backend_3 → host:8000 конфликт
Поэтому обычно backend не публикуют наружу напрямую, а используют expose:
services:
backend:
build: .
expose:
- "8000"
А наружу публикуют только балансировщик:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
Пример с nginx #
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- backend
backend:
build: .
expose:
- "8000"
Запуск нескольких backend-контейнеров:
docker compose up -d --scale backend=3
Пример nginx.conf:
events {}
http {
upstream backend_pool {
server backend:8000;
}
server {
listen 80;
location / {
proxy_pass http://backend_pool;
}
}
}
В Docker Compose имя сервиса backend резолвится внутри общей Docker-сети. Compose по умолчанию создаёт сеть для приложения, а контейнеры могут обращаться друг к другу по имени сервиса.
Балансировка в Docker Swarm #
В Docker Swarm балансировка встроена лучше, чем в обычном Compose.
Можно создать сервис с несколькими репликами:
docker service create \
--name backend \
--replicas 3 \
-p 80:8000 \
my-backend-image
Или изменить количество реплик:
docker service scale backend=5
Команда docker service scale масштабирует replicated services до нужного количества реплик.
В Swarm есть routing mesh: если сервис публикует порт, все nodes в swarm участвуют в ingress routing mesh. Docker указывает, что запрос к опубликованному порту на любом node может быть направлен к активной задаче сервиса.
Схема:
client
↓
node_1:80 / node_2:80 / node_3:80
↓
Docker routing mesh
↓
backend replica 1
backend replica 2
backend replica 3
Внешний балансировщик #
Даже при Docker Swarm часто используют внешний балансировщик:
client
↓
external LB
↓
Docker nodes
↓
services / containers
Например:
nginx
HAProxy
Traefik
cloud load balancer
Docker документация прямо описывает возможность настроить внешний load balancer, например HAProxy, чтобы направлять запросы к swarm service.
Виды балансировки #
На практике встречаются два уровня.
1. L4-балансировка #
Работает на уровне TCP/UDP:
IP
port
TCP connection
Пример:
:80 → replica_1
:80 → replica_2
:80 → replica_3
Docker Swarm routing mesh ближе к такому уровню.
2. L7-балансировка #
Работает на уровне HTTP:
/api/users → users-service
/api/orders → orders-service
Host header → нужный сервис
cookies → sticky sessions
Это обычно делают через:
nginx
Traefik
HAProxy
Envoy
Для backend-приложений чаще нужен именно L7 reverse proxy, потому что он может учитывать HTTP-маршруты, домены, заголовки, TLS и другие параметры.
Важное требование к приложению #
Чтобы backend нормально масштабировался, контейнеры должны быть stateless.
Плохо:
сессии хранятся только в памяти одного контейнера;
загруженные файлы лежат только внутри контейнера;
фоновые задачи запускаются в каждом replica без координации;
каждый контейнер держит уникальное локальное состояние.
Лучше:
сессии → Redis / БД
файлы → S3 / MinIO / volume
очереди → RabbitMQ / Redis
состояние → PostgreSQL / внешнее хранилище
Иначе запрос пользователя может попасть то в backend_1, то в backend_2, а нужного состояния там не окажется.
Коротко #
Балансировка нагрузки в Docker — это распределение входящих запросов между несколькими контейнерами одного сервиса. Сначала приложение масштабируют, например:
docker compose up -d --scale backend=3
или в Swarm:
docker service scale backend=5
Затем перед репликами ставят балансировщик: nginx, Traefik, HAProxy или используют встроенный routing mesh в Docker Swarm. В обычном Docker Compose внешний HTTP-трафик обычно балансируют через reverse proxy, потому что несколько контейнеров не могут одновременно занять один и тот же host-порт. Для корректного масштабирования backend должен быть stateless, а общее состояние нужно выносить в БД, Redis, S3/MinIO или другое внешнее хранилище.
25. Как работает система кэширования слоёв в Dockerfile? #
Что такое кэширование слоёв Docker #
При сборке Docker-образа каждая инструкция Dockerfile создаёт слой или влияет на состояние сборки:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "main.py"]
Условно это превращается в цепочку:
base image layer
↓
WORKDIR layer
↓
COPY requirements.txt layer
↓
RUN pip install layer
↓
COPY app source layer
↓
final image
Docker сохраняет результат выполнения инструкций в build cache. При следующей сборке он проверяет: можно ли переиспользовать уже готовый слой вместо повторного выполнения команды. Это ускоряет сборку, потому что Docker пропускает неизменившиеся шаги. Docker Docs описывает build cache как механизм переиспользования результатов предыдущих сборок для пропуска лишней работы.
Как Docker понимает, можно ли использовать кэш #
Docker идёт по Dockerfile сверху вниз.
Для каждой инструкции он проверяет:
такая же ли инструкция;
не изменились ли файлы, от которых она зависит;
не был ли сломан кэш на предыдущем шаге.
Если слой подходит — Docker пишет примерно:
CACHED
Если слой не подходит — Docker выполняет инструкцию заново, и все следующие слои тоже пересобираются.
Общее правило из Docker Docs: builder сначала проверяет base image, затем каждая следующая инструкция сравнивается с cached layers; если подходящий слой не найден, cache invalidated.
Пример неправильного Dockerfile #
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Проблема здесь:
COPY . .
Если ты изменишь любой файл проекта:
app/main.py
README.md
tests/test_api.py
Docker увидит, что COPY . . изменился, сбросит кэш для этого слоя, а потом заново выполнит:
RUN pip install -r requirements.txt
Даже если зависимости не менялись.
Правильнее для Python #
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Почему так лучше:
requirements.txt меняется редко
↓
слой с pip install часто берётся из кэша
код приложения меняется часто
↓
пересобирается только COPY . . и следующие слои
Docker рекомендует упорядочивать слои так, чтобы редко изменяемые шаги шли раньше часто изменяемых. Это один из способов оптимизации build cache.
Как это работает по шагам #
Первая сборка:
docker build -t backend .
Docker выполняет всё:
FROM python:3.12-slim выполнено
WORKDIR /app выполнено
COPY requirements.txt . выполнено
RUN pip install ... выполнено
COPY . . выполнено
CMD ... выполнено
Вторая сборка без изменений:
FROM python:3.12-slim CACHED
WORKDIR /app CACHED
COPY requirements.txt . CACHED
RUN pip install ... CACHED
COPY . . CACHED
CMD ... CACHED
Если изменился только app/main.py:
FROM python:3.12-slim CACHED
WORKDIR /app CACHED
COPY requirements.txt . CACHED
RUN pip install ... CACHED
COPY . . пересобирается
CMD ... пересобирается
Если изменился requirements.txt:
FROM python:3.12-slim CACHED
WORKDIR /app CACHED
COPY requirements.txt . пересобирается
RUN pip install ... пересобирается
COPY . . пересобирается
CMD ... пересобирается
Почему изменение раннего слоя пересобирает всё ниже #
Dockerfile — это последовательная цепочка. Каждый следующий слой зависит от предыдущего.
layer 1
↓
layer 2
↓
layer 3
↓
layer 4
Если изменился layer 2, Docker уже не может безопасно использовать старые layer 3 и layer 4, потому что они были построены поверх старого состояния layer 2.
Поэтому важно не ставить часто меняющиеся инструкции в начало Dockerfile.
Плохо:
COPY . .
RUN pip install -r requirements.txt
Лучше:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
Что именно ломает кэш #
Кэш может сброситься, если:
изменилась инструкция Dockerfile;
изменился файл, который участвует в COPY или ADD;
изменился порядок инструкций;
изменилась base image;
изменилась build argument, влияющая на слой;
предыдущий слой был пересобран.
Например, было:
RUN pip install -r requirements.txt
Стало:
RUN pip install --no-cache-dir -r requirements.txt
Для Docker это уже другая инструкция, значит старый кэш не подходит.
.dockerignore тоже влияет на кэш
#
Build context — это набор файлов, доступных Docker во время сборки. Docker Docs определяет build context как набор файлов, к которым сборка может обращаться.
Если в build context попадают лишние изменяющиеся файлы, они могут ломать кэш.
Например, без .dockerignore в сборку могут попадать:
.git
.idea
.vscode
__pycache__
.pytest_cache
.venv
logs
.env
Хороший .dockerignore:
.git
.idea
.vscode
__pycache__
*.pyc
.pytest_cache
.venv
venv
.env
logs
Это уменьшает build context и снижает вероятность лишней invalidation.
RUN apt-get update и кэш
#
Есть важная ловушка:
RUN apt-get update
RUN apt-get install -y curl
Так делать плохо, потому что apt-get update может взяться из старого кэша, а apt-get install получит устаревшие package lists.
Лучше объединять:
RUN apt-get update && apt-get install -y \
curl \
gcc \
&& rm -rf /var/lib/apt/lists/*
Так установка пакетов и обновление индекса находятся в одном слое.
Как отключить кэш #
Для полностью чистой сборки:
docker build --no-cache -t backend .
Флаг --no-cache отключает build cache и заставляет Docker пересобрать все слои с нуля. Docker Docs отдельно указывает, что --no-cache forcing Docker to rebuild all layers from scratch. (
Docker Documentation)
Но важно: --no-cache не обязательно скачивает свежую base image. Для этого используют:
docker build --pull --no-cache -t backend .
Практическое правило #
Dockerfile нужно писать так, чтобы сверху были редкие изменения, а снизу частые:
1. FROM base image
2. системные зависимости
3. dependency-файлы: requirements.txt / pyproject.toml / package.json
4. установка зависимостей
5. исходный код приложения
6. команда запуска
Пример:
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Коротко #
Docker кэширует результат выполнения инструкций Dockerfile. При следующей сборке он идёт сверху вниз и проверяет, можно ли переиспользовать слой. Если инструкция и связанные с ней файлы не изменились, слой берётся из кэша. Если какой-то слой изменился, этот слой и все слои ниже пересобираются.
Поэтому Dockerfile нужно строить так, чтобы редко изменяемые шаги были выше, а часто изменяемые ниже. Например, сначала копируют requirements.txt и устанавливают зависимости, а только потом копируют весь исходный код. Это позволяет не переустанавливать зависимости при каждом изменении кода приложения.
26. Какие Linux-команды чаще всего использует backend-разработчик? #
Базовый набор #
Backend-разработчик чаще всего использует Linux-команды для работы с файлами, логами, процессами, сетью, сервисами, правами доступа, Docker и базой данных. Базовые команды вроде ls, cp, mv, rm, cat, mkdir, chmod, chown относятся к GNU Coreutils — стандартному набору утилит для работы с файлами и текстом в GNU/Linux-системах.
1. Навигация по файловой системе #
pwd
ls
ls -la
cd /path/to/project
tree
Назначение:
pwd показать текущую директорию
ls показать файлы
ls -la показать все файлы, включая скрытые
cd перейти в директорию
tree показать структуру каталогов деревом
Пример:
cd /var/www/backend
ls -la
pwd
2. Работа с файлами и папками #
touch app.log
mkdir logs
mkdir -p app/config/local
cp settings.py settings.example.py
mv old_name.py new_name.py
rm file.txt
rm -rf __pycache__
Назначение:
touch создать пустой файл или обновить время изменения
mkdir создать директорию
cp скопировать файл
mv переместить или переименовать
rm удалить файл
rm -rf удалить директорию рекурсивно и без подтверждения
rm -rf нужно использовать осторожно: команда удаляет рекурсивно и без интерактивного подтверждения.
3. Просмотр содержимого файлов #
cat .env
less app.log
head -n 50 app.log
tail -n 100 app.log
tail -f app.log
Назначение:
cat быстро вывести файл
less удобно читать большой файл
head показать начало файла
tail показать конец файла
tail -f смотреть файл в реальном времени
Частый пример для логов:
tail -f /var/log/nginx/error.log
4. Поиск по файлам и тексту #
find . -name "*.py"
find . -name "*.log"
grep "ERROR" app.log
grep -r "DATABASE_URL" .
grep -rn "UserRepository" .
Назначение:
find искать файлы и директории
grep искать текст
-r рекурсивно
-n показывать номер строки
find проходит по дереву каталогов и ищет файлы по заданному выражению, а grep ищет строки по шаблону в файлах или входном потоке.
Практический пример:
grep -rn "SECRET_KEY" .
Так можно найти, где в проекте используется SECRET_KEY.
5. Права доступа и владельцы #
chmod +x entrypoint.sh
chmod 600 .env
chmod 644 config.yaml
chown appuser:appuser app.log
chown -R appuser:appuser /var/www/backend
Назначение:
chmod изменить права доступа
chown изменить владельца и группу
Типичные права:
600 файл доступен только владельцу
644 обычный конфиг или текстовый файл
755 исполняемый файл или директория
Пример для скрипта запуска:
chmod +x entrypoint.sh
6. Процессы #
ps aux
ps aux | grep uvicorn
top
htop
kill 12345
kill -9 12345
pgrep python
pkill -f uvicorn
Назначение:
ps показать процессы
top интерактивный монитор процессов
htop более удобный top, если установлен
kill отправить сигнал процессу
pgrep найти PID по имени процесса
pkill завершить процесс по имени
Утилиты ps, top, free, pgrep, pkill входят в семейство procps/procps-ng и используются для просмотра процессов, памяти и состояния системы.
Пример:
ps aux | grep gunicorn
7. Память, CPU, диск #
free -h
df -h
du -sh .
du -sh /var/log/*
uptime
Назначение:
free -h показать RAM
df -h показать свободное место на дисках
du -sh показать размер директории
uptime показать время работы системы и load average
Частый пример:
df -h
du -sh /var/lib/docker
Так можно быстро понять, не забит ли диск логами, Docker-образами или volume.
8. Сеть #
ip addr
ip route
ss -tulpen
curl http://localhost:8000/health
curl -I https://example.com
ping 8.8.8.8
dig example.com
nslookup example.com
Назначение:
ip addr показать сетевые интерфейсы и IP
ip route показать маршруты
ss -tulpen показать открытые порты и процессы
curl сделать HTTP-запрос
ping проверить доступность хоста
dig/nslookup проверить DNS
ss используется для просмотра socket statistics и может показывать TCP-соединения, listening-порты и связанную сетевую информацию; curl — инструмент для передачи данных по URL, включая HTTP/HTTPS-запросы.
Примеры:
ss -tulpen | grep 8000
curl http://localhost:8000/health
curl -X POST http://localhost:8000/api/login
9. Логи systemd-сервисов #
journalctl
journalctl -u nginx
journalctl -u backend.service
journalctl -u backend.service -f
journalctl -u backend.service --since "1 hour ago"
Назначение:
journalctl -u service посмотреть логи конкретного сервиса
-f смотреть логи в реальном времени
--since фильтр по времени
journalctl используется для вывода логов, сохранённых systemd-journald.
Пример:
journalctl -u gunicorn.service -f
10. Управление сервисами через systemd #
systemctl status nginx
systemctl start nginx
systemctl stop nginx
systemctl restart nginx
systemctl reload nginx
systemctl enable nginx
systemctl disable nginx
Назначение:
status статус сервиса
start запустить
stop остановить
restart перезапустить
reload перечитать конфиг без полного рестарта, если сервис поддерживает
enable добавить в автозапуск
disable убрать из автозапуска
Частый пример после изменения nginx-конфига:
sudo nginx -t
sudo systemctl reload nginx
11. SSH и копирование файлов #
ssh user@server
ssh user@server "docker ps"
scp file.txt user@server:/tmp/
rsync -av ./app user@server:/var/www/app
Назначение:
ssh подключиться к серверу или выполнить удалённую команду
scp скопировать файл по SSH
rsync синхронизировать директории
OpenSSH включает ssh как клиент для удалённого входа и выполнения команд, а также связанные утилиты вроде scp, ssh-agent, ssh-add.
Пример:
ssh deploy@10.0.0.5
12. Архивы #
tar -czf logs.tar.gz logs/
tar -xzf logs.tar.gz
gzip app.log
gunzip app.log.gz
Назначение:
tar -czf создать .tar.gz архив
tar -xzf распаковать .tar.gz архив
gzip сжать файл
gunzip распаковать .gz файл
tar используется для создания и распаковки архивов, а gzip — для сжатия и распаковки файлов.
13. Переменные окружения #
env
printenv
echo $PATH
export DEBUG=1
export DATABASE_URL="postgresql://user:pass@localhost:5432/db"
Назначение:
env вывести окружение
printenv вывести переменные окружения
echo $VAR вывести конкретную переменную
export задать переменную для текущей shell-сессии
Частый пример:
echo $DATABASE_URL
14. Пакетные менеджеры #
Для Debian/Ubuntu:
sudo apt update
sudo apt install nginx
sudo apt install postgresql-client
Для Alpine:
apk add curl
apk add postgresql-client
Для RHEL/CentOS/Fedora:
sudo dnf install nginx
Backend-разработчик часто ставит:
curl
git
nginx
postgresql-client
redis-tools
htop
net-tools
15. Git на сервере #
git status
git pull
git log --oneline
git branch
git checkout main
git reset --hard HEAD
Назначение:
git status состояние репозитория
git pull забрать изменения
git log история коммитов
git checkout переключить ветку
git reset --hard жёстко откатить рабочую директорию
На production-серверах напрямую через git pull деплоят не всегда, но на учебных, тестовых и небольших проектах это часто встречается.
16. Docker-команды, которые backend-разработчик часто использует в Linux #
docker ps
docker ps -a
docker logs backend
docker logs -f backend
docker exec -it backend sh
docker restart backend
docker images
docker volume ls
docker network ls
docker compose up -d
docker compose down
docker compose logs -f backend
Назначение:
docker ps контейнеры
docker logs логи контейнера
docker exec выполнить команду внутри контейнера
docker restart перезапустить контейнер
docker compose up -d поднять compose-проект
docker compose down остановить compose-проект
Docker CLI имеет отдельную справочную документацию по командам, а docker logs используется для получения логов контейнера.
17. PostgreSQL через терминал #
psql -h localhost -U postgres -d app_db
psql "$DATABASE_URL"
pg_dump -h localhost -U postgres app_db > dump.sql
psql -h localhost -U postgres app_db < dump.sql
Назначение:
psql подключение к PostgreSQL
pg_dump создать SQL-дамп базы
psql — терминальный клиент PostgreSQL, через который можно выполнять SQL-запросы интерактивно или из файла.
Пример:
psql -h localhost -U app_user -d app_db
18. Самые важные команды для собеседования #
Минимум, который backend-разработчик должен уверенно знать:
cd
ls -la
pwd
cat
less
tail -f
grep -rn
find
chmod
chown
ps aux
kill
top
free -h
df -h
du -sh
curl
ss -tulpen
ssh
scp
systemctl status
journalctl -u service -f
docker ps
docker logs -f
docker exec -it
docker compose up -d
Коротко #
Backend-разработчик чаще всего использует Linux-команды для навигации по файловой системе, просмотра логов, диагностики процессов, проверки сети, управления сервисами и работы с Docker. Например, ls, cd, cat, less, tail -f, grep, find, ps, top, kill, df, du, free, curl, ss, ssh, systemctl, journalctl, docker ps, docker logs и docker exec. Эти команды нужны, чтобы смотреть состояние сервера, читать логи приложения, проверять порты, перезапускать сервисы, заходить в контейнеры и быстро диагностировать проблемы backend-сервиса.
27. Как изменить права на файл в Linux? | Как работает команда chmod? #
Права на файл в Linux меняют командой:
chmod MODE FILE
Пример:
chmod 644 config.yaml
chmod +x entrypoint.sh
chmod 600 .env
chmod изменяет file mode bits — биты режима файла, которые определяют права доступа к файлу или директории. Права можно задавать в символьной форме или в числовой/octal форме.
Как посмотреть текущие права #
ls -l
Пример вывода:
-rw-r--r-- 1 user user 1200 Jun 27 app.py
Разбор:
- rw- r-- r--
│ │ │ │
│ │ │ └── others: остальные пользователи
│ │ └────── group: группа
│ └────────── user/owner: владелец
└──────────── тип файла
Права делятся на 3 группы:
u user / owner / владелец
g group / группа
o others / остальные
a all / все
Что означают r, w, x
#
r read чтение
w write запись/изменение
x execute выполнение
Для обычного файла:
r можно читать файл
w можно изменять файл
x можно запускать как программу/скрипт
Для директории смысл немного другой:
r можно посмотреть список файлов
w можно создавать/удалять/переименовывать файлы
x можно входить в директорию и проходить через неё
Числовой режим chmod
#
Каждое право имеет число:
r = 4
w = 2
x = 1
Права складываются:
7 = 4 + 2 + 1 = rwx
6 = 4 + 2 = rw-
5 = 4 + 1 = r-x
4 = 4 = r--
0 = нет прав = ---
В man page chmod указано, что numeric mode строится из octal digits, где права складываются из значений 4, 2, 1.
Примеры числовых прав #
chmod 644 file.txt
Означает:
owner: rw- = 6
group: r-- = 4
others: r-- = 4
То есть:
-rw-r--r--
chmod 600 .env
Означает:
owner: rw- = 6
group: --- = 0
others: --- = 0
То есть:
-rw-------
Так часто ставят права на .env, приватные ключи и чувствительные конфиги.
chmod 755 entrypoint.sh
Означает:
owner: rwx = 7
group: r-x = 5
others: r-x = 5
То есть:
-rwxr-xr-x
Так часто делают исполняемые скрипты.
Символьный режим chmod
#
Вместо чисел можно использовать символьную форму:
chmod u+x script.sh
Означает:
u user/owner
+ добавить право
x execute
То есть добавить владельцу право на выполнение.
Основные операторы:
+ добавить право
- убрать право
= выставить ровно такие права
GNU Coreutils описывает symbolic modes как способ менять права через категории пользователей u, g, o, a, операторы +, -, = и права r, w, x.
Примеры символьного режима #
Добавить выполнение владельцу:
chmod u+x script.sh
Добавить выполнение всем:
chmod +x script.sh
Убрать запись у группы и остальных:
chmod go-w file.txt
Убрать все права у группы и остальных:
chmod go-rwx .env
Выставить владельцу rw, группе и остальным только чтение:
chmod u=rw,g=r,o=r file.txt
Это примерно то же самое, что:
chmod 644 file.txt
Рекурсивное изменение прав #
Для директории и всех файлов внутри используют -R:
chmod -R 755 /var/www/app
Но с -R нужно быть осторожным. Например, если сделать chmod -R 777, ты дашь всем пользователям право читать, изменять и выполнять всё внутри директории.
Обычно для backend-проекта безопаснее так:
find /var/www/app -type d -exec chmod 755 {} \;
find /var/www/app -type f -exec chmod 644 {} \;
То есть:
директории: 755
файлы: 644
Для исполняемых скриптов отдельно:
chmod +x /var/www/app/entrypoint.sh
Частые значения прав #
600 приватный файл: .env, private key
644 обычный файл: config, .py, .txt
700 приватная директория или скрипт только для владельца
755 директория или исполняемый скрипт
777 полный доступ всем, обычно плохая практика
Пример для backend-разработчика #
Файл .env:
chmod 600 .env
Скрипт запуска контейнера:
chmod +x entrypoint.sh
Файлы проекта:
find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
Логи, которые должен писать пользователь appuser:
sudo chown appuser:appuser app.log
chmod 644 app.log
Важно: chmod меняет права, а chown меняет владельца файла.
Коротко #
chmod — это команда для изменения прав доступа к файлам и директориям в Linux. Права делятся на три группы: владелец, группа и остальные пользователи. Для каждой группы можно задать чтение r, запись w и выполнение x.
Права можно задавать численно:
chmod 644 file.txt
chmod 755 script.sh
chmod 600 .env
Или символически:
chmod +x script.sh
chmod u+x script.sh
chmod go-rwx .env
Числа считаются так:
r = 4
w = 2
x = 1
Поэтому 755 означает:
owner: rwx
group: r-x
others: r-x
28. Что делает chmod 751? #
Что делает chmod 751
#
Команда:
chmod 751 file
выставляет права доступа:
rwxr-x--x
Расшифровка:
7 owner/user rwx читать, изменять, выполнять
5 group r-x читать, выполнять, но не изменять
1 others --x только выполнять
chmod меняет file mode bits — права доступа файла или директории; числовой режим задаётся octal-числом, где r=4, w=2, x=1.
Как считается 751
#
7 = 4 + 2 + 1 = rwx
5 = 4 + 0 + 1 = r-x
1 = 0 + 0 + 1 = --x
Итог:
owner: rwx
group: r-x
others: --x
В ls -l это будет выглядеть примерно так:
-rwxr-x--x 1 user group 1234 file
Для директории:
drwxr-x--x 2 user group 4096 directory
Что это значит для файла #
Для обычного файла chmod 751 script.sh означает:
владелец может читать, изменять и запускать файл
группа может читать и запускать, но не изменять
остальные могут только запускать, но не читать и не изменять
Пример:
chmod 751 script.sh
После этого:
owner может открыть, изменить и выполнить script.sh
group может посмотреть содержимое и выполнить
others могут выполнить, но не прочитать содержимое
Что это значит для директории #
Для директории x имеет особый смысл: оно позволяет входить в директорию и проходить через неё.
chmod 751 /var/www/app
Означает:
owner может заходить, читать список файлов, создавать/удалять файлы
group может заходить и смотреть список файлов, но не изменять
others могут только проходить через директорию, но не видеть список файлов
Для others право --x на директории означает: пользователь не может сделать ls этой директории, но может обратиться к конкретному файлу внутри, если знает точное имя файла и имеет права на сам файл.
Пример:
/var/www/app/public.txt
Если у пользователя есть x на /var/www/app и права на public.txt, он может обратиться к файлу напрямую, но не сможет просмотреть список файлов в /var/www/app.
Коротко #
chmod 751 file
это:
owner rwx
group r-x
others --x
То есть владелец имеет полный доступ, группа может читать и выполнять, остальные могут только выполнять. Для файлов это встречается редко, а для директорий может использоваться, когда нужно разрешить проход к известному пути, но запретить просмотр содержимого директории.
29. Зачем нужен Docker, если можно поставить всё на Linux-сервер? | Какие преимущества Docker даёт backend-приложению? #
Поставить всё напрямую на Linux-сервер можно. Docker нужен не потому, что без него приложение невозможно запустить, а потому что он делает запуск, перенос, обновление и изоляцию backend-приложения намного управляемее.
Docker позволяет упаковать приложение вместе с зависимостями в образ, а потом запускать этот образ одинаково на разных окружениях: локально, на staging, production или в CI/CD. Docker описывает контейнер как изолированное окружение, содержащее всё необходимое для запуска приложения, без жёсткой зависимости от того, что установлено на host-сервере.
Без Docker #
Обычный деплой на Linux-сервер выглядит примерно так:
Linux server
├── system Python
├── nginx
├── PostgreSQL client
├── Redis client
├── virtualenv
├── systemd service
├── env variables
├── project files
└── logs
Проблема в том, что со временем сервер превращается в уникальную ручную конфигурацию:
на одном сервере Python 3.10
на другом Python 3.12
где-то другая версия libpq
где-то забыли поставить системный пакет
где-то другие env-переменные
где-то старый nginx-конфиг
Из-за этого появляется классическая проблема:
"У меня локально работает, а на сервере нет"
С Docker #
С Docker ты описываешь окружение в файлах:
Dockerfile
docker-compose.yml
.env
И получаешь воспроизводимый запуск:
Docker image
├── нужная версия Python
├── зависимости приложения
├── системные библиотеки
├── код приложения
└── команда запуска
Запуск:
docker run backend:1.0
Или через Compose:
docker compose up -d
Docker Compose позволяет описать multi-container приложение: backend, PostgreSQL, Redis, volume и сети в одном YAML-файле.
1. Изоляция зависимостей #
Без Docker разные приложения на одном сервере могут конфликтовать:
project A требует Python 3.10
project B требует Python 3.12
project A требует libpq v14
project B требует libpq v16
С Docker каждое приложение получает своё окружение:
container backend_1 → Python 3.10 + свои зависимости
container backend_2 → Python 3.12 + свои зависимости
container redis → Redis
container postgres → PostgreSQL
Контейнер — это не полноценная виртуальная машина, а изолированный процесс с нужными файлами для запуска; несколько контейнеров используют общее ядро host-системы.
2. Одинаковое окружение везде #
Docker image можно собрать один раз и запустить:
на ноутбуке разработчика
в CI/CD
на staging-сервере
на production-сервере
Это снижает риск, что приложение работает только на конкретной машине.
Без Docker:
надо вручную повторить установку зависимостей на каждом сервере
С Docker:
собрал image → запустил image
3. Удобный деплой #
Без Docker деплой часто выглядит так:
git pull
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
systemctl restart backend
С Docker деплой обычно проще:
docker pull registry.example.com/backend:1.2.0
docker compose up -d
Образ уже содержит нужную версию приложения и его зависимости.
4. Простые откаты #
Если новая версия сломалась, без Docker нужно откатывать код, зависимости, конфиги и иногда состояние окружения.
С Docker проще:
docker run backend:1.1.0
или в Compose поменять tag:
services:
backend:
image: backend:1.1.0
То есть каждая версия backend-сервиса может быть отдельным immutable image:
backend:1.0.0
backend:1.1.0
backend:1.2.0
5. Удобная работа с микросервисами #
Backend-приложение часто состоит не только из одного API:
backend
postgres
redis
celery worker
rabbitmq
nginx
minio
Без Docker это всё нужно ставить и настраивать вручную на сервере.
С Docker Compose можно описать всё окружение:
services:
backend:
build: .
depends_on:
- postgres
- redis
postgres:
image: postgres:16
redis:
image: redis:7
Compose управляет сервисами, сетями и volume в одном конфигурационном файле.
6. Меньше загрязняется host-сервер #
Без Docker на сервер устанавливают много пакетов:
python
pip
node
postgresql-client
redis-tools
gcc
nginx
system libraries
Со временем становится сложно понять:
какой пакет нужен какому приложению;
что можно удалить;
почему после обновления пакета сломался сервис;
какие версии библиотек реально используются.
С Docker большая часть зависимостей живёт внутри image/container, а host остаётся чище.
7. Быстрое масштабирование #
С Docker проще запустить несколько экземпляров backend:
docker compose up -d --scale backend=3
В Compose service — это абстрактное описание вычислительного ресурса приложения, который может масштабироваться или заменяться независимо от других компонентов.
Схема:
nginx
↓
backend_1
backend_2
backend_3
Для обычного Linux-сервера без Docker тоже можно поднять несколько процессов через systemd, supervisor или gunicorn workers, но Docker делает управление экземплярами более унифицированным.
8. Удобнее CI/CD #
В CI/CD Docker хорошо ложится на процесс:
1. Запустить тесты
2. Собрать Docker image
3. Присвоить tag
4. Push в registry
5. Pull на сервере
6. Запустить новую версию
Это лучше, чем каждый раз вручную повторять настройку окружения на сервере.
9. Быстрее поднять dev-окружение #
Новому разработчику не нужно вручную ставить PostgreSQL, Redis, MinIO и настраивать локальную систему.
Достаточно:
docker compose up -d
И он получает готовое окружение:
backend
database
cache
object storage
message broker
Это особенно полезно для backend-команд, где много инфраструктурных зависимостей.
10. Изоляция и безопасность #
Docker не делает приложение полностью безопасным сам по себе, но даёт дополнительную изоляцию:
отдельная файловая система
отдельная сеть
отдельное дерево процессов
ограничение ресурсов
отдельные users внутри контейнера
Docker описывает контейнеры как loosely isolated environment, а изоляция позволяет запускать много контейнеров на одном host.
Но Docker не заменяет Linux #
Важно понимать: Docker не отменяет знание Linux.
Всё равно нужно понимать:
права файлов
сети
порты
логи
systemd
диски
память
процессы
nginx
firewall
Docker работает поверх host-ОС и использует её ядро. Поэтому при проблемах всё равно приходится диагностировать Linux-сервер.
Когда Docker особенно полезен #
Docker особенно полезен, когда:
есть несколько сервисов;
нужны PostgreSQL/Redis/RabbitMQ/MinIO;
важна одинаковость окружений;
есть CI/CD;
нужно быстро деплоить и откатывать версии;
нужно масштабировать backend;
несколько проектов живут на одном сервере;
команда из нескольких разработчиков.
Когда можно обойтись без Docker #
Без Docker можно нормально жить, если:
проект маленький;
один сервер;
один backend;
нет сложных зависимостей;
деплой простой;
команда маленькая;
окружение редко меняется.
Например, маленький Django/FastAPI-проект можно запустить напрямую через:
venv + gunicorn/uvicorn + systemd + nginx
Это рабочий подход. Docker просто делает окружение более воспроизводимым и удобным для переноса.
Коротко #
Docker нужен не потому, что backend нельзя запустить напрямую на Linux-сервере, а потому что Docker упаковывает приложение вместе с зависимостями в единый image и позволяет запускать его одинаково в разных окружениях. Это упрощает деплой, снижает конфликты зависимостей, облегчает откаты, масштабирование и работу с микросервисами.
Главные преимущества Docker для backend-приложения:
изоляция зависимостей;
одинаковое окружение local/staging/prod;
быстрый деплой;
простые откаты версий;
удобный запуск PostgreSQL/Redis/RabbitMQ рядом с backend;
меньше ручной настройки сервера;
удобная интеграция с CI/CD;
проще масштабировать приложение.
Но Docker не заменяет Linux и не делает архитектуру автоматически правильной. Он решает проблему упаковки, запуска и управления окружением backend-сервиса.