"Коротко:
<br />
Полный справочник команд Docker с примерами из реальных сценариев. Контейнеры, образы, сети, тома, compose, очистка, безопасность и troubleshooting. Копируй команду — выполняй — проверяй результат. Без предисловий на трёх страницах.<br />
<p>Ты поднял контейнер — что-то не работает. Или работает, но непонятно как. Или работало, а потом перестало. Эта шпаргалка закроет 90% вопросов по <a href="https://it-apteka.com/1243-2/" title="Сервер активации Windows (KMS) в Docker: варианты развёртывания и интеграция с Active Directory" target="_blank" rel="noopener" data-wpil-monitor-id="2224">Docker</a> которые возникают в реальной работе на сервере.</p>
<p>Здесь не будет рассказа о том, что такое контейнеризация и почему <a class="wpil_keyword_link" href="https://it-apteka.com/tag/docker/" target="_blank" rel="noopener" title="Docker" data-wpil-keyword-link="linked" data-wpil-monitor-id="2239">Docker</a> это хорошо. Ты это уже знаешь. Здесь команды, примеры, объяснение почему именно так, и что делать когда что-то пошло не по плану.</p>
<p><strong>Что понадобится:</strong> сервер или VPS с Ubuntu/Debian/CentOS, root или sudo, установленный Docker. Установку разберём отдельным блоком.</p>
<p><strong>Время на освоение:</strong> зависит от задачи. Базовые команды — 20 минут. Compose и <a class="wpil_keyword_link" href="https://it-apteka.com/category/networks/" target="_blank" rel="noopener" title="Сети" data-wpil-keyword-link="linked" data-wpil-monitor-id="2223">сети</a> — ещё час. Полный справочник оставь открытым и возвращайся по мере необходимости.</p>
<h2>Содержание</h2>
<ul>
<li>Архитектура Docker: как это работает</li>
<li>Установка Docker на Ubuntu и <a class="wpil_keyword_link" href="https://it-apteka.com/tag/debian/" target="_blank" rel="noopener" title="Debian" data-wpil-keyword-link="linked" data-wpil-monitor-id="2222">Debian</a></li>
<li>Системные команды: daemon, info, версия</li>
<li>Образы: pull, build, tag, push</li>
<li>Контейнеры: run, ps, exec, stop, rm</li>
<li>Логи и диагностика</li>
<li>Volumes: хранение данных</li>
<li>Networks: связь между контейнерами</li>
<li>Docker Compose: запуск стека</li>
<li>Dockerfile: собрать образ своими руками</li>
<li>Продакшен: лимиты, рестарт, <a class="wpil_keyword_link" href="https://it-apteka.com/category/security/" target="_blank" rel="noopener" title="Безопасность" data-wpil-keyword-link="linked" data-wpil-monitor-id="2219">безопасность</a></li>
<li>Очистка и обслуживание</li>
<li>Troubleshooting</li>
<li>FAQ</li>
</ul>
<h2>Архитектура Docker: как это работает</h2>
<p>Прежде чем копировать команды — одна минута на структуру. Это сэкономит час отладки позже.</p>
<pre class="mermaid">%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
CLI["Docker CLI\ndocker run / ps / exec"] --> |"API запрос"| D["Docker Daemon\n(dockerd)"]
D --> |"создаёт"| C1["Контейнер 1"]
D --> |"создаёт"| C2["Контейнер 2"]
D --> |"управляет"| I["Docker Images\n(образы)"]
D --> |"управляет"| V["Volumes\n(данные)"]
D --> |"управляет"| N["Networks\n(сети)"]
C1 --> V
C2 --> V
C1 --> N
C2 --> N
style CLI fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style C1 fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style C2 fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style I fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#1e293b
style V fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#1e293b
style N fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#1e293b
</pre>
<p><strong>Docker daemon (dockerd)</strong> — это сердце системы. Он получает команды от CLI, управляет образами, контейнерами, сетями и томами. Если daemon не запущен — ничего не работает вообще. Первая точка проверки при любой проблеме.</p>
<p><strong>Образ (image)</strong> — шаблон. Неизменяемый. Из одного образа можно запустить сколько угодно контейнеров.</p>
<p><strong>Контейнер (container)</strong> — запущенный экземпляр образа. Изолированный процесс с собственной файловой системой. Останови его — данные внутри исчезнут, если не используешь volume.</p>
<p><strong>Volume</strong> — постоянное хранилище, живёт отдельно от контейнера. База данных без volume — это не продакшен, это лотерея.</p>
<h2>Установка Docker на Ubuntu и Debian</h2>
<p>Есть два пути: docker.io из репозитория дистрибутива и официальный репозиторий Docker Inc. Первый — проще. Второй — свежее версия и это важно если используешь Compose v2 или BuildKit.</p>
<h3>Вариант 1: из репозитория Ubuntu/Debian (проще)</h3>
<pre><code class="language-bash">
apt update && apt upgrade -y
apt install -y docker.io
systemctl start docker
systemctl enable docker
</code></pre>
<h3>Вариант 2: официальный репозиторий Docker (рекомендую для продакшена)</h3>
<pre><code class="language-bash">
# Убираем старые версии если есть
apt remove docker docker-engine docker.io containerd runc 2>/dev/null
# Зависимости
apt update
apt install -y ca-certificates curl gnupg lsb-release
# GPG ключ
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Репозиторий
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Установка
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
</code></pre>
<h3>Проверяем установку</h3>
<pre><code class="language-bash">
docker --version
docker info
systemctl status docker
</code></pre>
<p>После <code>docker --version</code> должна появиться версия. После <code>docker info</code> — подробная информация о системе, контейнерах, образах, хранилище. Если вылезло <code>Cannot connect to the Docker daemon</code> — daemon не запущен, смотри следующий раздел.</p>
<h3>Добавляем пользователя в группу docker</h3>
<p>Чтобы не запускать каждую команду через sudo:</p>
<pre><code class="language-bash">
usermod -aG docker $USER
newgrp docker
</code></pre>
"Перезайди
<br />
После добавления в группу нужно выйти и зайти заново или выполнить newgrp docker. Иначе права не применятся и docker команды будут давать permission denied.<br />
<h3>Таблица: совместимость версий</h3>
<table>
<thead>
<tr>
<th>ОС</th>
<th>Версия</th>
<th>Docker CE</th>
<th>Compose Plugin</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ubuntu</td>
<td>22.04 LTS</td>
<td>24.x, 25.x, 26.x</td>
<td>v2.x</td>
</tr>
<tr>
<td>Ubuntu</td>
<td>24.04 LTS</td>
<td>26.x, 27.x</td>
<td>v2.x</td>
</tr>
<tr>
<td>Debian</td>
<td>11 (Bullseye)</td>
<td>24.x, 25.x</td>
<td>v2.x</td>
</tr>
<tr>
<td>Debian</td>
<td>12 (Bookworm)</td>
<td>26.x, 27.x</td>
<td>v2.x</td>
</tr>
<tr>
<td>CentOS/RHEL</td>
<td>8, 9</td>
<td>25.x, 26.x</td>
<td>v2.x</td>
</tr>
</tbody>
</table>
<p>На момент публикации актуальна версия Docker 27.x. Перед установкой проверь свежие релизы на <a href="https://docs.docker.com/engine/release-notes/" target="_blank" rel="noopener">docs.docker.com/engine/release-notes</a>.</p>
<h2>Системные команды: daemon, info, версия</h2>
<h3>Управление daemon через systemctl</h3>
<pre><code class="language-bash">
# Статус
systemctl status docker
# Запуск
systemctl start docker
# Перезапуск
systemctl restart docker
# Остановка
systemctl stop docker
# Автозапуск при старте системы
systemctl enable docker
# Отключить автозапуск
systemctl disable docker
</code></pre>
<h3>Информация о системе</h3>
<pre><code class="language-bash">
# Версия клиента и сервера
docker version
# Подробная инфо: контейнеры, образы, хранилище, драйвер
docker info
# Использование диска Docker
docker system df
# Подробно с размерами
docker system df -v
</code></pre>
<p>Команда <code>docker system df</code> покажет сколько места занимают образы, контейнеры, volumes и build cache. На серверах которые давно не чистили цифра бывает неприятно большой.</p>
<h3>Логи docker daemon</h3>
<pre><code class="language-bash">
# Логи daemon в journald
journalctl -u docker
# Последние 100 строк
journalctl -u docker -n 100
# В реальном времени
journalctl -u docker -f
</code></pre>
<h2>Образы: pull, build, tag, push</h2>
<h3>Работа с Docker Hub</h3>
<pre><code class="language-bash">
# Скачать образ последней версии
docker pull nginx
# Конкретная версия (тег)
docker pull nginx:1.25
# Конкретная версия под архитектуру
docker pull --platform linux/amd64 nginx:1.25
# Список скачанных образов
docker images
# Подробный список с датами
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
# Удалить образ
docker rmi nginx:1.25
# Удалить образ принудительно (если используется контейнером)
docker rmi -f nginx:1.25
</code></pre>
<h3>Поиск образов</h3>
<pre><code class="language-bash">
# Поиск на Docker Hub
docker search nginx
# Только официальные образы
docker search --filter is-official=true nginx
</code></pre>
<h3>Авторизация в реестре</h3>
<pre><code class="language-bash">
# Docker Hub
docker login
# Приватный реестр
docker login registry.example.com
# Выйти
docker logout
</code></pre>
<h3>Tag и push</h3>
<pre><code class="language-bash">
# Пометить образ своим тегом
docker tag nginx:latest myregistry.com/myapp:1.0
# Отправить в реестр
docker push myregistry.com/myapp:1.0
</code></pre>
<h3>Инспект образа</h3>
<pre><code class="language-bash">
# Метаданные образа
docker inspect nginx
# Только конкретное поле (например, переменные окружения)
docker inspect --format='{{.Config.Env}}' nginx
# История слоёв образа
docker history nginx
</code></pre>
<h2>Контейнеры: run, ps, exec, stop, rm</h2>
<p>Это основа. Здесь половина всех команд которые ты будешь использовать каждый день.</p>
<h3>docker run: запуск контейнера</h3>
<pre><code class="language-bash">
# Простой запуск (в foreground, Ctrl+C останавливает)
docker run nginx
# Фоновый режим (detached)
docker run -d nginx
# С именем
docker run -d --name web nginx
# Проброс порта: порт_хоста:порт_контейнера
docker run -d -p 8080:80 --name web nginx
# Переменные окружения
docker run -d -e MY_VAR=value --name web nginx
# Несколько переменных
docker run -d -e DB_HOST=localhost -e DB_PORT=5432 --name app myapp
# Монтирование директории хоста
docker run -d -v /host/path:/container/path nginx
# Монтирование volume
docker run -d -v myvolume:/data nginx
# Автоматически удалять контейнер после остановки
docker run --rm nginx
# Интерактивный режим (зайти внутрь сразу)
docker run -it ubuntu bash
# Ограничение памяти
docker run -d --memory="512m" nginx
# Ограничение CPU
docker run -d --cpus="1.5" nginx
# Политика рестарта
docker run -d --restart=always nginx
# Подключить к сети
docker run -d --network mynetwork nginx
</code></pre>
<p>Флаги <code>-d</code> и <code>-it</code> не совместимы как основной режим: <code>-d</code> запускает в фоне, <code>-it</code> открывает терминал. Для продакшена почти всегда <code>-d</code>. Для отладки — <code>-it --rm</code>.</p>
<h3>Таблица: политики рестарта</h3>
<table>
<thead>
<tr>
<th>Политика</th>
<th>Поведение</th>
<th>Когда использовать</th>
</tr>
</thead>
<tbody>
<tr>
<td>no</td>
<td>Не перезапускать (по умолчанию)</td>
<td>Разработка, тестовые запуски</td>
</tr>
<tr>
<td>always</td>
<td>Всегда перезапускать</td>
<td>Продакшен сервисы</td>
</tr>
<tr>
<td>unless-stopped</td>
<td>Перезапускать, кроме ручной остановки</td>
<td>Продакшен, гибкий контроль</td>
</tr>
<tr>
<td>on-failure</td>
<td>Перезапускать только при ненулевом exit code</td>
<td>Задачи с обработкой ошибок</td>
</tr>
<tr>
<td>on-failure:3</td>
<td>Не более 3 попыток перезапуска</td>
<td>Предотвращение restart loop</td>
</tr>
</tbody>
</table>
<h3>Управление запущенными контейнерами</h3>
<pre><code class="language-bash">
# Список запущенных контейнеров
docker ps
# Все контейнеры включая остановленные
docker ps -a
# Только ID контейнеров
docker ps -q
# Форматированный вывод
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Остановить контейнер (SIGTERM, ждёт 10 секунд)
docker stop web
# Остановить немедленно (SIGKILL)
docker kill web
# Запустить остановленный
docker start web
# Перезапустить
docker restart web
# Пауза / снятие паузы
docker pause web
docker unpause web
# Переименовать
docker rename web web-old
# Удалить остановленный контейнер
docker rm web
# Удалить принудительно (работающий)
docker rm -f web
# Удалить все остановленные контейнеры
docker container prune
# Инспект контейнера (полная информация)
docker inspect web
# Статистика ресурсов в реальном времени
docker stats
# Статистика одного контейнера без обновления
docker stats web --no-stream
# Процессы внутри контейнера
docker top web
</code></pre>
<h3>docker exec: работа внутри контейнера</h3>
<pre><code class="language-bash">
# Зайти в bash
docker exec -it web bash
# Если bash не установлен - sh
docker exec -it web sh
# Выполнить одну команду
docker exec web nginx -t
# Выполнить команду от имени конкретного пользователя
docker exec -u www-data web whoami
# Открыть ещё один терминал в уже подключённой сессии
docker exec -it web bash
</code></pre>
<p>Если контейнер отвечает на <code>docker exec -it web bash</code> ошибкой <code>OCI runtime exec failed</code> — контейнер либо не запущен, либо <a class="wpil_keyword_link" href="https://it-apteka.com/tag/bash/" target="_blank" rel="noopener" title="Bash" data-wpil-keyword-link="linked" data-wpil-monitor-id="2218">bash</a> в нём нет. Проверь через <code>docker ps</code> что он вообще работает.</p>
<h3>Копирование файлов</h3>
<pre><code class="language-bash">
# Скопировать файл из контейнера на хост
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
# Скопировать файл с хоста в контейнер
docker cp ./nginx.conf web:/etc/nginx/nginx.conf
# Скопировать директорию
docker cp web:/var/log/nginx/ ./logs/
</code></pre>
<h2>Логи и диагностика</h2>
<h3>Команды для работы с логами</h3>
<pre><code class="language-bash">
# Показать все логи контейнера
docker logs web
# Последние N строк
docker logs web --tail 100
# Следить в реальном времени (как tail -f)
docker logs -f web
# С временными метками
docker logs -t web
# Логи за последний час
docker logs --since 1h web
# Логи с определённого времени
docker logs --since "2024-01-15T10:00:00" web
# Логи между двумя временными точками
docker logs --since 2h --until 1h web
</code></pre>
<h3>Диагностика без захода внутрь</h3>
<pre><code class="language-bash">
# Полный инспект контейнера в JSON
docker inspect web
# IP адрес контейнера
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
# Подключённые порты
docker inspect -f '{{.NetworkSettings.Ports}}' web
# Переменные окружения
docker inspect -f '{{.Config.Env}}' web
# Смонтированные тома
docker inspect -f '{{.Mounts}}' web
# События в реальном времени (старт/стоп контейнеров и т.п.)
docker events
# События за последние 30 минут
docker events --since 30m
</code></pre>
<h2>Volumes: хранение данных</h2>
<p>Всё что записывается внутрь контейнера — исчезает при его удалении. Volumes решают эту проблему. Для баз данных, конфигов и любых данных которые должны пережить перезапуск контейнера — только volumes.</p>
<h3>Управление volumes</h3>
<pre><code class="language-bash">
# Создать volume
docker volume create pgdata
# Список volumes
docker volume ls
# Инспект volume (где хранится, кто использует)
docker volume inspect pgdata
# Удалить volume
docker volume rm pgdata
# Удалить все неиспользуемые volumes
docker volume prune
# Удалить volume принудительно
docker volume rm -f pgdata
</code></pre>
<h3>Типы монтирования</h3>
<pre><code class="language-bash">
# Volume (рекомендуется для данных)
docker run -d -v pgdata:/var/lib/postgresql/data postgres
# Bind mount (конкретная папка хоста)
docker run -d -v /home/user/data:/app/data myapp
# Tmpfs mount (только в RAM, не сохраняется)
docker run -d --tmpfs /tmp myapp
# Только чтение
docker run -d -v /host/config:/app/config:ro myapp
</code></pre>
<h3>Где физически лежат volumes</h3>
<pre><code class="language-bash">
# Путь на хосте
docker volume inspect pgdata --format '{{.Mountpoint}}'
# Обычно: /var/lib/docker/volumes/pgdata/_data
</code></pre>
<h3>Backup и восстановление volume</h3>
<pre><code class="language-bash">
# Создать бэкап volume в tar.gz
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
ubuntu tar czf /backup/pgdata-backup.tar.gz -C /data .
# Восстановить из бэкапа
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
ubuntu bash -c "cd /data && tar xzf /backup/pgdata-backup.tar.gz"
</code></pre>
<p><a href="https://it-apteka.com/docker-compose-ustanovka-komandy-i-nastrojka-kontejnerov/" title="Docker Compose — установка, команды и настройка контейнеров" target="_blank" rel="noopener" data-wpil-monitor-id="2225">Команда создаёт временный контейнер</a> ubuntu, монтирует в него volume и директорию хоста, упаковывает данные в архив. Контейнер удаляется сам после завершения — за это отвечает <code>--rm</code>.</p>
"Бэкап
<br />
Для PostgreSQL и <a class="wpil_keyword_link" href="https://it-apteka.com/tag/mysql/" target="_blank" rel="noopener" title="MySQL" data-wpil-keyword-link="linked" data-wpil-monitor-id="2217">MySQL</a> не используй прямой backup volume файлов пока база запущена. Используй pg_dump или mysqldump. Бэкап файлов на работающей БД может дать повреждённые данные. Это не параноя, это стандарт.<br />
<h2>Networks: связь между контейнерами</h2>
<p>По умолчанию контейнеры могут общаться только внутри одной сети. Правильно организованные сети — это ещё и изоляция: database не должна быть доступна снаружи, только из app-контейнера.</p>
<h3>Управление сетями</h3>
<pre><code class="language-bash">
# Список сетей
docker network ls
# Создать bridge сеть
docker network create backend
# Создать с настройками подсети
docker network create --subnet=172.20.0.0/24 backend
# Инспект сети
docker network inspect backend
# Удалить сеть
docker network rm backend
# Удалить все неиспользуемые сети
docker network prune
</code></pre>
<h3>Типы сетей</h3>
<table>
<thead>
<tr>
<th>Тип</th>
<th>Описание</th>
<th>Когда использовать</th>
</tr>
</thead>
<tbody>
<tr>
<td>bridge</td>
<td>Изолированная сеть на хосте (по умолчанию)</td>
<td>Большинство сценариев</td>
</tr>
<tr>
<td>host</td>
<td>Контейнер использует сеть хоста напрямую</td>
<td>Высокая производительность, осторожно с безопасностью</td>
</tr>
<tr>
<td>none</td>
<td>Нет сети</td>
<td>Полная изоляция</td>
</tr>
<tr>
<td>overlay</td>
<td>Сеть между несколькими хостами</td>
<td>Docker Swarm, multi-host</td>
</tr>
<tr>
<td>macvlan</td>
<td>Контейнер получает MAC-адрес в физической сети</td>
<td>Legacy системы, требующие Layer 2</td>
</tr>
</tbody>
</table>
<h3>Подключение контейнеров к сети</h3>
<pre><code class="language-bash">
# Запустить контейнер сразу в нужной сети
docker run -d --name db --network backend postgres
# Подключить работающий контейнер к сети
docker network connect backend web
# Отключить контейнер от сети
docker network disconnect backend web
# Контейнер в нескольких сетях
docker network connect frontend web
</code></pre>
<h3>Связь между контейнерами</h3>
<p>Контейнеры в одной user-defined сети могут обращаться друг к другу по имени. Это автоматический <a class="wpil_keyword_link" href="https://it-apteka.com/tag/dns/" target="_blank" rel="noopener" title="DNS" data-wpil-keyword-link="linked" data-wpil-monitor-id="2221">DNS</a> внутри Docker.</p>
<pre class="mermaid">%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart LR
U["Пользователь\n:80"] --> |"порт 80"| N["Nginx\ncontainer"]
N --> |"backend сеть\napp:8000"| A["App\ncontainer"]
A --> |"backend сеть\ndb:5432"| D["PostgreSQL\ncontainer"]
style U fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style N fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style A fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
</pre>
<pre><code class="language-bash">
# Пример: app обращается к db по имени "db"
docker run -d --name db --network backend \
-e POSTGRES_PASSWORD=secret postgres
docker run -d --name app --network backend \
-e DB_HOST=db \
-e DB_PORT=5432 \
myapp
</code></pre>
<p>Обрати внимание: <code>DB_HOST=db</code> — это именно имя контейнера. <a href="https://it-apteka.com/avtomaticheskoe-obnovlenie-docker-kontejnerov-polnoe-rukovodstvo-i-primery/" title="Автоматическое обновление Docker контейнеров: полное руководство и примеры" target="_blank" rel="noopener" data-wpil-monitor-id="2226">Docker внутри сети разрешает имена контейнеров</a> в IP адреса автоматически. Никаких hosts файлов, никаких ручных IP.</p>
<h2>Dockerfile: собрать образ</h2>
<p>Dockerfile — текстовый файл с инструкциями для сборки образа. Каждая инструкция создаёт новый слой. Слои кэшируются — поэтому порядок инструкций влияет на скорость повторных сборок.</p>
<h3>Базовая структура Dockerfile</h3>
<pre><code class="language-text">
# Базовый образ
FROM ubuntu:22.04
# Метаданные
LABEL maintainer="admin@example.com"
# Переменные сборки
ARG APP_VERSION=1.0
# Переменные окружения
ENV APP_HOME=/app
# Рабочая директория
WORKDIR /app
# Копировать файлы
COPY . .
# Установка зависимостей
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Установить зависимости Python
RUN pip3 install -r requirements.txt
# Открыть порт (документация, не реальный проброс)
EXPOSE 8000
# Пользователь для запуска (не root!)
USER nobody
# Команда запуска
CMD ["python3", "app.py"]
</code></pre>
<h3>Сборка образа</h3>
<pre><code class="language-bash">
# Собрать с тегом
docker build -t myapp:1.0 .
# Собрать из конкретного Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# Без кэша
docker build --no-cache -t myapp:1.0 .
# С аргументами сборки
docker build --build-arg APP_VERSION=2.0 -t myapp:2.0 .
# Посмотреть слои при сборке
docker build --progress=plain -t myapp:1.0 .
</code></pre>
<h3>Многоступенчатая сборка (multi-stage build)</h3>
<p>Уменьшает итоговый размер образа: в первом этапе компилируем, во второй копируем только бинарник.</p>
<pre><code class="language-text">
# Этап 1: сборка
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# Этап 2: финальный образ
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
</code></pre>
<p>Итоговый образ содержит только alpine и бинарник. Никаких компиляторов, исходников и зависимостей сборки. Размер падает с гигабайт до десятков мегабайт. Это не магия, это архитектура.</p>
<h2>Docker Compose: запуск стека</h2>
<p>Compose позволяет описать несколько сервисов в одном файле и запустить их одной командой. Версия 2 (compose plugin) встроена в Docker и вызывается через <code>docker compose</code> без дефиса.</p>
<h3>Базовый docker-compose.yml</h3>
<pre><code class="language-text">
version: "3.8"
services:
web:
image: nginx:1.25
container_name: web
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:ro
networks:
- frontend
depends_on:
- app
app:
build:
context: .
dockerfile: Dockerfile
container_name: app
restart: unless-stopped
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=mydb
- DB_USER=appuser
- DB_PASSWORD=${DB_PASSWORD}
networks:
- frontend
- backend
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
container_name: db
restart: unless-stopped
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
networks:
frontend:
backend:
volumes:
pgdata:
</code></pre>
"Пароли
<br />
Переменная DB_PASSWORD берётся из файла .env в той же директории. Создай файл .env с содержимым DB_PASSWORD=yourpassword и добавь его в .gitignore. Пароли в docker-compose.yml это не практика, это катастрофа которая ждёт своего момента.<br />
<h3>Команды Docker Compose</h3>
<pre><code class="language-bash">
# Запустить стек в фоне
docker compose up -d
# Запустить с пересборкой образов
docker compose up -d --build
# Остановить стек (контейнеры останавливаются, не удаляются)
docker compose stop
# Остановить и удалить контейнеры
docker compose down
# Остановить, удалить контейнеры и volumes
docker compose down -v
# Остановить, удалить и удалить образы
docker compose down --rmi all
# Статус сервисов
docker compose ps
# Логи всех сервисов
docker compose logs
# Логи конкретного сервиса в реальном времени
docker compose logs -f app
# Зайти в контейнер сервиса
docker compose exec app bash
# Выполнить команду в сервисе
docker compose exec db psql -U appuser mydb
# Перезапустить один сервис без остановки других
docker compose restart app
# Масштабировать сервис
docker compose up -d --scale app=3
# Посмотреть переменные окружения
docker compose config
</code></pre>
<h3>Healthcheck в Compose</h3>
<p>Конструкция <code>depends_on: condition: service_healthy</code> дождётся пока база не пройдёт healthcheck перед запуском app. Без этого app стартует раньше базы и получает ошибку подключения.</p>
<pre><code class="language-text">
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
</code></pre>
<h2>Docker для продакшена: настройки которые нельзя пропускать</h2>
<h3>Ограничения ресурсов</h3>
<pre><code class="language-bash">
# Ограничение памяти и CPU через run
docker run -d \
--name app \
--memory="512m" \
--memory-swap="512m" \
--cpus="2.0" \
--restart=unless-stopped \
myapp
# Обновить лимиты работающего контейнера
docker update --memory="1g" --cpus="2.0" app
</code></pre>
<p><code>--memory-swap</code> равный <code>--memory</code> означает что swap не используется. Если не задать — контейнер сможет использовать swap в два раза больше лимита памяти. На продакшене это нежелательно.</p>
<h3>Настройка docker daemon для продакшена</h3>
<p>Файл конфигурации: <code>/etc/docker/daemon.json</code></p>
<pre><code class="language-text">
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
},
"storage-driver": "overlay2",
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true
}
</code></pre>
<p>Что здесь важно:</p>
<ul>
<li><code>log-driver: json-file</code> + ограничения — без этого логи растут бесконечно и заполняют диск. Проверенный способ убить сервер в 3 ночи.</li>
<li><code>live-restore: true</code> — контейнеры продолжают работать при перезапуске daemon. Для обновления Docker без даунтайма.</li>
<li><code>no-new-privileges: true</code> — процессы внутри контейнера не могут получить новые привилегии через setuid.</li>
</ul>
<pre><code class="language-bash">
# Применить изменения daemon.json
systemctl restart docker
</code></pre>
<h3>Безопасность: минимальный набор</h3>
<pre><code class="language-bash">
# Никогда не запускать как root если можно без этого
docker run -d --user 1000:1000 myapp
# Read-only файловая система
docker run -d --read-only myapp
# Запретить запись в tmp (если приложение не требует)
docker run -d --read-only --tmpfs /tmp myapp
# Без дополнительных capabilities
docker run -d --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
# Запрет эскалации привилегий
docker run -d --security-opt no-new-privileges myapp
</code></pre>
<h3>Firewall: порты Docker и UFW</h3>
"Важно:
<br />
Docker по умолчанию напрямую модифицирует iptables и обходит правила UFW. Если ты открыл порт через docker run -p, он будет доступен всем даже если UFW его закрывает. Чтобы этого не было — используй 127.0.0.1 для привязки портов которые не должны быть публичными.<br />
<pre><code class="language-bash">
# Привязать порт только к localhost (не публичный)
docker run -d -p 127.0.0.1:5432:5432 postgres
# Публичный порт (доступен всем)
docker run -d -p 0.0.0.0:80:80 nginx
# Или просто
docker run -d -p 80:80 nginx
</code></pre>
<h3>Таблица портов: стандартные сервисы в Docker</h3>
<table>
<thead>
<tr>
<th>Сервис</th>
<th>Порт контейнера</th>
<th>Рекомендуемая привязка</th>
<th>Примечание</th>
</tr>
</thead>
<tbody>
<tr>
<td>Nginx/Apache</td>
<td>80, 443</td>
<td>0.0.0.0:80:80</td>
<td>Публичный</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>5432</td>
<td>127.0.0.1:5432:5432</td>
<td>Только localhost</td>
</tr>
<tr>
<td>MySQL/MariaDB</td>
<td>3306</td>
<td>127.0.0.1:3306:3306</td>
<td>Только localhost</td>
</tr>
<tr>
<td>Redis</td>
<td>6379</td>
<td>127.0.0.1:6379:6379</td>
<td>Только localhost</td>
</tr>
<tr>
<td>MongoDB</td>
<td>27017</td>
<td>127.0.0.1:27017:27017</td>
<td>Только localhost</td>
</tr>
<tr>
<td>RabbitMQ</td>
<td>5672, 15672</td>
<td>127.0.0.1:15672:15672</td>
<td>Management только localhost</td>
</tr>
</tbody>
</table>
<h2>Очистка и обслуживание</h2>
<p>Docker накапливает мусор: остановленные контейнеры, неиспользуемые образы, осиротевшие volumes и build cache. На серверах которые работают больше года без чистки это запросто 50+ гигабайт.</p>
<pre><code class="language-bash">
# Посмотреть что занимает место
docker system df
docker system df -v
# Удалить всё неиспользуемое (контейнеры, сети, образы без тегов, build cache)
docker system prune
# То же самое но и volumes
docker system prune --volumes
# Удалить вообще всё неиспользуемое включая образы с тегами
docker system prune -a
# Только остановленные контейнеры
docker container prune
# Только образы без тегов (dangling)
docker image prune
# Все образы без запущенных контейнеров
docker image prune -a
# Только volumes без контейнеров
docker volume prune
# Только неиспользуемые сети
docker network prune
# Только build cache
docker builder prune
</code></pre>
"docker
<br />
Эта команда удаляет вообще всё: и образы, и тома без контейнеров. Если у тебя есть <a href="https://it-apteka.com/docker-volumes-kak-hranit-dannye-kontejnerov-i-ne-poterjat-ih-posle-prune/" target="_blank" rel="noopener" data-wpil-monitor-id="2857">данные только в volumes без контейнера</a> — они исчезнут. Проверь <a href="https://it-apteka.com/311/" title="Бэкап Docker Compose: автоматизация через Bash и Crontab" target="_blank" rel="noopener" data-wpil-monitor-id="2227">docker volume ls и сделай бэкап</a> перед выполнением.<br />
<h3>Автоматическая очистка через cron</h3>
<pre><code class="language-bash">
# Добавить в crontab (каждое воскресенье в 3 ночи)
crontab -e
</code></pre>
<pre><code class="language-text">
0 3 * * 0 /usr/bin/docker system prune -f >> /var/log/docker-prune.log 2>&1
</code></pre>
<p>Флаг <code>-f</code> убирает запрос подтверждения. Без него cron задача просто зависнет.</p>
<h2>Обновление Docker и контейнеров</h2>
<h3>Обновление самого Docker</h3>
<pre><code class="language-bash">
# Проверить текущую версию
docker --version
# Обновить (если ставили из официального репозитория)
apt update
apt upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Проверить что daemon работает после обновления
systemctl status docker
docker ps
</code></pre>
<p>Если включён <code>live-restore: true</code> в daemon.json — контейнеры продолжат работать во время перезапуска daemon при обновлении. Без этого флага все контейнеры остановятся.</p>
<h3>Обновление контейнеров (вручную)</h3>
<pre><code class="language-bash">
# Скачать новый образ
docker pull nginx:latest
# Остановить старый контейнер
docker stop web
# Удалить старый контейнер (данные в volumes сохранятся)
docker rm web
# Запустить с новым образом
docker run -d --name web -p 80:80 --restart=unless-stopped nginx:latest
# Удалить старый образ
docker image prune
</code></pre>
<h3>Обновление стека через Compose</h3>
<pre><code class="language-bash">
# Скачать новые образы
docker compose pull
# Пересобрать и перезапустить
docker compose up -d --build
# Только пересоздать контейнеры с новыми образами без сборки
docker compose up -d --pull always
</code></pre>
<h3>Откат к предыдущей версии</h3>
<pre><code class="language-bash">
# Посмотреть доступные теги через Hub
docker search nginx
# Запустить конкретную версию
docker run -d --name web -p 80:80 nginx:1.24
# В compose - изменить тег в файле и перезапустить
# image: nginx:1.24
docker compose up -d
</code></pre>
<h2>Troubleshooting</h2>
<h3>Cannot connect to the Docker daemon</h3>
<p>Daemon не запущен или нет прав.</p>
<pre><code class="language-bash">
# Проверить статус
systemctl status docker
# Запустить
systemctl start docker
# Проверить права текущего пользователя
groups $USER
# Добавить в группу docker
usermod -aG docker $USER
newgrp docker
</code></pre>
<h3>Port is already allocated / address already in use</h3>
<p>Порт занят другим процессом или контейнером.</p>
<pre><code class="language-bash">
# Найти что занимает порт
ss -tlnp | grep :80
lsof -i :80
# Найти контейнер который использует порт
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep 80
# Остановить конкурирующий контейнер
docker stop old_container
</code></pre>
<h3>No space left on device</h3>
<p>Закончилось место на диске.</p>
<pre><code class="language-bash">
# Проверить диск
df -h
du -sh /var/lib/docker/*
# Очистить Docker
docker system prune -f
# Если не помогает - смотри логи контейнеров (они могут занимать много места)
find /var/lib/docker/containers -name "*.log" -exec du -sh {} \; | sort -rh | head
</code></pre>
<h3>Container exits immediately (exit code 1 или 137)</h3>
<pre><code class="language-bash">
# Посмотреть логи остановленного контейнера
docker logs mycontainer
# Запустить интерактивно для диагностики
docker run -it --entrypoint bash myimage
# Exit code 137 - контейнер убит по OOM (нехватка памяти)
# Проверить системные сообщения
journalctl -k | grep -i "oom"
dmesg | grep -i "oom"
</code></pre>
<h3>Контейнер не может подключиться к другому контейнеру</h3>
<pre><code class="language-bash">
# Проверить что оба в одной сети
docker inspect container1 | grep -A 20 Networks
docker inspect container2 | grep -A 20 Networks
# Пинг по имени изнутри контейнера
docker exec container1 ping container2
# Проверить DNS
docker exec container1 nslookup container2
# Создать общую сеть и подключить оба
docker network create shared
docker network connect shared container1
docker network connect shared container2
</code></pre>
<h3>Образ не собирается / COPY failed / файл не найден</h3>
<pre><code class="language-bash">
# Проверить .dockerignore - возможно файл исключён
cat .dockerignore
# Собрать с verbose выводом
docker build --progress=plain -t myapp . 2>&1 | tee build.log
# Проверить context сборки (что отправляется в daemon)
docker build --no-cache -t myapp . 2>&1 | head -5
</code></pre>
<h3>Volume не монтируется / Permission denied</h3>
<pre><code class="language-bash">
# Проверить владельца директории на хосте
ls -la /host/path
# Проверить UID внутри контейнера
docker run --rm myimage id
# Исправить права
chown -R 1000:1000 /host/path
# Или запустить контейнер с нужным пользователем
docker run -d --user $(id -u):$(id -g) -v /host/path:/app/data myapp
</code></pre>
<h3>docker compose up зависает на «Waiting for container…»</h3>
<pre><code class="language-bash">
# Посмотреть статус healthcheck
docker inspect db | grep -A 10 Health
# Проверить логи сервиса
docker compose logs db
# Запустить без depends_on для диагностики
docker compose up db
</code></pre>
<h3>Таблица: коды выхода контейнеров</h3>
<table>
<thead>
<tr>
<th>Exit Code</th>
<th>Причина</th>
<th>Что делать</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Штатное завершение</td>
<td>Норма для одноразовых задач</td>
</tr>
<tr>
<td>1</td>
<td>Ошибка приложения</td>
<td>Смотреть docker logs</td>
</tr>
<tr>
<td>125</td>
<td>Ошибка docker run</td>
<td>Проверить параметры команды</td>
</tr>
<tr>
<td>126</td>
<td>Команда найдена, но не исполняема</td>
<td>Проверить права на файл</td>
</tr>
<tr>
<td>127</td>
<td>Команда не найдена</td>
<td>Проверить entrypoint/cmd</td>
</tr>
<tr>
<td>137</td>
<td>SIGKILL — OOM или docker kill</td>
<td>Проверить лимиты памяти</td>
</tr>
<tr>
<td>139</td>
<td>Segfault</td>
<td>Баг в приложении</td>
</tr>
<tr>
<td>143</td>
<td>SIGTERM — штатная остановка</td>
<td>Норма при docker stop</td>
</tr>
</tbody>
</table>
<h2>Мониторинг Docker</h2>
<h3>Встроенный мониторинг</h3>
<pre><code class="language-bash">
# Статистика всех контейнеров в реальном времени
docker stats
# Одиночный снимок без обновления
docker stats --no-stream
# Конкретные контейнеры
docker stats web app db --no-stream
# Форматированный вывод
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
</code></pre>
<h3>Cadvisor для метрик</h3>
<pre><code class="language-bash">
# Запустить cAdvisor для мониторинга контейнеров
docker run -d \
--name cadvisor \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \
--restart=unless-stopped \
gcr.io/cadvisor/cadvisor:latest
</code></pre>
<p>После запуска метрики доступны на <code>http://server:8080</code>. Prometheus может их собирать — cAdvisor экспортирует в нужном формате из коробки.</p>
<h2>Альтернативные инструменты</h2>
<p>Docker — не единственный вариант. Знать альтернативы полезно.</p>
<table>
<thead>
<tr>
<th>Инструмент</th>
<th>Когда использовать</th>
<th>Отличие от Docker</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://podman.io" target="_blank" rel="noopener">Podman</a></td>
<td>Rootless контейнеры, RHEL среды</td>
<td>Без daemon, совместимые команды</td>
</tr>
<tr>
<td><a href="https://containerd.io" target="_blank" rel="noopener">containerd</a></td>
<td>Kubernetes, минимальный рантайм</td>
<td>Только рантайм, нет CLI для людей</td>
</tr>
<tr>
<td><a href="https://docs.docker.com/engine/swarm/" target="_blank" rel="noopener">Docker Swarm</a></td>
<td>Кластер из нескольких хостов</td>
<td>Встроен в Docker, проще Kubernetes</td>
</tr>
<tr>
<td><a href="https://kubernetes.io" target="_blank" rel="noopener">Kubernetes</a></td>
<td>Большие кластеры, оркестрация</td>
<td>Гораздо сложнее, гораздо мощнее</td>
</tr>
<tr>
<td><a href="https://www.portainer.io" target="_blank" rel="noopener">Portainer</a></td>
<td>Веб-интерфейс для Docker</td>
<td>GUI поверх Docker API</td>
</tr>
</tbody>
</table>
<h2>FAQ</h2>
<h3>Как посмотреть IP адрес контейнера?</h3>
<pre><code class="language-bash">
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name
</code></pre>
<h3>Как зайти внутрь контейнера без bash?</h3>
<p>Многие минимальные образы не содержат bash. Используй sh:</p>
<pre><code class="language-bash">
docker exec -it container_name sh
# или
docker exec -it container_name /bin/sh
</code></pre>
<h3>Почему docker ps не показывает мой контейнер?</h3>
<p><code>docker ps</code> без флагов показывает только запущенные контейнеры. Если контейнер упал — используй <code>docker ps -a</code>. Потом <code>docker logs container_name</code> чтобы понять почему упал.</p>
<h3>Как передать переменные окружения безопасно?</h3>
<pre><code class="language-bash">
# Через файл .env (не добавлять в git)
docker run --env-file .env myapp
# Через Docker secrets (Swarm)
docker secret create db_password ./password.txt
</code></pre>
<h3>Как посмотреть что слушает контейнер изнутри?</h3>
<pre><code class="language-bash">
docker exec container_name ss -tlnp
# или если ss нет
docker exec container_name netstat -tlnp
</code></pre>
<h3>Как ограничить лог файлы Docker чтобы они не заполняли диск?</h3>
<p>Глобально в <code>/etc/docker/daemon.json</code>:</p>
<pre><code class="language-text">
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
</code></pre>
<p>Или для конкретного контейнера:</p>
<pre><code class="language-bash">
docker run -d \
--log-driver json-file \
--log-opt max-size=50m \
--log-opt max-file=3 \
myapp
</code></pre>
<h3>Как скопировать данные между двумя volumes?</h3>
<pre><code class="language-bash">
docker run --rm \
-v source_volume:/source:ro \
-v target_volume:/target \
alpine cp -r /source/. /target/
</code></pre>
<h3>Как обновить переменную окружения в работающем контейнере?</h3>
<p>Никак. Переменные окружения устанавливаются при создании контейнера. Нужно пересоздать контейнер с новыми значениями. Это ещё одна причина использовать Compose — <code>docker compose up -d</code> пересоздаёт контейнеры с обновлённой конфигурацией.</p>
<h2>Профилактика: как не сломать снова</h2>
<ul>
<li>Всегда фиксируй версии образов (<code>nginx:1.25</code>, а не <code>nginx:latest</code>) на продакшене. <code>latest</code> — это билет в ситуацию «всё работало вчера».</li>
<li>Настрой ротацию логов в daemon.json до того как диск заполнится.</li>
<li>Базы данных — только с volume. Без исключений.</li>
<li>Пароли и секреты — только через env файлы или <a href="https://it-apteka.com/docker-compose-dlja-domashnego-servera-struktura-reverse-proxy-sekrety-i-obnovlenija/" target="_blank" rel="noopener" data-wpil-monitor-id="2794">Docker</a> secrets. Не в Dockerfile, не в compose.yml.</li>
<li>Добавь healthcheck к критичным сервисам — это даст понять когда контейнер запущен но не работает.</li>
<li>Делай бэкап volumes перед обновлением. Особенно баз данных.</li>
<li>Настрой <code>--restart=unless-stopped</code> для всех продакшен контейнеров.</li>
</ul>
<h2>Итог</h2>
<p>Ты закрыл справочник который покрывает 95% реальных сценариев работы с Docker. Образы, контейнеры, сети, тома, Compose, безопасность, очистка, <a class="wpil_keyword_link" href="https://it-apteka.com/category/monitoring/" target="_blank" rel="noopener" title="Мониторинг" data-wpil-keyword-link="linked" data-wpil-monitor-id="2220">мониторинг</a> и troubleshooting — всё на одной странице.</p>
<p>Держи страницу в закладках. Когда что-то снова пойдёт не так — начинай с раздела Troubleshooting. Если там нет ответа — <code>docker logs</code> и <code>docker inspect</code> закроют оставшиеся 5%.</p>
"Не
<br />
Пиши в комментарии: команда, вывод ошибки, версия Docker и ОС. Разберёмся.<br />
Коротко: что здесь есть
Полный справочник команд Docker с примерами из реальных сценариев. Контейнеры, образы, сети, тома, compose, очистка, безопасность и troubleshooting. Копируй команду — выполняй — проверяй результат. Без предисловий на трёх страницах.
Ты поднял контейнер — что-то не работает. Или работает, но непонятно как. Или работало, а потом перестало. Эта шпаргалка закроет 90% вопросов по Docker которые возникают в реальной работе на сервере.
Здесь не будет рассказа о том, что такое контейнеризация и почему Docker это хорошо. Ты это уже знаешь. Здесь команды, примеры, объяснение почему именно так, и что делать когда что-то пошло не по плану.
Что понадобится: сервер или VPS с Ubuntu/Debian/CentOS, root или sudo, установленный Docker. Установку разберём отдельным блоком.
Время на освоение: зависит от задачи. Базовые команды — 20 минут. Compose и сети — ещё час. Полный справочник оставь открытым и возвращайся по мере необходимости.
Содержание
- Архитектура Docker: как это работает
- Установка Docker на Ubuntu и Debian
- Системные команды: daemon, info, версия
- Образы: pull, build, tag, push
- Контейнеры: run, ps, exec, stop, rm
- Логи и диагностика
- Volumes: хранение данных
- Networks: связь между контейнерами
- Docker Compose: запуск стека
- Dockerfile: собрать образ своими руками
- Продакшен: лимиты, рестарт, безопасность
- Очистка и обслуживание
- Troubleshooting
- FAQ
Архитектура Docker: как это работает
Прежде чем копировать команды — одна минута на структуру. Это сэкономит час отладки позже.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
CLI["Docker CLI\ndocker run / ps / exec"] --> |"API запрос"| D["Docker Daemon\n(dockerd)"]
D --> |"создаёт"| C1["Контейнер 1"]
D --> |"создаёт"| C2["Контейнер 2"]
D --> |"управляет"| I["Docker Images\n(образы)"]
D --> |"управляет"| V["Volumes\n(данные)"]
D --> |"управляет"| N["Networks\n(сети)"]
C1 --> V
C2 --> V
C1 --> N
C2 --> N
style CLI fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style C1 fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style C2 fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style I fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#1e293b
style V fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#1e293b
style N fill:#f8fafc,stroke:#94a3b8,stroke-width:2px,color:#1e293b
Docker daemon (dockerd) — это сердце системы. Он получает команды от CLI, управляет образами, контейнерами, сетями и томами. Если daemon не запущен — ничего не работает вообще. Первая точка проверки при любой проблеме.
Образ (image) — шаблон. Неизменяемый. Из одного образа можно запустить сколько угодно контейнеров.
Контейнер (container) — запущенный экземпляр образа. Изолированный процесс с собственной файловой системой. Останови его — данные внутри исчезнут, если не используешь volume.
Volume — постоянное хранилище, живёт отдельно от контейнера. База данных без volume — это не продакшен, это лотерея.
Установка Docker на Ubuntu и Debian
Есть два пути: docker.io из репозитория дистрибутива и официальный репозиторий Docker Inc. Первый — проще. Второй — свежее версия и это важно если используешь Compose v2 или BuildKit.
Вариант 1: из репозитория Ubuntu/Debian (проще)
apt update && apt upgrade -y
apt install -y docker.io
systemctl start docker
systemctl enable docker
Вариант 2: официальный репозиторий Docker (рекомендую для продакшена)
# Убираем старые версии если есть
apt remove docker docker-engine docker.io containerd runc 2>/dev/null
# Зависимости
apt update
apt install -y ca-certificates curl gnupg lsb-release
# GPG ключ
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# Репозиторий
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
# Установка
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Проверяем установку
docker --version
docker info
systemctl status docker
После docker --version должна появиться версия. После docker info — подробная информация о системе, контейнерах, образах, хранилище. Если вылезло Cannot connect to the Docker daemon — daemon не запущен, смотри следующий раздел.
Добавляем пользователя в группу docker
Чтобы не запускать каждую команду через sudo:
usermod -aG docker $USER
newgrp docker
Перезайди в сессию
После добавления в группу нужно выйти и зайти заново или выполнить newgrp docker. Иначе права не применятся и docker команды будут давать permission denied.
Таблица: совместимость версий
| ОС |
Версия |
Docker CE |
Compose Plugin |
| Ubuntu |
22.04 LTS |
24.x, 25.x, 26.x |
v2.x |
| Ubuntu |
24.04 LTS |
26.x, 27.x |
v2.x |
| Debian |
11 (Bullseye) |
24.x, 25.x |
v2.x |
| Debian |
12 (Bookworm) |
26.x, 27.x |
v2.x |
| CentOS/RHEL |
8, 9 |
25.x, 26.x |
v2.x |
На момент публикации актуальна версия Docker 27.x. Перед установкой проверь свежие релизы на docs.docker.com/engine/release-notes.
Системные команды: daemon, info, версия
Управление daemon через systemctl
# Статус
systemctl status docker
# Запуск
systemctl start docker
# Перезапуск
systemctl restart docker
# Остановка
systemctl stop docker
# Автозапуск при старте системы
systemctl enable docker
# Отключить автозапуск
systemctl disable docker
Информация о системе
# Версия клиента и сервера
docker version
# Подробная инфо: контейнеры, образы, хранилище, драйвер
docker info
# Использование диска Docker
docker system df
# Подробно с размерами
docker system df -v
Команда docker system df покажет сколько места занимают образы, контейнеры, volumes и build cache. На серверах которые давно не чистили цифра бывает неприятно большой.
Логи docker daemon
# Логи daemon в journald
journalctl -u docker
# Последние 100 строк
journalctl -u docker -n 100
# В реальном времени
journalctl -u docker -f
Образы: pull, build, tag, push
Работа с Docker Hub
# Скачать образ последней версии
docker pull nginx
# Конкретная версия (тег)
docker pull nginx:1.25
# Конкретная версия под архитектуру
docker pull --platform linux/amd64 nginx:1.25
# Список скачанных образов
docker images
# Подробный список с датами
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
# Удалить образ
docker rmi nginx:1.25
# Удалить образ принудительно (если используется контейнером)
docker rmi -f nginx:1.25
Поиск образов
# Поиск на Docker Hub
docker search nginx
# Только официальные образы
docker search --filter is-official=true nginx
Авторизация в реестре
# Docker Hub
docker login
# Приватный реестр
docker login registry.example.com
# Выйти
docker logout
Tag и push
# Пометить образ своим тегом
docker tag nginx:latest myregistry.com/myapp:1.0
# Отправить в реестр
docker push myregistry.com/myapp:1.0
Инспект образа
# Метаданные образа
docker inspect nginx
# Только конкретное поле (например, переменные окружения)
docker inspect --format='{{.Config.Env}}' nginx
# История слоёв образа
docker history nginx
Контейнеры: run, ps, exec, stop, rm
Это основа. Здесь половина всех команд которые ты будешь использовать каждый день.
docker run: запуск контейнера
# Простой запуск (в foreground, Ctrl+C останавливает)
docker run nginx
# Фоновый режим (detached)
docker run -d nginx
# С именем
docker run -d --name web nginx
# Проброс порта: порт_хоста:порт_контейнера
docker run -d -p 8080:80 --name web nginx
# Переменные окружения
docker run -d -e MY_VAR=value --name web nginx
# Несколько переменных
docker run -d -e DB_HOST=localhost -e DB_PORT=5432 --name app myapp
# Монтирование директории хоста
docker run -d -v /host/path:/container/path nginx
# Монтирование volume
docker run -d -v myvolume:/data nginx
# Автоматически удалять контейнер после остановки
docker run --rm nginx
# Интерактивный режим (зайти внутрь сразу)
docker run -it ubuntu bash
# Ограничение памяти
docker run -d --memory="512m" nginx
# Ограничение CPU
docker run -d --cpus="1.5" nginx
# Политика рестарта
docker run -d --restart=always nginx
# Подключить к сети
docker run -d --network mynetwork nginx
Флаги -d и -it не совместимы как основной режим: -d запускает в фоне, -it открывает терминал. Для продакшена почти всегда -d. Для отладки — -it --rm.
Таблица: политики рестарта
| Политика |
Поведение |
Когда использовать |
| no |
Не перезапускать (по умолчанию) |
Разработка, тестовые запуски |
| always |
Всегда перезапускать |
Продакшен сервисы |
| unless-stopped |
Перезапускать, кроме ручной остановки |
Продакшен, гибкий контроль |
| on-failure |
Перезапускать только при ненулевом exit code |
Задачи с обработкой ошибок |
| on-failure:3 |
Не более 3 попыток перезапуска |
Предотвращение restart loop |
Управление запущенными контейнерами
# Список запущенных контейнеров
docker ps
# Все контейнеры включая остановленные
docker ps -a
# Только ID контейнеров
docker ps -q
# Форматированный вывод
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Остановить контейнер (SIGTERM, ждёт 10 секунд)
docker stop web
# Остановить немедленно (SIGKILL)
docker kill web
# Запустить остановленный
docker start web
# Перезапустить
docker restart web
# Пауза / снятие паузы
docker pause web
docker unpause web
# Переименовать
docker rename web web-old
# Удалить остановленный контейнер
docker rm web
# Удалить принудительно (работающий)
docker rm -f web
# Удалить все остановленные контейнеры
docker container prune
# Инспект контейнера (полная информация)
docker inspect web
# Статистика ресурсов в реальном времени
docker stats
# Статистика одного контейнера без обновления
docker stats web --no-stream
# Процессы внутри контейнера
docker top web
docker exec: работа внутри контейнера
# Зайти в bash
docker exec -it web bash
# Если bash не установлен - sh
docker exec -it web sh
# Выполнить одну команду
docker exec web nginx -t
# Выполнить команду от имени конкретного пользователя
docker exec -u www-data web whoami
# Открыть ещё один терминал в уже подключённой сессии
docker exec -it web bash
Если контейнер отвечает на docker exec -it web bash ошибкой OCI runtime exec failed — контейнер либо не запущен, либо bash в нём нет. Проверь через docker ps что он вообще работает.
Копирование файлов
# Скопировать файл из контейнера на хост
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
# Скопировать файл с хоста в контейнер
docker cp ./nginx.conf web:/etc/nginx/nginx.conf
# Скопировать директорию
docker cp web:/var/log/nginx/ ./logs/
Логи и диагностика
Команды для работы с логами
# Показать все логи контейнера
docker logs web
# Последние N строк
docker logs web --tail 100
# Следить в реальном времени (как tail -f)
docker logs -f web
# С временными метками
docker logs -t web
# Логи за последний час
docker logs --since 1h web
# Логи с определённого времени
docker logs --since "2024-01-15T10:00:00" web
# Логи между двумя временными точками
docker logs --since 2h --until 1h web
Диагностика без захода внутрь
# Полный инспект контейнера в JSON
docker inspect web
# IP адрес контейнера
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
# Подключённые порты
docker inspect -f '{{.NetworkSettings.Ports}}' web
# Переменные окружения
docker inspect -f '{{.Config.Env}}' web
# Смонтированные тома
docker inspect -f '{{.Mounts}}' web
# События в реальном времени (старт/стоп контейнеров и т.п.)
docker events
# События за последние 30 минут
docker events --since 30m
Volumes: хранение данных
Всё что записывается внутрь контейнера — исчезает при его удалении. Volumes решают эту проблему. Для баз данных, конфигов и любых данных которые должны пережить перезапуск контейнера — только volumes.
Управление volumes
# Создать volume
docker volume create pgdata
# Список volumes
docker volume ls
# Инспект volume (где хранится, кто использует)
docker volume inspect pgdata
# Удалить volume
docker volume rm pgdata
# Удалить все неиспользуемые volumes
docker volume prune
# Удалить volume принудительно
docker volume rm -f pgdata
Типы монтирования
# Volume (рекомендуется для данных)
docker run -d -v pgdata:/var/lib/postgresql/data postgres
# Bind mount (конкретная папка хоста)
docker run -d -v /home/user/data:/app/data myapp
# Tmpfs mount (только в RAM, не сохраняется)
docker run -d --tmpfs /tmp myapp
# Только чтение
docker run -d -v /host/config:/app/config:ro myapp
Где физически лежат volumes
# Путь на хосте
docker volume inspect pgdata --format '{{.Mountpoint}}'
# Обычно: /var/lib/docker/volumes/pgdata/_data
Backup и восстановление volume
# Создать бэкап volume в tar.gz
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
ubuntu tar czf /backup/pgdata-backup.tar.gz -C /data .
# Восстановить из бэкапа
docker run --rm \
-v pgdata:/data \
-v $(pwd):/backup \
ubuntu bash -c "cd /data && tar xzf /backup/pgdata-backup.tar.gz"
Команда создаёт временный контейнер ubuntu, монтирует в него volume и директорию хоста, упаковывает данные в архив. Контейнер удаляется сам после завершения — за это отвечает --rm.
Бэкап баз данных
Для PostgreSQL и
MySQL не используй прямой backup volume файлов пока база запущена. Используй pg_dump или mysqldump. Бэкап файлов на работающей БД может дать повреждённые данные. Это не параноя, это стандарт.
Networks: связь между контейнерами
По умолчанию контейнеры могут общаться только внутри одной сети. Правильно организованные сети — это ещё и изоляция: database не должна быть доступна снаружи, только из app-контейнера.
Управление сетями
# Список сетей
docker network ls
# Создать bridge сеть
docker network create backend
# Создать с настройками подсети
docker network create --subnet=172.20.0.0/24 backend
# Инспект сети
docker network inspect backend
# Удалить сеть
docker network rm backend
# Удалить все неиспользуемые сети
docker network prune
Типы сетей
| Тип |
Описание |
Когда использовать |
| bridge |
Изолированная сеть на хосте (по умолчанию) |
Большинство сценариев |
| host |
Контейнер использует сеть хоста напрямую |
Высокая производительность, осторожно с безопасностью |
| none |
Нет сети |
Полная изоляция |
| overlay |
Сеть между несколькими хостами |
Docker Swarm, multi-host |
| macvlan |
Контейнер получает MAC-адрес в физической сети |
Legacy системы, требующие Layer 2 |
Подключение контейнеров к сети
# Запустить контейнер сразу в нужной сети
docker run -d --name db --network backend postgres
# Подключить работающий контейнер к сети
docker network connect backend web
# Отключить контейнер от сети
docker network disconnect backend web
# Контейнер в нескольких сетях
docker network connect frontend web
Связь между контейнерами
Контейнеры в одной user-defined сети могут обращаться друг к другу по имени. Это автоматический DNS внутри Docker.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart LR
U["Пользователь\n:80"] --> |"порт 80"| N["Nginx\ncontainer"]
N --> |"backend сеть\napp:8000"| A["App\ncontainer"]
A --> |"backend сеть\ndb:5432"| D["PostgreSQL\ncontainer"]
style U fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style N fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style A fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
# Пример: app обращается к db по имени "db"
docker run -d --name db --network backend \
-e POSTGRES_PASSWORD=secret postgres
docker run -d --name app --network backend \
-e DB_HOST=db \
-e DB_PORT=5432 \
myapp
Обрати внимание: DB_HOST=db — это именно имя контейнера. Docker внутри сети разрешает имена контейнеров в IP адреса автоматически. Никаких hosts файлов, никаких ручных IP.
Dockerfile: собрать образ
Dockerfile — текстовый файл с инструкциями для сборки образа. Каждая инструкция создаёт новый слой. Слои кэшируются — поэтому порядок инструкций влияет на скорость повторных сборок.
Базовая структура Dockerfile
# Базовый образ
FROM ubuntu:22.04
# Метаданные
LABEL maintainer="admin@example.com"
# Переменные сборки
ARG APP_VERSION=1.0
# Переменные окружения
ENV APP_HOME=/app
# Рабочая директория
WORKDIR /app
# Копировать файлы
COPY . .
# Установка зависимостей
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Установить зависимости Python
RUN pip3 install -r requirements.txt
# Открыть порт (документация, не реальный проброс)
EXPOSE 8000
# Пользователь для запуска (не root!)
USER nobody
# Команда запуска
CMD ["python3", "app.py"]
Сборка образа
# Собрать с тегом
docker build -t myapp:1.0 .
# Собрать из конкретного Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
# Без кэша
docker build --no-cache -t myapp:1.0 .
# С аргументами сборки
docker build --build-arg APP_VERSION=2.0 -t myapp:2.0 .
# Посмотреть слои при сборке
docker build --progress=plain -t myapp:1.0 .
Многоступенчатая сборка (multi-stage build)
Уменьшает итоговый размер образа: в первом этапе компилируем, во второй копируем только бинарник.
# Этап 1: сборка
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# Этап 2: финальный образ
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]
Итоговый образ содержит только alpine и бинарник. Никаких компиляторов, исходников и зависимостей сборки. Размер падает с гигабайт до десятков мегабайт. Это не магия, это архитектура.
Docker Compose: запуск стека
Compose позволяет описать несколько сервисов в одном файле и запустить их одной командой. Версия 2 (compose plugin) встроена в Docker и вызывается через docker compose без дефиса.
Базовый docker-compose.yml
version: "3.8"
services:
web:
image: nginx:1.25
container_name: web
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./html:/usr/share/nginx/html:ro
networks:
- frontend
depends_on:
- app
app:
build:
context: .
dockerfile: Dockerfile
container_name: app
restart: unless-stopped
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_NAME=mydb
- DB_USER=appuser
- DB_PASSWORD=${DB_PASSWORD}
networks:
- frontend
- backend
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
container_name: db
restart: unless-stopped
environment:
- POSTGRES_DB=mydb
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
networks:
frontend:
backend:
volumes:
pgdata:
Пароли в .env файле
Переменная DB_PASSWORD берётся из файла .env в той же директории. Создай файл .env с содержимым DB_PASSWORD=yourpassword и добавь его в .gitignore. Пароли в docker-compose.yml это не практика, это катастрофа которая ждёт своего момента.
Команды Docker Compose
# Запустить стек в фоне
docker compose up -d
# Запустить с пересборкой образов
docker compose up -d --build
# Остановить стек (контейнеры останавливаются, не удаляются)
docker compose stop
# Остановить и удалить контейнеры
docker compose down
# Остановить, удалить контейнеры и volumes
docker compose down -v
# Остановить, удалить и удалить образы
docker compose down --rmi all
# Статус сервисов
docker compose ps
# Логи всех сервисов
docker compose logs
# Логи конкретного сервиса в реальном времени
docker compose logs -f app
# Зайти в контейнер сервиса
docker compose exec app bash
# Выполнить команду в сервисе
docker compose exec db psql -U appuser mydb
# Перезапустить один сервис без остановки других
docker compose restart app
# Масштабировать сервис
docker compose up -d --scale app=3
# Посмотреть переменные окружения
docker compose config
Healthcheck в Compose
Конструкция depends_on: condition: service_healthy дождётся пока база не пройдёт healthcheck перед запуском app. Без этого app стартует раньше базы и получает ошибку подключения.
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d mydb"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
Docker для продакшена: настройки которые нельзя пропускать
Ограничения ресурсов
# Ограничение памяти и CPU через run
docker run -d \
--name app \
--memory="512m" \
--memory-swap="512m" \
--cpus="2.0" \
--restart=unless-stopped \
myapp
# Обновить лимиты работающего контейнера
docker update --memory="1g" --cpus="2.0" app
--memory-swap равный --memory означает что swap не используется. Если не задать — контейнер сможет использовать swap в два раза больше лимита памяти. На продакшене это нежелательно.
Настройка docker daemon для продакшена
Файл конфигурации: /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
},
"storage-driver": "overlay2",
"live-restore": true,
"userland-proxy": false,
"no-new-privileges": true
}
Что здесь важно:
log-driver: json-file + ограничения — без этого логи растут бесконечно и заполняют диск. Проверенный способ убить сервер в 3 ночи.
live-restore: true — контейнеры продолжают работать при перезапуске daemon. Для обновления Docker без даунтайма.
no-new-privileges: true — процессы внутри контейнера не могут получить новые привилегии через setuid.
# Применить изменения daemon.json
systemctl restart docker
Безопасность: минимальный набор
# Никогда не запускать как root если можно без этого
docker run -d --user 1000:1000 myapp
# Read-only файловая система
docker run -d --read-only myapp
# Запретить запись в tmp (если приложение не требует)
docker run -d --read-only --tmpfs /tmp myapp
# Без дополнительных capabilities
docker run -d --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
# Запрет эскалации привилегий
docker run -d --security-opt no-new-privileges myapp
Firewall: порты Docker и UFW
Важно: Docker обходит UFW
Docker по умолчанию напрямую модифицирует iptables и обходит правила UFW. Если ты открыл порт через docker run -p, он будет доступен всем даже если UFW его закрывает. Чтобы этого не было — используй 127.0.0.1 для привязки портов которые не должны быть публичными.
# Привязать порт только к localhost (не публичный)
docker run -d -p 127.0.0.1:5432:5432 postgres
# Публичный порт (доступен всем)
docker run -d -p 0.0.0.0:80:80 nginx
# Или просто
docker run -d -p 80:80 nginx
Таблица портов: стандартные сервисы в Docker
| Сервис |
Порт контейнера |
Рекомендуемая привязка |
Примечание |
| Nginx/Apache |
80, 443 |
0.0.0.0:80:80 |
Публичный |
| PostgreSQL |
5432 |
127.0.0.1:5432:5432 |
Только localhost |
| MySQL/MariaDB |
3306 |
127.0.0.1:3306:3306 |
Только localhost |
| Redis |
6379 |
127.0.0.1:6379:6379 |
Только localhost |
| MongoDB |
27017 |
127.0.0.1:27017:27017 |
Только localhost |
| RabbitMQ |
5672, 15672 |
127.0.0.1:15672:15672 |
Management только localhost |
Очистка и обслуживание
Docker накапливает мусор: остановленные контейнеры, неиспользуемые образы, осиротевшие volumes и build cache. На серверах которые работают больше года без чистки это запросто 50+ гигабайт.
# Посмотреть что занимает место
docker system df
docker system df -v
# Удалить всё неиспользуемое (контейнеры, сети, образы без тегов, build cache)
docker system prune
# То же самое но и volumes
docker system prune --volumes
# Удалить вообще всё неиспользуемое включая образы с тегами
docker system prune -a
# Только остановленные контейнеры
docker container prune
# Только образы без тегов (dangling)
docker image prune
# Все образы без запущенных контейнеров
docker image prune -a
# Только volumes без контейнеров
docker volume prune
# Только неиспользуемые сети
docker network prune
# Только build cache
docker builder prune
Автоматическая очистка через cron
# Добавить в crontab (каждое воскресенье в 3 ночи)
crontab -e
0 3 * * 0 /usr/bin/docker system prune -f >> /var/log/docker-prune.log 2>&1
Флаг -f убирает запрос подтверждения. Без него cron задача просто зависнет.
Обновление Docker и контейнеров
Обновление самого Docker
# Проверить текущую версию
docker --version
# Обновить (если ставили из официального репозитория)
apt update
apt upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Проверить что daemon работает после обновления
systemctl status docker
docker ps
Если включён live-restore: true в daemon.json — контейнеры продолжат работать во время перезапуска daemon при обновлении. Без этого флага все контейнеры остановятся.
Обновление контейнеров (вручную)
# Скачать новый образ
docker pull nginx:latest
# Остановить старый контейнер
docker stop web
# Удалить старый контейнер (данные в volumes сохранятся)
docker rm web
# Запустить с новым образом
docker run -d --name web -p 80:80 --restart=unless-stopped nginx:latest
# Удалить старый образ
docker image prune
Обновление стека через Compose
# Скачать новые образы
docker compose pull
# Пересобрать и перезапустить
docker compose up -d --build
# Только пересоздать контейнеры с новыми образами без сборки
docker compose up -d --pull always
Откат к предыдущей версии
# Посмотреть доступные теги через Hub
docker search nginx
# Запустить конкретную версию
docker run -d --name web -p 80:80 nginx:1.24
# В compose - изменить тег в файле и перезапустить
# image: nginx:1.24
docker compose up -d
Troubleshooting
Cannot connect to the Docker daemon
Daemon не запущен или нет прав.
# Проверить статус
systemctl status docker
# Запустить
systemctl start docker
# Проверить права текущего пользователя
groups $USER
# Добавить в группу docker
usermod -aG docker $USER
newgrp docker
Port is already allocated / address already in use
Порт занят другим процессом или контейнером.
# Найти что занимает порт
ss -tlnp | grep :80
lsof -i :80
# Найти контейнер который использует порт
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep 80
# Остановить конкурирующий контейнер
docker stop old_container
No space left on device
Закончилось место на диске.
# Проверить диск
df -h
du -sh /var/lib/docker/*
# Очистить Docker
docker system prune -f
# Если не помогает - смотри логи контейнеров (они могут занимать много места)
find /var/lib/docker/containers -name "*.log" -exec du -sh {} \; | sort -rh | head
Container exits immediately (exit code 1 или 137)
# Посмотреть логи остановленного контейнера
docker logs mycontainer
# Запустить интерактивно для диагностики
docker run -it --entrypoint bash myimage
# Exit code 137 - контейнер убит по OOM (нехватка памяти)
# Проверить системные сообщения
journalctl -k | grep -i "oom"
dmesg | grep -i "oom"
Контейнер не может подключиться к другому контейнеру
# Проверить что оба в одной сети
docker inspect container1 | grep -A 20 Networks
docker inspect container2 | grep -A 20 Networks
# Пинг по имени изнутри контейнера
docker exec container1 ping container2
# Проверить DNS
docker exec container1 nslookup container2
# Создать общую сеть и подключить оба
docker network create shared
docker network connect shared container1
docker network connect shared container2
Образ не собирается / COPY failed / файл не найден
# Проверить .dockerignore - возможно файл исключён
cat .dockerignore
# Собрать с verbose выводом
docker build --progress=plain -t myapp . 2>&1 | tee build.log
# Проверить context сборки (что отправляется в daemon)
docker build --no-cache -t myapp . 2>&1 | head -5
Volume не монтируется / Permission denied
# Проверить владельца директории на хосте
ls -la /host/path
# Проверить UID внутри контейнера
docker run --rm myimage id
# Исправить права
chown -R 1000:1000 /host/path
# Или запустить контейнер с нужным пользователем
docker run -d --user $(id -u):$(id -g) -v /host/path:/app/data myapp
docker compose up зависает на «Waiting for container…»
# Посмотреть статус healthcheck
docker inspect db | grep -A 10 Health
# Проверить логи сервиса
docker compose logs db
# Запустить без depends_on для диагностики
docker compose up db
Таблица: коды выхода контейнеров
| Exit Code |
Причина |
Что делать |
| 0 |
Штатное завершение |
Норма для одноразовых задач |
| 1 |
Ошибка приложения |
Смотреть docker logs |
| 125 |
Ошибка docker run |
Проверить параметры команды |
| 126 |
Команда найдена, но не исполняема |
Проверить права на файл |
| 127 |
Команда не найдена |
Проверить entrypoint/cmd |
| 137 |
SIGKILL — OOM или docker kill |
Проверить лимиты памяти |
| 139 |
Segfault |
Баг в приложении |
| 143 |
SIGTERM — штатная остановка |
Норма при docker stop |
Мониторинг Docker
Встроенный мониторинг
# Статистика всех контейнеров в реальном времени
docker stats
# Одиночный снимок без обновления
docker stats --no-stream
# Конкретные контейнеры
docker stats web app db --no-stream
# Форматированный вывод
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
Cadvisor для метрик
# Запустить cAdvisor для мониторинга контейнеров
docker run -d \
--name cadvisor \
--volume=/:/rootfs:ro \
--volume=/var/run:/var/run:ro \
--volume=/sys:/sys:ro \
--volume=/var/lib/docker/:/var/lib/docker:ro \
--publish=8080:8080 \
--restart=unless-stopped \
gcr.io/cadvisor/cadvisor:latest
После запуска метрики доступны на http://server:8080. Prometheus может их собирать — cAdvisor экспортирует в нужном формате из коробки.
Альтернативные инструменты
Docker — не единственный вариант. Знать альтернативы полезно.
| Инструмент |
Когда использовать |
Отличие от Docker |
| Podman |
Rootless контейнеры, RHEL среды |
Без daemon, совместимые команды |
| containerd |
Kubernetes, минимальный рантайм |
Только рантайм, нет CLI для людей |
| Docker Swarm |
Кластер из нескольких хостов |
Встроен в Docker, проще Kubernetes |
| Kubernetes |
Большие кластеры, оркестрация |
Гораздо сложнее, гораздо мощнее |
| Portainer |
Веб-интерфейс для Docker |
GUI поверх Docker API |
FAQ
Как посмотреть IP адрес контейнера?
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container_name
Как зайти внутрь контейнера без bash?
Многие минимальные образы не содержат bash. Используй sh:
docker exec -it container_name sh
# или
docker exec -it container_name /bin/sh
Почему docker ps не показывает мой контейнер?
docker ps без флагов показывает только запущенные контейнеры. Если контейнер упал — используй docker ps -a. Потом docker logs container_name чтобы понять почему упал.
Как передать переменные окружения безопасно?
# Через файл .env (не добавлять в git)
docker run --env-file .env myapp
# Через Docker secrets (Swarm)
docker secret create db_password ./password.txt
Как посмотреть что слушает контейнер изнутри?
docker exec container_name ss -tlnp
# или если ss нет
docker exec container_name netstat -tlnp
Как ограничить лог файлы Docker чтобы они не заполняли диск?
Глобально в /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
Или для конкретного контейнера:
docker run -d \
--log-driver json-file \
--log-opt max-size=50m \
--log-opt max-file=3 \
myapp
Как скопировать данные между двумя volumes?
docker run --rm \
-v source_volume:/source:ro \
-v target_volume:/target \
alpine cp -r /source/. /target/
Как обновить переменную окружения в работающем контейнере?
Никак. Переменные окружения устанавливаются при создании контейнера. Нужно пересоздать контейнер с новыми значениями. Это ещё одна причина использовать Compose — docker compose up -d пересоздаёт контейнеры с обновлённой конфигурацией.
Профилактика: как не сломать снова
- Всегда фиксируй версии образов (
nginx:1.25, а не nginx:latest) на продакшене. latest — это билет в ситуацию «всё работало вчера».
- Настрой ротацию логов в daemon.json до того как диск заполнится.
- Базы данных — только с volume. Без исключений.
- Пароли и секреты — только через env файлы или Docker secrets. Не в Dockerfile, не в compose.yml.
- Добавь healthcheck к критичным сервисам — это даст понять когда контейнер запущен но не работает.
- Делай бэкап volumes перед обновлением. Особенно баз данных.
- Настрой
--restart=unless-stopped для всех продакшен контейнеров.
Итог
Ты закрыл справочник который покрывает 95% реальных сценариев работы с Docker. Образы, контейнеры, сети, тома, Compose, безопасность, очистка, мониторинг и troubleshooting — всё на одной странице.
Держи страницу в закладках. Когда что-то снова пойдёт не так — начинай с раздела Troubleshooting. Если там нет ответа — docker logs и docker inspect закроют оставшиеся 5%.
Не заработало?
Пиши в комментарии: команда, вывод ошибки, версия Docker и ОС. Разберёмся.