Docker для сисадмина — контейнеры без боли
Помню своё первое знакомство с Docker. Открыл документацию, увидел слова «слои образов», «оркестрация», «volumes», «сети между контейнерами» — и закрыл вкладку. Показалось что это какая-то космическая технология для больших компаний с DevOps-отделом.
Прошло несколько месяцев, я всё-таки заставил себя разобраться — и теперь не представляю жизнь без Docker. Оказалось что 80% того что нужно сисадмину в реальной работе — это очень простые вещи. Никакой космонавтики.
В этой статье я расскажу именно про эти 80%. Без теории ради теории, без академических определений. Только то, что реально используется каждый день.
Зачем вообще Docker — если и так работает
Хороший вопрос. Раньше я тоже так думал: «у меня nginx стоит, PHP работает, MySQL крутится — зачем мне какие-то контейнеры?»
Вот реальные сценарии где Docker спасает:
«На моей машине работает» — классика жанра. Разработчик сделал приложение, на его ноуте всё ок, на сервере падает. Версии Node.js разные, библиотеки разные, переменные окружения разные. С Docker приложение везде запускается одинаково — потому что тащит своё окружение с собой.
Несколько проектов на одном сервере. Один проект требует PHP 7.4, другой — PHP 8.2. Один хочет MySQL 5.7, другой — PostgreSQL 15. Без Docker это головная боль совместимости. С Docker — каждый проект живёт в своей изолированной среде и не знает о других.
Быстрое разворачивание сервисов. Нужен Redis? Одна команда. Нужен PostgreSQL для тестов? Одна команда. Не нужно ставить, настраивать, потом чистить — запустил контейнер, поработал, удалил.
Откат и обновление без страха. Обновляешь приложение — старый образ никуда не делся. Что-то пошло не так — откатился за секунды.
Воспроизводимость. Конфигурация описана в файле. Хочешь поднять такое же окружение на другом сервере — копируешь файл и запускаешь. Никакого «вспоминать что я там настраивал полгода назад».
Основные понятия — быстро и по-человечески
Прежде чем лезть в команды, нужно понять три слова. Только три — остальное придёт само.
Образ (Image) — это шаблон. Как ISO-файл операционной системы. Неизменяемый, хранится в репозитории. Из одного образа можно запустить хоть сто контейнеров.
Контейнер (Container) — это запущенный экземпляр образа. Живой процесс со своей файловой системой, сетью, окружением. Можно запустить, остановить, удалить.
Docker Hub — публичный репозиторий образов. Там лежат готовые образы nginx, postgres, redis, node — тысячи всего. Не нужно собирать с нуля — берёшь готовый и используешь.
Аналогия: образ — это рецепт блюда, контейнер — это само приготовленное блюдо. Из одного рецепта можно приготовить сколько угодно порций.
Установка — одной командой
На Ubuntu/Debian:
bash
curl -fsSL https://get.docker.com | sh
Официальный скрипт от Docker. Ставит всё необходимое автоматически.
После установки добавляем своего пользователя в группу docker — чтобы не писать sudo перед каждой командой:
bash
sudo usermod -aG docker $USER
Перелогиниться или выполнить:
bash
newgrp docker
Проверяем что всё работает:
bash
docker --version
docker run hello-world
Если увидел «Hello from Docker!» — всё готово.
Команды которые использую каждый день
Вот мой реальный рабочий набор. Не полный список из документации, а именно то что в мышечной памяти.
Запустить контейнер
bash
docker run nginx
Скачает образ nginx из Docker Hub и запустит контейнер. Просто чтобы попробовать.
Но в реальной работе почти всегда нужны флаги:
bash
docker run -d -p 8080:80 --name my-nginx nginx
Разбираем:
- -d — detached mode, запустить в фоне (без него контейнер занимает терминал)
- -p 8080:80 — пробросить порт: порт_хоста:порт_контейнера. Теперь nginx доступен на порту 8080 твоей машины
- --name my-nginx — дать контейнеру имя. Без имени Docker придумает что-то вроде «silly_hopper» и работать с этим неудобно
Посмотреть запущенные контейнеры
bash
docker ps
Показывает: ID, образ, команду, время запуска, статус, порты, имя.
Все контейнеры включая остановленные:
bash
docker ps -a
Остановить и запустить
bash
docker stop my-nginx
docker start my-nginx
docker restart my-nginx
Просто и понятно.
Зайти внутрь контейнера
bash
docker exec -it my-nginx bash
Открывает интерактивный bash внутри контейнера. Можно ходить по файловой системе, смотреть конфиги, дебажить.
Флаги -it — interactive + tty. Без них команда выполнится и закроется, с ними — получаешь интерактивную сессию.
Если bash нет (в минималистичных образах бывает):
bash
docker exec -it my-nginx sh
Посмотреть логи
bash
docker logs my-nginx
Логи в реальном времени:
bash
docker logs -f my-nginx
Флаг -f — follow, как tail -f. Это я использую постоянно при отладке.
Последние 100 строк:
bash
docker logs --tail 100 my-nginx
Удалить контейнер
bash
docker rm my-nginx
Работающий контейнер сначала нужно остановить. Или принудительно:
bash
docker rm -f my-nginx
Образы — посмотреть и удалить
bash
docker images # список образов
docker rmi nginx # удалить образ
docker image prune # удалить все неиспользуемые образы
Генеральная уборка
Со временем накапливаются остановленные контейнеры, ненужные образы, тома. Одна команда чтобы всё почистить:
bash
docker system prune -a
Осторожно — удалит всё неиспользуемое. Спросит подтверждение.
Volumes — как хранить данные правильно
Вот важная вещь которую нужно понять сразу. Контейнер — эфемерный. Удалил контейнер — удалились все данные внутри него.
Если у тебя там база данных — это проблема.
Решение — volumes. Это способ примонтировать папку с хост-машины внутрь контейнера.
bash
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=mypassword \
-v /var/data/postgres:/var/lib/postgresql/data \
postgres:15
Флаг -v /var/data/postgres:/var/lib/postgresql/data — монтирует папку /var/data/postgres с твоего сервера в /var/lib/postgresql/data внутри контейнера. Данные базы пишутся на хост, а не в контейнер.
Теперь можно удалить и пересоздать контейнер — данные останутся.
Ещё один вариант — именованные тома (Docker управляет ими сам):
bash
docker volume create postgres-data
docker run -d \
--name postgres \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
Посмотреть тома:
bash
docker volume ls
Docker Compose — когда сервисов несколько
Вот тут начинается настоящее удобство. Представь: тебе нужно запустить веб-приложение на Node.js, к нему PostgreSQL, к нему Redis для кэша. Три контейнера, между ними сеть, volumes, переменные окружения.
Руками это три длинные команды docker run которые нужно помнить и выполнять в правильном порядке.
Docker Compose решает это элегантно — описываешь всё в одном файле docker-compose.yml и запускаешь одной командой.
Вот реальный пример — Next.js приложение с PostgreSQL:
yaml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:password@db:5432/myapp
depends_on:
- db
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres-data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres-data:
Запускаем:
bash
docker compose up -d
Всё. Оба контейнера запущены, между ними настроена сеть, приложение видит базу по имени db — Docker сам разрешает имена сервисов в IP-адреса внутри своей сети.
Остановить:
bash
docker compose down
Остановить и удалить volumes (осторожно — удалит данные):
bash
docker compose down -v
Посмотреть логи всех сервисов:
bash
docker compose logs -f
Конкретного сервиса:
bash
docker compose logs -f app
Пересобрать образ и перезапустить:
bash
docker compose up -d --build
Это я использую при деплое — пересобрать образ приложения с новым кодом и перезапустить.
Dockerfile — собираем свой образ
Готовые образы с Docker Hub — это хорошо. Но для своего приложения нужен свой образ.
Dockerfile — это инструкция как собрать образ. Вот простой пример для Node.js приложения:
dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Разбираем по строкам:
FROM node:20-alpine — берём за основу официальный образ Node.js 20 на Alpine Linux. Alpine — минималистичный дистрибутив, образ весит меньше.
WORKDIR /app — рабочая директория внутри контейнера. Все последующие команды выполняются здесь.
COPY package*.json ./ — копируем только package.json сначала. Зачем? Следующий шаг — RUN npm ci — будет закэширован Docker'ом пока package.json не изменится. Это сильно ускоряет повторные сборки.
RUN npm ci --only=production — устанавливаем зависимости.
COPY . . — копируем остальной код.
RUN npm run build — собираем приложение.
EXPOSE 3000 — документируем что контейнер слушает порт 3000. Не открывает порт наружу — это делает -p при запуске.
CMD ["npm", "start"] — команда которая выполняется при запуске контейнера.
Собрать образ:
bash
docker build -t my-app:latest .
```
`-t my-app:latest` — имя и тег образа. Точка в конце — контекст сборки (текущая директория).
---
## .dockerignore — не копируем лишнее
Рядом с Dockerfile создаём файл `.dockerignore`. Он работает как `.gitignore` — исключает файлы и папки из контекста сборки.
```
node_modules
.git
.env
*.log
dist
.next
```
Без этого файла `COPY . .` скопирует `node_modules` со всеми зависимостями внутрь образа — это сотни мегабайт мусора. С `.dockerignore` — только нужный код.
---
## Переменные окружения — правильно и безопасно
Никогда не хардкодь секреты в Dockerfile или docker-compose.yml. Пароли, API-ключи, токены — всё это в переменные окружения.
Создаём файл `.env` рядом с `docker-compose.yml`:
```
POSTGRES_PASSWORD=supersecretpassword
DATABASE_URL=postgresql://postgres:supersecretpassword@db:5432/myapp
JWT_SECRET=anothersecret
В docker-compose.yml используем:
yaml
services:
app:
env_file:
- .env
db:
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
Файл .env добавляем в .gitignore — он не попадает в репозиторий. На сервере создаём его вручную или через CI/CD.
Типичные сценарии из жизни
Быстро поднять PostgreSQL для разработки:
bash
docker run -d \
--name dev-postgres \
-e POSTGRES_PASSWORD=devpassword \
-e POSTGRES_DB=mydb \
-p 5432:5432 \
postgres:15
Подключаешься любым клиентом на localhost:5432. Закончил — docker rm -f dev-postgres.
Redis для кэша:
bash
docker run -d \
--name redis \
-p 6379:6379 \
redis:alpine
Запустить команду в одноразовом контейнере:
bash
docker run --rm node:20 node -e "console.log('hello')"
Флаг --rm — удалить контейнер после завершения. Удобно для одноразовых задач.
Посмотреть сколько ресурсов потребляют контейнеры:
bash
docker stats
```
Живой мониторинг CPU, памяти, сети по каждому контейнеру. Незаменимо когда что-то жрёт ресурсы и непонятно кто.
---
## Частые проблемы и как их решать
**Порт уже занят:**
```
Error: port is already allocated
Кто-то уже слушает этот порт. Найти кто:
bash
ss -tulpn | grep :8080
Либо поменять порт хоста в -p.
Контейнер сразу останавливается:
bash
docker logs my-container
Смотришь логи — там обычно написано почему упало.
Не хватает места:
bash
docker system df # сколько занимает Docker
docker system prune -a # почистить всё неиспользуемое
Нет доступа к сети из контейнера:
Проверить что контейнер в нужной сети:
bash
docker network ls
docker inspect my-container | grep Network
С чего начать прямо сейчас
Если никогда не работал с Docker — вот минимальный путь чтобы почувствовать как это работает:
Первое — поставь Docker по инструкции выше. Второе — запусти docker run -d -p 8080:80 --name test-nginx nginx и открой http://localhost:8080 в браузере. Третье — зайди внутрь контейнера: docker exec -it test-nginx bash. Четвёртое — посмотри логи: docker logs -f test-nginx. Пятое — останови и удали: docker stop test-nginx && docker rm test-nginx.
Всё. После этих пяти шагов основная механика понятна. Дальше — Docker Compose и свой Dockerfile, когда появится реальная задача.
Docker не сложный. Он просто кажется сложным пока не потрогаешь руками.
Используешь Docker в своих проектах? Или только присматриваешься? Пиши в комментарии — разберём твой кейс.