1. Зачем Docker Compose и как он работает
Допустим, у вас FastAPI-приложение, PostgreSQL и Redis. Без Docker нужно вручную установить три разных продукта, следить за их версиями и конфликтами, настраивать systemd-юниты и сетевые соединения между ними.
Docker Compose решает это одним файлом docker-compose.yml. Каждый компонент — отдельный сервис. Compose автоматически создаёт изолированную сеть, в которой сервисы обращаются друг к другу по имени.
Воспроизводимость
Один и тот же compose-файл работает одинаково на ноутбуке разработчика и продакшн-сервере. "У меня работает" уходит в прошлое.
Изоляция
Каждый контейнер — изолированная среда. Разные проекты могут использовать разные версии Python, Node.js или PostgreSQL без конфликтов.
Простота
docker compose up -d — поднять всё. docker compose down — остановить. Деплой нового сервера занимает минуты.
Compose v1 vs v2 — важное отличие
Старый docker-compose (через дефис) — отдельный Python-инструмент, устаревший. Современный Docker включает Compose как встроенный плагин: команда docker compose (без дефиса). В этом гайде используется Compose v2.
2. Установка Docker на Ubuntu
Официальный скрипт — самый простой способ. Устанавливает Docker Engine, containerd и плагин Compose одной командой.
curl -fsSL https://get.docker.com | sh
# Запустить Docker и добавить в автозапуск
systemctl enable --now docker
# Проверить версии
docker --version # Docker version 26.x.x
docker compose version # Docker Compose version v2.x.xЧтобы запускать Docker без sudo:
sudo usermod -aG docker $USER
# Применить изменения (перелогиниться или выполнить)
newgrp docker
# Проверить — должно работать без sudo
docker psТребования к VPS
Docker работает на любом KVM-сервере с Ubuntu 22.04+. OpenVZ-контейнеры не поддерживают Docker из-за общего ядра. Минимум для простого приложения — 1 ГБ RAM; для стека с БД — 2 ГБ+.
3. Ключевые концепции за 5 минут
Неизменяемый слепок файловой системы. Скачивается из Docker Hub или собирается из Dockerfile. Образы не запускаются сами по себе — они основа для контейнеров.
Запущенный экземпляр образа. Изолированный процесс со своей файловой системой. Контейнеры одноразовые — данные внутри не сохраняются при удалении.
Постоянное хранилище под управлением Docker. Данные в volume живут отдельно от контейнера. Используется для БД, загруженных файлов, конфигов.
Один блок в docker-compose.yml — описывает образ, переменные окружения, порты, volumes и зависимости. Один сервис = один контейнер (или несколько при масштабировании).
Compose автоматически создаёт изолированную bridge-сеть для проекта. Сервисы обращаются друг к другу по имени сервиса: postgres, redis, app — как DNS-имена.
4. Первый compose: Nginx за 2 минуты
Начнём с простого примера — запустим Nginx и посмотрим структуру compose-файла.
mkdir -p ~/myapp && cd ~/myapp# Версия схемы — опциональна в Compose v2, но помогает IDE
version: "3.9"
services:
web:
image: nginx:alpine # Образ с Docker Hub
container_name: myapp-nginx
restart: unless-stopped # Автоперезапуск (кроме явной остановки)
ports:
- "8080:80" # хост:контейнер
volumes:
- ./html:/usr/share/nginx/html:ro # bind mount — только чтениеmkdir html
echo "<h1>Hello from Docker Compose!</h1>" > html/index.html
# Запустить в фоне (-d = detached)
docker compose up -d
# Проверить статус
docker compose ps
# Открыть в браузере или curl
curl http://localhost:8080docker compose stop # Остановить (контейнеры остаются)
docker compose start # Запустить снова
docker compose down # Остановить И удалить контейнеры (volumes сохраняются)
docker compose down -v # Удалить контейнеры И volumesrestart: unless-stopped
Контейнер перезапустится автоматически после перезагрузки VPS или краша. Исключение — если вы сами остановили его командой docker compose stop. Используйте это на продакшне для всех сервисов.
5. Реальный деплой: приложение + PostgreSQL
Разберём типичный стек для Python/Node.js приложения: само приложение, PostgreSQL и Redis. Принципы одинаковы для FastAPI, Django, Express, Next.js.
Структура проекта
myapp/
├── docker-compose.yml
├── .env # секреты (в .gitignore!)
├── .env.example # шаблон для команды (в git)
└── app/
├── Dockerfile
└── ... # исходный кодФайл .env
# База данных
POSTGRES_USER=appuser
POSTGRES_PASSWORD=ЗАМЕНИТЕ_НА_СЛОЖНЫЙ_ПАРОЛЬ
POSTGRES_DB=appdb
# Подключение приложения к БД
DATABASE_URL=postgresql://appuser:ЗАМЕНИТЕ_НА_СЛОЖНЫЙ_ПАРОЛЬ@postgres:5432/appdb
# Redis
REDIS_URL=redis://redis:6379/0
# Приложение
SECRET_KEY=СГЕНЕРИРУЙТЕ_openssl_rand_-hex_32
APP_ENV=productionДобавьте .env в .gitignore
echo ".env" >> .gitignoreФайл .env содержит пароли. Никогда не коммитьте его в репозиторий. Создайте .env.example с теми же ключами, но пустыми значениями.
docker-compose.yml для production
version: "3.9"
services:
# ──── База данных ────────────────────────────────────────
postgres:
image: postgres:16-alpine
container_name: myapp-postgres
restart: unless-stopped
env_file: .env
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
# ──── Кэш ────────────────────────────────────────────────
redis:
image: redis:7-alpine
container_name: myapp-redis
restart: unless-stopped
volumes:
- redis_data:/data
# ──── Приложение ─────────────────────────────────────────
app:
build:
context: ./app # Dockerfile в директории app/
dockerfile: Dockerfile
container_name: myapp-app
restart: unless-stopped
env_file: .env
ports:
- "127.0.0.1:8000:8000" # только localhost, Nginx снаружи
depends_on:
postgres:
condition: service_healthy # ждать готовности БД
redis:
condition: service_started
# ──── Именованные volumes (данные не удаляются при down) ───
volumes:
postgres_data:
redis_data:127.0.0.1:8000 — не 0.0.0.0
Привязка к 127.0.0.1:8000 означает, что порт виден только на самом сервере. Nginx будет проксировать запросы с 80/443 внутрь. Если написать 8000:8000 — порт откроется на весь интернет, что небезопасно.
Пример Dockerfile для FastAPI / Node.js
FastAPI (Python)
FROM python:3.12-slim
WORKDIR /app
# Сначала зависимости — кэшируются отдельным слоем
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Запустить uvicorn на 0.0.0.0:8000
CMD ["uvicorn", "main:app",
"--host", "0.0.0.0",
"--port", "8000",
"--workers", "2"]Next.js (Node.js)
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone .
COPY --from=builder /app/.next/static .next/static
COPY --from=builder /app/public ./public
ENV PORT=8000
CMD ["node", "server.js"]Next.js standalone: нужно включить в next.config.js
Dockerfile для Next.js копирует .next/standalone — эта папка создаётся только при включённом режиме standalone. Добавьте в next.config.js (или next.config.mjs):
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
}
export default nextConfigБез этой строки папка .next/standalone не создаётся и сборка Docker завершится ошибкой.
cd ~/myapp
# Сборка образа и запуск всего стека
docker compose up -d --build
# Смотреть логи запуска
docker compose logs -f
# Проверить что все сервисы healthy
docker compose ps6. Nginx как обратный прокси
Приложение слушает 127.0.0.1:8000. Nginx принимает запросы снаружи на порту 443 и проксирует их внутрь.
apt install nginx certbot python3-certbot-nginx -yserver {
listen 80;
server_name example.com www.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Для WebSocket (Next.js HMR, FastAPI WebSocket)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
client_max_body_size 50M;
proxy_read_timeout 300s;
}
}ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
# SSL через Let's Encrypt
certbot --nginx -d example.com -d www.example.comПосле Certbot Nginx перезапустится с HTTPS. Certbot автоматически добавит cron для обновления сертификата каждые 90 дней.
7. Обновление без downtime
При стандартном docker compose up -d контейнер пересоздаётся — есть небольшая пауза (1–3 секунды). Для большинства проектов это приемлемо. Вот оптимальный процесс:
Обновление приложения (пересборка образа)
cd ~/myapp
# 1. Получить новый код
git pull
# 2. Пересобрать образ и перезапустить ТОЛЬКО сервис app
# --no-deps — не трогать postgres и redis
docker compose up -d --build --no-deps app
# 3. Убедиться, что всё работает
docker compose ps
docker compose logs -f app --tail 50Обновление сторонних образов (postgres, redis)
# Скачать обновлённые образы
docker compose pull postgres redis
# Пересоздать только обновлённые сервисы
docker compose up -d --no-deps postgres redisБэкап перед обновлением PostgreSQL
При переходе на новую мажорную версию PostgreSQL (например, 15→16) требуется миграция данных. Сделайте дамп заранее:
# Создать дамп (замените appuser/appdb на значения из .env)
# docker compose exec читает переменные из .env — docker exec не читает
docker compose exec postgres pg_dump -U appuser appdb \
> backup-$(date +%Y%m%d-%H%M).sql
# Восстановить (если что-то пошло не так)
# -T — отключить псевдо-TTY, необходимо при передаче stdin через pipe
cat backup-YYYYMMDD-HHMM.sql | docker compose exec -T postgres \
psql -U appuser appdbФиксация версий образов
В продакшне используйте конкретные теги вместо latest, чтобы контролировать момент обновления:
# Вместо:
image: postgres:16-alpine
image: redis:7-alpine
# Фиксированные патч-версии:
image: postgres:16.4-alpine
image: redis:7.4-alpine8. Шпаргалка по командам
| Команда | Что делает |
|---|---|
| docker compose up -d | Поднять все сервисы в фоне |
| docker compose up -d --build | Поднять с пересборкой образов |
| docker compose down | Остановить и удалить контейнеры |
| docker compose down -v | Удалить контейнеры и volumes (осторожно!) |
| docker compose ps | Статус всех сервисов |
| docker compose logs -f app | Live-логи сервиса app |
| docker compose exec app sh | Зайти в контейнер app |
| docker compose restart app | Перезапустить один сервис |
| docker compose pull | Скачать обновлённые образы |
| docker compose config | Показать итоговый конфиг (с подстановкой .env) |
| docker system prune -f | Удалить неиспользуемые образы/контейнеры |
| docker volume ls | Список всех volumes |
| docker stats | Потребление CPU/RAM в реальном времени |
# Посмотреть все переменные окружения контейнера
docker compose exec app env
# Выполнить команду в контейнере без интерактивного режима
docker compose exec postgres psql -U appuser appdb
# Скопировать файл из контейнера на хост
docker compose cp app:/app/logs/error.log ./error.log
# Посмотреть реальное потребление ресурсов
docker stats --no-stream