Добавить в корзинуПозвонить
Найти в Дзене
Ivan-Yurievich

Docker для сисадмина — контейнеры без боли

Помню своё первое знакомство с Docker. Открыл документацию, увидел слова «слои образов», «оркестрация», «volumes», «сети между контейнерами» — и закрыл вкладку. Показалось что это какая-то космическая технология для больших компаний с DevOps-отделом. Прошло несколько месяцев, я всё-таки заставил себя разобраться — и теперь не представляю жизнь без Docker. Оказалось что 80% того что нужно сисадмину в реальной работе — это очень простые вещи. Никакой космонавтики. В этой статье я расскажу именно про эти 80%. Без теории ради теории, без академических определений. Только то, что реально используется каждый день. Хороший вопрос. Раньше я тоже так думал: «у меня nginx стоит, PHP работает, MySQL крутится — зачем мне какие-то контейнеры?» Вот реальные сценарии где Docker спасает: «На моей машине работает» — классика жанра. Разработчик сделал приложение, на его ноуте всё ок, на сервере падает. Версии Node.js разные, библиотеки разные, переменные окружения разные. С Docker приложение везде запус
Оглавление

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 в своих проектах? Или только присматриваешься? Пиши в комментарии — разберём твой кейс.