← Назад в блог

Docker для фронтенд- и бэк-разработчиков: практический гайд без DevOps-магии

Практический гайд по Docker: контейнеры, Dockerfile, Docker Compose, деплой и типовые ошибки. Примеры для Node.js, PHP, WordPress, PostgreSQL. Без оверинженеринга — для разработчиков.

Docker для фронтенд- и бэк-разработчиков: практический гайд без DevOps-магии

Требования

  • Linux / macOS / Windows (WSL2)
  • Базовое понимание командной строки
  • Опыт разработки на PHP или Node.js

Docker для фронтенд- и бэк-разработчиков: практический гайд без DevOps-магии

Docker — это не «магия для DevOps», а нормальный инженерный инструмент, который решает очень приземлённую проблему: один и тот же проект должен одинаково запускаться локально, у коллеги и на сервере.

Если ты фронтендер, бэкендер, PHP- или Node-разработчик — Docker нужен тебе ровно для этого. Не для Kubernetes, не для резюме, не для пафоса.

Ниже — честный и практический гайд. Строго по документации, с примерами из реальной разработки.


Что такое контейнер и почему он лучше VM

Контейнер простыми словами

Контейнер — это изолированный процесс в Linux с:

  • собственным файловым пространством,
  • своей сетью,
  • своими зависимостями.

Контейнер не содержит своей операционной системы — только процесс(ы) и файлы из образа.


Контейнер vs VirtualBox / KVM — на одном примере

Задача: запустить Node.js + PostgreSQL для проекта.

Виртуальная машина (VirtualBox / KVM)

  1. Создаёшь VM с Ubuntu
  2. Ставишь Node.js
  3. Ставишь PostgreSQL
  4. Ловишь конфликт версий
  5. Настраиваешь systemd
  6. VM весит 2–4 ГБ
  7. Запускается минутами

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)

  1. Установить Docker Desktop
  2. Включить WSL2
  3. 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 — конфиги

Контейнеры можно удалять, данные — нет.


Обновление без простоя

  1. Поднять новый контейнер
  2. Переключить трафик (nginx)
  3. Остановить старый

Для небольших проектов этого достаточно.


Про 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 — это:

  • не DevOps-магия,
  • не Kubernetes,
  • не оверинженерия.

Это инструмент разработчика, который:

  • упрощает локальную разработку,
  • убирает «у меня работает»,
  • делает деплой предсказуемым.

Если раньше ты делал VM — Docker станет логичным следующим шагом. Не сразу идеально, но один раз правильно.

0 просмотров

Комментарии

Загрузка комментариев...
Пока нет комментариев. Будьте первым!