Docker для фронтенд- и бэк-разработчиков: практический гайд без DevOps-магии
Docker — это не «магия для DevOps», а нормальный инженерный инструмент, который решает очень приземлённую проблему:
один и тот же проект должен одинаково запускаться локально, у коллеги и на сервере.
Если ты фронтендер, бэкендер, PHP- или Node-разработчик — Docker нужен тебе ровно для этого.
Не для Kubernetes, не для резюме, не для пафоса.
Ниже — честный и практический гайд. Строго по документации, с примерами из реальной разработки.
Что такое контейнер и почему он лучше VM
Контейнер простыми словами
Контейнер — это изолированный процесс в Linux с:
- собственным файловым пространством,
- своей сетью,
- своими зависимостями.
Контейнер не содержит своей операционной системы — только процесс(ы) и файлы из образа.
Контейнер vs VirtualBox / KVM — на одном примере
Задача: запустить Node.js + PostgreSQL для проекта.
Виртуальная машина (VirtualBox / KVM)
- Создаёшь VM с Ubuntu
- Ставишь Node.js
- Ставишь PostgreSQL
- Ловишь конфликт версий
- Настраиваешь systemd
- VM весит 2–4 ГБ
- Запускается минутами
Docker
docker compose up
- Node и Postgres — в контейнерах
- Вес — сотни мегабайт
- Запуск — секунды
- У всех разработчиков одинаковое окружение
Ключевая разница:
VM виртуализирует железо , Docker изолирует процессы.
Основы Docker
Установка Docker
Ubuntu
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo usermod -aG docker $USER
Перелогинься, иначе Docker будет требовать sudo.
CentOS / Rocky / AlmaLinux
sudo dnf install -y docker
sudo systemctl enable --now docker
Windows (через WSL2)
- Установить Docker Desktop
- Включить WSL2
- Docker работает в Linux , а не в Windows
Docker без WSL2 на Windows — плохая идея. Не надо так.
Основные понятия Docker
- Image (образ) — шаблон (read-only)
- Container (контейнер) — запущенный образ
- Layer (слой) — шаг сборки образа
- Registry — хранилище образов (Docker Hub, private registry)
Базовые команды Docker
docker run nginx
docker ps
docker ps -a
docker logs container\_name
docker exec -it container\_name sh
docker run vs docker exec
- docker run — создаёт и запускает новый контейнер
- docker exec — входит в уже запущенный контейнер
Типичная ошибка новичков — пытаться exec в контейнер, который не запущен.
Типичные ошибки новичков
- Хранить данные внутри контейнера
- Использовать тег latest
- Запускать сервисы через service nginx start
- Не читать docker logs
- Копировать весь проект без .dockerignore
Dockerfile: собираем образ
Базовая структура Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package\*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["npm","run","start"]
Основные инструкции
- FROM — базовый образ
- RUN — команда при сборке
- COPY — копирование файлов
- ENV — переменные окружения
- EXPOSE — документируем порт
- CMD — команда запуска контейнера
- ENTRYPOINT — точка входа
Пример 1: Dockerfile для Node.js (Astro / Express)
FROM node:20-alpine AS build
WORKDIR /app
COPY package\*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY package\*.json ./
RUN npm ci --omit=dev
EXPOSE 3000
CMD ["node","dist/server.js"]
Почему так правильно:
- multistage-сборка
- dev-зависимости не попадают в production
- минимальный размер образа
Пример 2: Dockerfile для PHP (WordPress / Bitrix)
FROM php:8.4-fpm-alpine
RUN apk add --no-cache \
bash git icu-dev libzip-dev oniguruma-dev \
&& docker-php-ext-install intl zip mysqli opcache
WORKDIR /var/www/html
WordPress и Bitrix официально используют php-fpm.
Apache внутри контейнера почти всегда лишний.
.dockerignore — обязательно
Минимальный набор (скопируй в корень проекта):
node\_modules
vendor
.git
.gitignore
.env
.env.local
.env.\*.local
\*.log
npm-debug.log\*
.DS\_Store
coverage
.nyc\_output
dist
.next
Расширенный вариант для Node.js (ещё меньше контекста — быстрее сборка):
node\_modules
vendor
.git
.gitignore
.env\*
\*.log
.DS\_Store
coverage
dist
.next
.nuxt
.cache
\*.md
!README.md
Без .dockerignore:
- образы раздуваются,
- ломается кеш,
- сборка становится медленной.
Рекомендации по Dockerfile
- Используй alpine
- Объединяй RUN в один слой
- COPY package.json до копирования кода
- Всегда используй multistage, если есть сборка
Проверка размера образа:
docker image ls
Пример вывода (образ без alpine и с лишними слоями будет в разы больше):
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp latest a1b2c3d4e5f6 2 minutes ago 180MB
Сборка с тегом и без кеша (если что-то пошло не так):
docker build --no-cache -t myapp:1.0 .
Минимальный рабочий пример (copy-paste)
Ниже — полный набор файлов, чтобы поднять Node.js + PostgreSQL за минуту.
Dockerfile в корне проекта:
FROM node:20-alpine
WORKDIR /app
COPY package\*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
docker-compose.yml :
services:
app:
build: .
ports:
- "3000:3000"
environment:
DATABASE\_URL: postgresql://postgres:postgres@db:5432/app
depends\_on:
db:
condition: service\_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES\_USER: postgres
POSTGRES\_PASSWORD: postgres
POSTGRES\_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg\_isready -U postgres"]
interval: 2s
timeout: 5s
retries: 5
volumes:
pgdata:
Запуск и проверка:
docker compose up -d
docker compose ps
curl -s http://localhost:3000
docker compose logs -f app
Docker Compose для разработки
docker-compose.yml (версия 3.x)
version: "3.9"
services:
app:
build: .
ports:
- "3000:3000"
env\_file:
- .env
Типовые сервисы
- nginx
- php-fpm
- mysql / postgres
- redis
Один сервис — один контейнер. Всегда.
Networks и volumes
- network — контейнеры общаются по имени сервиса
- volume — постоянные данные
- bind mount — файлы проекта (локальная разработка)
Пример 1: WordPress (nginx + php-fpm + mysql + phpMyAdmin)
services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./wp:/var/www/html
ports:
- "8080:80"
php:
build: .
volumes:
- ./wp:/var/www/html
environment:
DB\_HOST: db
depends\_on:
- db
db:
image: mysql:8
environment:
MYSQL\_ROOT\_PASSWORD: root
MYSQL\_DATABASE: wordpress
phpmyadmin:
image: phpmyadmin/phpmyadmin
environment:
PMA\_HOST: db
ports:
- "8081:80"
Пример 2: Node.js + PostgreSQL + Redis
services:
app:
build: .
ports:
- "3000:3000"
depends\_on:
- db
- redis
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7
volumes:
pgdata:
Env-файлы
- .env — локальная разработка
- .env.production — продакшен
- Секреты не коммить
Команды Docker Compose
docker compose up -d
docker compose down
docker compose ps
docker compose logs -f app
Как дебажить контейнер в Compose
Войти в оболочку запущенного сервиса:
docker compose exec app sh
Внутри контейнера можно проверить переменные, установленные пакеты и сеть:
Переменные окружения
env | grep DATABASE
Есть ли сеть до базы
nc -zv db 5432
Альпийский образ: вместо curl часто есть wget
wget -qO- http://localhost:3000
Выйти из контейнера: exit.
Логирование и отладка
docker logs
docker logs -f container\_name
Если контейнер упал — причина почти всегда в логах.
docker inspect
docker inspect container\_name
Там:
- IP,
- volumes,
- env,
- команды запуска.
Проброс портов
localhost:3000 → container:3000
Если порт не проброшен — с хоста до приложения в контейнере не достучаться.
localhost и IP-адреса внутри контейнера
Внутри Docker:
- ❌ 127.0.0.1
- ❌ localhost
- ✅ имя сервиса (db, redis)
Docker DNS работает по имени сервиса.
Деплой контейнера на сервер
Production-сборка
- без dev-зависимостей
- без hot-reload
- без bind-mount
Публикация образа в registry
docker build -t username/app:1.0 .
docker push username/app:1.0
Можно использовать Docker Hub или приватный registry.
Запуск на сервере через docker run
На сервере (после docker pull или если образ уже в registry):
docker run -d \
--name app \
-p 80:3000 \
--restart=always \
-e NODE\_ENV=production \
username/app:1.0
Проверка, что контейнер работает:
docker ps
curl -s -o /dev/null -w "%{http\_code}" http://localhost:80
systemd unit-файл
[Unit]
Description=Docker App
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker run --rm -p 80:3000 username/app:1.0
ExecStop=/usr/bin/docker stop app
[Install]
WantedBy=multi-user.target
Данные в production
- volumes — базы данных
- bind mounts — конфиги
Контейнеры можно удалять, данные — нет.
Обновление без простоя
- Поднять новый контейнер
- Переключить трафик (nginx)
- Остановить старый
Для небольших проектов этого достаточно.
Про Kubernetes — честно
Когда нужен Kubernetes
- десятки сервисов
- автоскейлинг
- отказоустойчивость
- несколько окружений
Базовые сущности K8s
- Pod — один или несколько контейнеров
- Deployment — управление версиями
- Service — доступ к подам
Когда Kubernetes не нужен
- один сервер
- один проект
- небольшая команда
В этом случае Docker Compose — лучше.
Альтернативы Kubernetes
- Docker Swarm
- AWS ECS
- HashiCorp Nomad
Типичные ошибки и решения
Контейнер стартует и сразу падает
Причина — ошибка в CMD или ENTRYPOINT. Сначала смотри логи:
docker logs container\_name
Если контейнер сразу падает и логи пустые — запусти образ без флага -d , чтобы увидеть вывод в консоли:
docker run --rm --name debug-app -p 3000:3000 myapp:latest
Ошибка (например, «Cannot find module») появится сразу в терминале. После исправления кода пересобери образ и снова запусти контейнер.
Port already in use
Узнай, какой процесс занял порт, и заверши его:
Linux / macOS
lsof -i :3000
или
sudo ss -tlnp | grep 3000
Убить процесс по PID (подставь реальный PID из вывода)
kill -9 PID
На Windows (WSL2) порт может держать другой контейнер — проверь docker ps и останови старый контейнер: docker stop container_name.
localhost внутри контейнера — это не хост
Для контейнера localhost и 127.0.0.1 указывают на сам контейнер , а не на твою машину. Это нормально: контейнер изолирован, у него свой сетевой namespace. Чтобы достучаться до сервиса на хосте с Windows/Mac, используй host.docker.internal (Docker Desktop) или --add-host=host.docker.internal:host-gateway при запуске.
Медленная сборка образа
- Неправильный порядок COPY
- Нет .dockerignore
- Не используется кеш слоёв
Практические сниппеты по теме
На сайте есть готовые сниппеты с разбором команд, сетей и хранения данных:
- Docker: разница между run, start и exec — когда создавать контейнер, когда запускать остановленный, как войти в уже работающий
- Docker networking: почему localhost не работает между контейнерами — embedded DNS, имя сервиса вместо 127.0.0.1, пример docker-compose
- Как уменьшить размер Docker-образа: alpine, кеш и порядок инструкций — Alpine vs Debian, кеширование слоёв, правильный порядок COPY в Dockerfile
- Docker Compose: volumes vs bind mounts — когда bind mount (разработка), когда volume (production), пример для обоих вариантов
- Docker system prune — очистка неиспользуемых образов, контейнеров и томов
Итог
Docker — это:
- не DevOps-магия,
- не Kubernetes,
- не оверинженерия.
Это инструмент разработчика , который:
- упрощает локальную разработку,
- убирает «у меня работает»,
- делает деплой предсказуемым.
Если раньше ты делал VM — Docker станет логичным следующим шагом.
Не сразу идеально, но один раз правильно.

Top comments (0)