DEV Community

Андрей Викулов (VProger)
Андрей Викулов (VProger)

Posted on • Originally published at viku-lov.ru on

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

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

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

Enter fullscreen mode Exit fullscreen mode
  • Node и Postgres — в контейнерах
  • Вес — сотни мегабайт
  • Запуск — секунды
  • У всех разработчиков одинаковое окружение

Ключевая разница:

VM виртуализирует железо , Docker изолирует процессы.


Основы Docker

Установка Docker

Ubuntu


sudo apt update

sudo apt install -y docker.io docker-compose-plugin

sudo usermod -aG docker $USER

Enter fullscreen mode Exit fullscreen mode

Перелогинься, иначе Docker будет требовать sudo.


CentOS / Rocky / AlmaLinux


sudo dnf install -y docker

sudo systemctl enable --now docker

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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"]

Enter fullscreen mode Exit fullscreen mode

Основные инструкции

  • 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"]

Enter fullscreen mode Exit fullscreen mode

Почему так правильно:

  • 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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Расширенный вариант для Node.js (ещё меньше контекста — быстрее сборка):


node\_modules

vendor

.git

.gitignore

.env\*

\*.log

.DS\_Store

coverage

dist

.next

.nuxt

.cache

\*.md

!README.md

Enter fullscreen mode Exit fullscreen mode

Без .dockerignore:

  • образы раздуваются,
  • ломается кеш,
  • сборка становится медленной.

Рекомендации по Dockerfile

  • Используй alpine
  • Объединяй RUN в один слой
  • COPY package.json до копирования кода
  • Всегда используй multistage, если есть сборка

Проверка размера образа:


docker image ls

Enter fullscreen mode Exit fullscreen mode

Пример вывода (образ без alpine и с лишними слоями будет в разы больше):


REPOSITORY TAG IMAGE ID CREATED SIZE

myapp latest a1b2c3d4e5f6 2 minutes ago 180MB

Enter fullscreen mode Exit fullscreen mode

Сборка с тегом и без кеша (если что-то пошло не так):


docker build --no-cache -t myapp:1.0 .

Enter fullscreen mode Exit fullscreen mode

Минимальный рабочий пример (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"]

Enter fullscreen mode Exit fullscreen mode

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:

Enter fullscreen mode Exit fullscreen mode

Запуск и проверка:


docker compose up -d

docker compose ps

curl -s http://localhost:3000

docker compose logs -f app

Enter fullscreen mode Exit fullscreen mode

Docker Compose для разработки

docker-compose.yml (версия 3.x)


version: "3.9"

services:

app:

build: .

ports:

- "3000:3000"

env\_file:

- .env

Enter fullscreen mode Exit fullscreen mode

Типовые сервисы

  • 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"

Enter fullscreen mode Exit fullscreen mode

Пример 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:

Enter fullscreen mode Exit fullscreen mode

Env-файлы

  • .env — локальная разработка
  • .env.production — продакшен
  • Секреты не коммить

Команды Docker Compose


docker compose up -d

docker compose down

docker compose ps

docker compose logs -f app

Enter fullscreen mode Exit fullscreen mode

Как дебажить контейнер в Compose

Войти в оболочку запущенного сервиса:


docker compose exec app sh

Enter fullscreen mode Exit fullscreen mode

Внутри контейнера можно проверить переменные, установленные пакеты и сеть:


Переменные окружения

env | grep DATABASE

Есть ли сеть до базы

nc -zv db 5432

Альпийский образ: вместо curl часто есть wget

wget -qO- http://localhost:3000

Enter fullscreen mode Exit fullscreen mode

Выйти из контейнера: exit.


Логирование и отладка

docker logs


docker logs -f container\_name

Enter fullscreen mode Exit fullscreen mode

Если контейнер упал — причина почти всегда в логах.


docker inspect


docker inspect container\_name

Enter fullscreen mode Exit fullscreen mode

Там:

  • IP,
  • volumes,
  • env,
  • команды запуска.

Проброс портов


localhost:3000 → container:3000

Enter fullscreen mode Exit fullscreen mode

Если порт не проброшен — с хоста до приложения в контейнере не достучаться.


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

Enter fullscreen mode Exit fullscreen mode

Можно использовать 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

Enter fullscreen mode Exit fullscreen mode

Проверка, что контейнер работает:


docker ps

curl -s -o /dev/null -w "%{http\_code}" http://localhost:80

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Данные в 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

Enter fullscreen mode Exit fullscreen mode

Если контейнер сразу падает и логи пустые — запусти образ без флага -d , чтобы увидеть вывод в консоли:


docker run --rm --name debug-app -p 3000:3000 myapp:latest

Enter fullscreen mode Exit fullscreen mode

Ошибка (например, «Cannot find module») появится сразу в терминале. После исправления кода пересобери образ и снова запусти контейнер.


Port already in use

Узнай, какой процесс занял порт, и заверши его:


Linux / macOS

lsof -i :3000

или

sudo ss -tlnp | grep 3000

Убить процесс по PID (подставь реальный PID из вывода)

kill -9 PID

Enter fullscreen mode Exit fullscreen mode

На 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 станет логичным следующим шагом.

Не сразу идеально, но один раз правильно.

Read more on viku-lov.ru

Top comments (0)