Docker, Docker Compose, Linux

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

Сравнение #

ПараметрCMDENTRYPOINT
НазначениеКоманда/аргументы по умолчаниюГлавная команда контейнера
Легко переопределяется через 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-сервиса.