VPSРейтинг
Разработка16 февраля 2026 · 12 мин чтения

Docker Compose на VPS: деплой приложения за 10 минут

Docker Compose — стандарт для деплоя современных приложений. Один файл описывает весь стек: приложение, база данных, кэш. Одна команда docker compose up -d поднимает всё на любом сервере. Настроим с нуля.

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 одной командой.

Установка Docker Engine на Ubuntu 22.04 / 24.04
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:

Добавить пользователя в группу docker
sudo usermod -aG docker $USER

# Применить изменения (перелогиниться или выполнить)
newgrp docker

# Проверить — должно работать без sudo
docker ps

Требования к VPS

Docker работает на любом KVM-сервере с Ubuntu 22.04+. OpenVZ-контейнеры не поддерживают Docker из-за общего ядра. Минимум для простого приложения — 1 ГБ RAM; для стека с БД — 2 ГБ+.

3. Ключевые концепции за 5 минут

Image (образ)

Неизменяемый слепок файловой системы. Скачивается из Docker Hub или собирается из Dockerfile. Образы не запускаются сами по себе — они основа для контейнеров.

Container (контейнер)

Запущенный экземпляр образа. Изолированный процесс со своей файловой системой. Контейнеры одноразовые — данные внутри не сохраняются при удалении.

Volume

Постоянное хранилище под управлением Docker. Данные в volume живут отдельно от контейнера. Используется для БД, загруженных файлов, конфигов.

Service (сервис)

Один блок в docker-compose.yml — описывает образ, переменные окружения, порты, volumes и зависимости. Один сервис = один контейнер (или несколько при масштабировании).

Network (сеть)

Compose автоматически создаёт изолированную bridge-сеть для проекта. Сервисы обращаются друг к другу по имени сервиса: postgres, redis, app — как DNS-имена.

4. Первый compose: Nginx за 2 минуты

Начнём с простого примера — запустим Nginx и посмотрим структуру compose-файла.

Создать проект
mkdir -p ~/myapp && cd ~/myapp
~/myapp/docker-compose.yml
# Версия схемы — опциональна в 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:8080
Управление
docker compose stop    # Остановить (контейнеры остаются)
docker compose start   # Запустить снова
docker compose down    # Остановить И удалить контейнеры (volumes сохраняются)
docker compose down -v # Удалить контейнеры И volumes

restart: 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

~/myapp/.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

~/myapp/docker-compose.yml
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)

app/Dockerfile
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)

app/Dockerfile
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 ps

6. Nginx как обратный прокси

Приложение слушает 127.0.0.1:8000. Nginx принимает запросы снаружи на порту 443 и проксирует их внутрь.

Установка Nginx и Certbot
apt install nginx certbot python3-certbot-nginx -y
/etc/nginx/sites-available/myapp
server {
    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;
    }
}
Активировать и получить SSL
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) требуется миграция данных. Сделайте дамп заранее:

Бэкап и восстановление PostgreSQL
# Создать дамп (замените 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, чтобы контролировать момент обновления:

Зафиксировать версии в docker-compose.yml
# Вместо:
image: postgres:16-alpine
image: redis:7-alpine

# Фиксированные патч-версии:
image: postgres:16.4-alpine
image: redis:7.4-alpine

8. Шпаргалка по командам

КомандаЧто делает
docker compose up -dПоднять все сервисы в фоне
docker compose up -d --buildПоднять с пересборкой образов
docker compose downОстановить и удалить контейнеры
docker compose down -vУдалить контейнеры и volumes (осторожно!)
docker compose psСтатус всех сервисов
docker compose logs -f appLive-логи сервиса 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

Частые вопросы

Нужен VPS под Docker? Рекомендуем тарифы от 2 ГБ RAM

VPS для Docker →

Смотрите также