Как развернуть приложение Docker Compose на VPS с Nginx и SSL

Валерий Волков

Время прочтения 15 минут

В этом руководстве мы развернём небольшое веб-приложение на VPS с Ubuntu 24.04 с помощью Docker Compose. Приложение будет работать внутри контейнера, а Nginx — принимать внешние HTTP- и HTTPS-запросы и передавать их на локальный порт контейнера.

Итоговая схема будет выглядеть так: пользователь → HTTPS → Nginx → Docker-контейнер → Приложение

Для развёртывания потребуется VPS с публичным IP, домен или поддомен, а также открытые TCP-порты 22, 80 и 443. Для небольшого тестового приложения достаточно конфигурации с 2 vCPU, 2–4 ГБ оперативной памяти и диском от 20 ГБ.

Сначала создадим виртуальную машину и подключимся к ней по SSH. Затем установим Docker Engine и Docker Compose Plugin, подготовим файлы приложения, Dockerfile, .dockerignore, переменные окружения и конфигурацию compose.yaml.

После запуска контейнера проверим его состояние и журналы, а затем настроим Nginx как reverse proxy. В этой схеме приложение будет слушать только локальный порт VPS, а наружу будут открыты только Nginx и стандартные веб-порты.

В нашем примере Nginx + certbot будут установлены на хост без докера, для демонстрации возможностей гибридного использования (или установки на разных VPS).

Для стенда будет использоваться поддомен: docker.deploy-test-lab.com

После создания DNS-записи выпустим SSL-сертификат Let’s Encrypt и настроим автоматическое перенаправление с HTTP на HTTPS. Также разберём порядок обновления приложения: получение новой версии файлов, повторную сборку образа и перезапуск контейнеров через Docker Compose.

В результате получим работающее контейнерное приложение, доступное по доменному имени через HTTPS, с автоматическим запуском Docker и контейнеров после перезагрузки VPS.

Архитектура приложения на Docker Compose

Перед развёртыванием разберём, какие компоненты будут работать на VPS и каким образом запросы пользователей попадут в контейнер с приложением.

Какие компоненты понадобятся

В этом руководстве используются следующие компоненты:

Компонент Назначение 
Ubuntu 24.04 LTS Операционная система виртуальной машины 
Docker Engine Запускает и изолирует контейнеры 
Docker Compose Управляет приложением через файл compose.yaml
Dockerfile Описывает сборку образа приложения 
Nginx Принимает внешние HTTP- и HTTPS-запросы, терминирует SSL 
Certbot Выпускает и обновляет SSL-сертификат Let’s Encrypt 
DNS Связывает поддомен с публичным IP виртуальной машины 

В качестве примера развернём небольшое веб-приложение на Node.js. Оно будет запускаться внутри контейнера и отвечать на локальном порту 3000.

Для тестового проекта достаточно VPS с 2 vCPU, 2–4 ГБ оперативной памяти и диском от 20 ГБ. Серверу также потребуются публичный IP и входящие TCP-порты 22, 80 и 443.

Как взаимодействуют Docker Compose, приложение и Nginx

Docker Engine создаёт изолированную среду для приложения. Внутри контейнера находятся нужная версия Node.js, зависимости и исходные файлы проекта. Благодаря этому приложение не зависит от глобально установленных на VPS библиотек.

Docker Compose читает конфигурацию из файла compose.yaml. В ней указываются параметры сборки, имя сервиса, порты, переменные окружения, сети и политика автоматического перезапуска контейнера.

Приложение будет доступно только через локальный интерфейс VPS: 127.0.0.1:3000

Такой адрес нельзя открыть напрямую из интернета. Все внешние запросы сначала принимает Nginx, после чего, как reverse proxy,  передаёт их приложению.

Nginx также отвечает за подключение домена, перенаправление с HTTP на HTTPS и работу с SSL-сертификатом. В результате посетитель взаимодействует только с Nginx, а внутренняя структура Docker-приложения остаётся скрытой.

Подготовка виртуальной машины

Сначала создадим чистую виртуальную машину с Ubuntu 24.04 LTS, подключим к ней публичный IP и проверим сетевые правила. Процесс практически не отличается от подготовки VPS для обычного приложения, однако сам проект позднее будет запускаться в контейнере.

Создание VM в облачной панели

В панели управления облаком создайте новую виртуальную машину. Для тестового приложения можно использовать следующую конфигурацию:

  • имя VM — docker-compose-guide;
  • образ — Ubuntu 24.04 LTS;
  • 2 vCPU;
  • 4 ГБ оперативной памяти;
  • системный диск 20 ГБ;
  • приватная сеть проекта;
  • авторизация по SSH key pair.

При создании нового SSH key pair скачайте приватный ключ и сохраните его на своём компьютере. Он потребуется для подключения к серверу.

После подтверждения конфигурации дождитесь, пока виртуальная машина перейдёт в статус Active.

Подключение публичного IP и настройка firewall

Изначально виртуальная машина получает приватный IP внутри облачной сети. Для подключения по SSH, работы домена и открытия приложения из интернета к VM нужно привязать Floating IP.

В этом руководстве будет повторно использован публичный адрес: 203.0.113.10

В своей конфигурации необходимо использовать Floating IP, который назначен вашей виртуальной машине.

В security group разрешите входящие TCP-подключения:

Порт Назначение 
22Подключение к VPS по SSH 
80HTTP и проверка домена Let’s Encrypt 
443Защищённые HTTPS-подключения 

Порт приложения 3000 открывать для интернета не нужно. Позднее Docker опубликует его только на локальном интерфейсе 127.0.0.1, а внешние запросы будет принимать Nginx.

Подключение к VPS по SSH

На  актуальных версиях Windows для подключения можно использовать PowerShell, командную строку или Windows Terminal. Перейдите в каталог с приватным SSH-ключом и выполните: ssh -i .\docker-compose-guide.pem ubuntu@<FLOATING_IP>

Вместо <FLOATING_IP> укажите публичный адрес своей виртуальной машины. В нашем случае команда будет выглядеть так: ssh -i .\docker-compose-guide.pem ubuntu@203.0.113.10

Название файла ключа также может отличаться. Вместо docker-compose-guide.pem укажите имя или полный путь к собственному приватному ключу.

При первом подключении SSH предложит подтвердить fingerprint сервера. Введите: yes

Если этот Floating IP ранее использовался другой виртуальной машиной, SSH может сообщить об изменении ключа удалённого узла. Старую запись можно удалить командой: ssh-keygen -R 203.0.113.10

После этого повторите подключение и подтвердите новый fingerprint. Удалять сохранённый ключ следует только в том случае, если изменение ожидаемо — например, после удаления старой VM и назначения того же IP новому серверу.

Обновление Ubuntu

После подключения обновите индекс пакетов и установленные компоненты:

sudo apt update

sudo apt upgrade -y

Затем установите базовые утилиты, которые понадобятся для добавления репозитория Docker и дальнейшей настройки сервера: sudo apt install -y ca-certificates curl gnupg unzip ufw

Проверьте версию операционной системы: cat /etc/os-release

В выводе должна быть указана Ubuntu 24.04 LTS. После обновления виртуальная машина готова к установке Docker Engine и Docker Compose.

Установка Docker и Docker Compose

Подключение официального репозитория Docker

Docker можно установить из стандартных репозиториев Ubuntu, но для актуальной версии Docker Engine и Compose Plugin используем официальный APT-репозиторий Docker.

Сначала создайте каталог для ключей репозиториев: sudo install -m 0755 -d /etc/apt/keyrings

Загрузите официальный GPG-ключ Docker:

sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \

-o /etc/apt/keyrings/docker.asc

Разрешите чтение ключа: sudo chmod a+r /etc/apt/keyrings/docker.asc

Добавьте репозиторий Docker:

echo \

  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \

  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" \

  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

После этого обновите индекс пакетов: sudo apt update

Такой способ установки позволяет получать Docker Engine и связанные компоненты непосредственно из официального репозитория Docker.

Установка Docker Engine и Compose Plugin

Установите Docker Engine, командный интерфейс, containerd, Buildx и Docker Compose Plugin:

sudo apt install -y \

docker-ce \

docker-ce-cli \

containerd.io \

docker-buildx-plugin \

docker-compose-plugin

Пакет docker-compose-plugin добавляет современную команду: docker compose

Она используется без дефиса между словами docker и compose. Отдельная команда docker-compose относится к устаревшему standalone-варианту, тогда как для новых установок рекомендуется Compose Plugin.

После установки Docker обычно запускается автоматически. Проверьте его состояние: sudo systemctl status docker --no-pager

Проверка установленных версий

Проверьте версию Docker Engine: docker --version

Затем проверьте Docker Compose: docker compose version

Убедитесь, что Docker отвечает на команды: sudo docker run --rm hello-world

Команда загрузит небольшой тестовый образ, запустит контейнер и удалит его после завершения.

Настройка автозапуска Docker

В Ubuntu сервис Docker обычно включается в автозагрузку при установке. Дополнительно закрепите его автоматический запуск: sudo systemctl enable --now docker

Проверьте статусы:

systemctl is-active docker

systemctl is-enabled docker

Ожидаемый результат:

active

enabled

На Ubuntu и Debian сервис Docker при штатной установке запускается вместе с системой, однако явная проверка systemctl is-enabled позволяет убедиться в правильной настройке VPS.

По умолчанию обычный пользователь не всегда имеет доступ к Docker socket, поэтому в дальнейших командах можно использовать sudo docker. Для тестового стенда также можно добавить пользователя ubuntu в группу docker: sudo usermod -aG docker ubuntu

Чтобы членство в группе применилось, завершите SSH-сеанс и подключитесь повторно: exit

После нового входа проверьте: docker ps

Если команда выполнилась без ошибки доступа, дальнейшие операции можно запускать без sudo.

Подготовка приложения

В качестве примера создадим небольшое Node.js-приложение. Оно будет возвращать веб-страницу с информацией о том, что контейнер успешно запущен через Docker Compose.

Создание каталога проекта

Создайте каталог приложения: sudo mkdir -p /opt/docker-compose-app

Назначьте текущего пользователя владельцем: sudo chown -R $USER:$USER /opt/docker-compose-app

Перейдите в каталог: cd /opt/docker-compose-app

Все файлы приложения, Dockerfile и конфигурация Compose будут находиться здесь.

Добавление файлов приложения

Создайте файл package.json: nano package.json

Добавьте:

{

  "name": "docker-compose-guide",

  "version": "1.0.0",

  "private": true,

  "description": "Test application deployed with Docker Compose",

  "main": "app.js",

  "scripts": {

    "start": "node app.js"

  },

  "dependencies": {

    "express": "^5.1.0"

  }

}

Сохраните файл: Ctrl+O → Enter → Ctrl+X

Теперь создайте основной файл приложения: nano app.js

Вставьте следующий код:

const express = require('express');

const app = express();

const port = Number.parseInt(process.env.PORT || '3000', 10);

const appName = process.env.APP_NAME || 'Docker Compose App';

app.get('/', (request, response) => {

  response.type('html').send(`

    <!doctype html>

    <html lang="ru">

      <head>

        <meta charset="utf-8">

        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>${appName}</title>

        <style>

          body {

            margin: 0;

            min-height: 100vh;

            display: grid;

            place-items: center;

            font-family: Arial, sans-serif;

            background: #f4f6f8;

            color: #17202a;

          }

          main {

            width: min(680px, calc(100% - 48px));

            padding: 48px;

            border-radius: 20px;

            background: #ffffff;

            box-shadow: 0 18px 50px rgba(0, 0, 0, 0.08);

          }

          code {

            padding: 3px 7px;

            border-radius: 6px;

            background: #eef1f4;

          }

        </style>

      </head>

      <body>

        <main>

          <h1>${appName}</h1>

          <p>Приложение успешно запущено в Docker-контейнере.</p>

          <p>Трафик передаётся через <code>Nginx</code> по защищённому соединению.</p>

        </main>

      </body>

    </html>

  `);

});

app.get('/health', (request, response) => {

  response.json({

    status: 'ok',

    service: appName

  });

});

app.listen(port, '0.0.0.0', () => {

  console.log(`${appName} is listening on port ${port}`);

});

Приложение прослушивает адрес 0.0.0.0 внутри контейнера. Сам порт позднее будет опубликован только на локальном интерфейсе VPS через compose.yaml.

Маршрут /health понадобится для быстрой проверки состояния приложения: http://127.0.0.1:3000/health

Создание Dockerfile

Создайте файл Dockerfile: nano Dockerfile

Добавьте:

FROM node:22-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install --omit=dev

COPY . .

ENV NODE_ENV=production

EXPOSE 3000

CMD ["npm", "start"]

В качестве базового образа используется официальный образ Node.js. Docker рекомендует использовать доверенные официальные образы и исключать из контекста сборки ненужные файлы.

Инструкции Dockerfile выполняются последовательно:

  • FROM выбирает базовый образ;
  • WORKDIR создаёт рабочий каталог контейнера;
  • COPY переносит файлы проекта;
  • RUN устанавливает зависимости;
  • EXPOSE документирует порт приложения;
  • CMD задаёт команду запуска.

Далее перейдем к созданию .dockerignore.

Создание .dockerignore

Создайте файл: nano .dockerignore

Добавьте:

node_modules

npm-debug.log

.git

.gitignore

.env

Dockerfile*

compose*.yaml

README.md

Файл .dockerignore исключает ненужные данные из контекста сборки. Это уменьшает объём передаваемых Docker файлов и предотвращает случайное попадание локальных зависимостей, истории Git и переменных окружения в образ.

Настройка переменных окружения

Создайте файл .env: nano .env

Добавьте:

APP_NAME=Deploy Test Lab

APP_PORT=3000

Переменная APP_NAME будет передана внутрь контейнера, а APP_PORT понадобится в конфигурации Docker Compose.

Закройте доступ к файлу для других пользователей VPS: chmod 600 .env

Не добавляйте .env в Docker-образ и публичный репозиторий. Для рабочего проекта в этом файле могут храниться пароли, токены и другие параметры окружения.

Проверьте структуру каталога: ls -la

На этом этапе в нём должны находиться:

.dockerignore

.env

Dockerfile

app.js

package.json

Файлы приложения подготовлены. Следующим этапом станет создание compose.yaml, настройка локального порта и запуск контейнера.

Создание конфигурации Docker Compose

Описание сервисов в compose.yaml

В корне проекта создайте файл compose.yaml:

cd /opt/docker-compose-app

nano compose.yaml

Добавьте следующую конфигурацию:

services:

  app:

    build:

      context: .

      dockerfile: Dockerfile

    container_name: docker-compose-guide

    restart: unless-stopped

    env_file:

      - .env

    environment:

      PORT: 3000

    ports:

      - "127.0.0.1:${APP_PORT}:3000"

    networks:

      - app-network

networks:

  app-network:

    driver: bridge

Верхнеуровневый раздел services содержит описание контейнеров приложения. В нашем случае используется один сервис app, который собирается из локального Dockerfile. Docker Compose поддерживает описание сервисов, сетей, портов и других параметров в одном конфигурационном файле.

Параметр:

build:

  context: .

  dockerfile: Dockerfile

указывает Docker использовать текущий каталог как контекст сборки и найти в нём файл Dockerfile.

Файл .env подключается через:

env_file:

  - .env

Переменная APP_NAME из него попадёт внутрь контейнера. Дополнительная переменная PORT задаёт порт, который слушает приложение внутри контейнера.

Настройка портов, сети и политики перезапуска

Связка портов выглядит так: 127.0.0.1:${APP_PORT}:3000

Здесь:

  • 127.0.0.1 ограничивает доступ локальным интерфейсом VPS;
  • ${APP_PORT} подставляется из файла .env;
  • 3000 — порт приложения внутри контейнера.

В результате приложение будет доступно на сервере по адресу: http://127.0.0.1:3000

но подключиться к этому порту напрямую из интернета будет нельзя. Позднее Nginx станет передавать на него внешние запросы.

Для сервиса также задана политика перезапуска: restart: unless-stopped

Она перезапускает контейнер после сбоя и запуска Docker, но не поднимает его снова, если контейнер был остановлен вручную. Docker Compose поддерживает политики no, always, on-failure и unless-stopped.

Отдельная bridge-сеть:

networks:

  - app-network

Cоздаёт изолированную сеть проекта. Сейчас в ней работает один контейнер, но позднее в конфигурацию можно добавить базу данных, Redis или другой внутренний сервис.

Проверка конфигурации Docker Compose

Сохраните файл: Ctrl+O → Enter → Ctrl+X

Проверьте итоговую конфигурацию: docker compose config

Команда обрабатывает compose.yaml, подставляет переменные из .env и выводит нормализованную конфигурацию. Если в YAML есть ошибка в отступах, неизвестный параметр или отсутствует обязательное значение, Docker Compose сообщит об этом.

Для более короткой проверки без вывода всей конфигурации можно выполнить: docker compose config --quiet

Если ошибок нет, команда завершится без сообщений. Затем выведите итоговую конфигурацию сервиса: docker compose config

Запуск приложения через Docker Compose

Сборка и запуск контейнеров

Находясь в каталоге проекта, запустите сборку образа и контейнер в фоновом режиме: docker compose up -d --build

Параметр --build принудительно запускает сборку образа перед стартом, а -d оставляет контейнер работать в фоновом режиме. Команда docker compose up создаёт и запускает сервисы, а при изменении конфигурации или образа пересоздаёт связанные контейнеры.

Во время первой сборки Docker:

  1. Загрузит базовый образ node:22-alpine;
  2. Скопирует package.json;
  3. Установит зависимости;
  4. Добавит файлы приложения;
  5. Создаст контейнер;
  6. Подключит его к сети app-network.

После завершения должен появиться статус запуска сервиса без ошибок.

Проверка состояния контейнеров

Посмотрите состояние проекта: docker compose ps

Ожидаемый вывод будет похож на следующий:

NAMEIMAGECOMMAND SERVICESTATUSPORTS
docker-compose-guidedocker-compose-app-app"npm start"appUp 10 seconds127.0.0.1:3000->3000/tcp 

Важно, чтобы:

  • Контейнер имел статус Up;
  • Сервис назывался app;
  • Порт был опубликован как 127.0.0.1:3000->3000/tcp.

При необходимости можно вывести список всех работающих контейнеров: docker ps

Команда docker compose ps показывает контейнеры, относящиеся к текущему Compose-проекту.

Просмотр журналов приложения

Посмотрите последние строки журналов: docker compose logs --tail=50 app

В выводе должно появиться сообщение: Deploy Test Lab is listening on port 3000

Для просмотра журналов в реальном времени используйте: docker compose logs -f app

Параметр -f продолжает выводить новые строки по мере их появления. Чтобы завершить просмотр и не останавливать контейнер, нажмите Ctrl+C.

Если контейнер постоянно перезапускается или получает статус Exited, журналы обычно показывают причину: ошибку JavaScript, отсутствие зависимости, неправильную переменную окружения или занятой порт.

Проверка приложения по локальному порту

Проверьте главную страницу непосредственно на VPS: curl -I http://127.0.0.1:3000

Ожидаемый ответ: HTTP/1.1 200 OK

Затем проверьте маршрут состояния: curl http://127.0.0.1:3000/health

Приложение должно вернуть JSON: {"status":"ok","service":"Deploy Test Lab"}

Дополнительно проверьте, что порт слушает только локальный интерфейс: sudo ss -lntp | grep :3000

В выводе должен отображаться адрес 127.0.0.1:3000 а не 0.0.0.0:3000.

Это подтверждает, что контейнер не доступен напрямую из интернета. Следующим этапом настроим Nginx как reverse proxy и направим внешний трафик на локальный порт приложения.

Настройка Nginx как reverse proxy

Установка Nginx

Установите Nginx из стандартного репозитория Ubuntu: sudo apt install -y nginx

Включите автоматический запуск и сразу запустите сервис: sudo systemctl enable --now nginx

Проверьте его состояние:

systemctl is-active nginx

systemctl is-enabled nginx

Ожидаемый результат:

active

enabled

Nginx будет работать на хостовой системе, принимать внешние запросы и передавать их приложению, опубликованному контейнером на локальном адресе 127.0.0.1:3000. Nginx официально поддерживает работу в качестве HTTP reverse proxy.

Создание server block

Создайте отдельный конфигурационный файл: sudo nano /etc/nginx/sites-available/docker-compose-app

Добавьте следующую конфигурацию:

server {

    listen 80;

    listen [::]:80;

    server_name docker.deploy-test-lab.com;

    access_log /var/log/nginx/docker-compose-app_access.log;

    error_log /var/log/nginx/docker-compose-app_error.log;

    location / {

        proxy_pass http://127.0.0.1:3000;

        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;

    }

}

В директиве server_name указывается домен или поддомен приложения. В нашем случае используется: docker.deploy-test-lab.com

При использовании другого домена замените его в этом файле, DNS-записи и последующей команде Certbot.

Сохраните файл: Ctrl+O → Enter → Ctrl+X

Активируйте server block:

sudo ln -s /etc/nginx/sites-available/docker-compose-app \

/etc/nginx/sites-enabled/docker-compose-app

Отключите стандартную конфигурацию Nginx: sudo rm -f /etc/nginx/sites-enabled/default

Передача запросов в Docker-контейнер

Основную передачу запросов выполняет директива: proxy_pass http://127.0.0.1:3000;

Она направляет запросы из блока location / на локальный порт приложения. Поскольку Docker Compose опубликовал порт только на 127.0.0.1, контейнер недоступен напрямую из интернета. Подключение к нему выполняет только Nginx. Поведение proxy_pass и передача запросов проксируемому серверу описаны в официальной документации модуля Nginx.

Заголовок Host сохраняет доменное имя, по которому обратился пользователь: 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;

Это позволяет приложению получить IP пользователя и понять, использовался ли HTTP или HTTPS.

Перед проверкой Nginx убедитесь, что контейнер отвечает локально: curl http://127.0.0.1:3000/health

Ожидаемый ответ: {"status":"ok","service":"Deploy Test Lab"}.

Проверка конфигурации Nginx

Проверьте синтаксис: sudo nginx -t

При корректной конфигурации появится результат:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

nginx: configuration file /etc/nginx/nginx.conf test is successful

Примените изменения без остановки веб-сервера: sudo systemctl reload nginx

Затем проверьте server block локально:

curl -I \

-H "Host: docker.deploy-test-lab.com" \

http://127.0.0.1

В ответе должен появиться код: HTTP/1.1 200 OK

Также можно проверить маршрут состояния через Nginx:

curl \

-H "Host: docker.deploy-test-lab.com" \

http://127.0.0.1/health

Если Nginx возвращает JSON приложения, reverse proxy работает корректно.

Подключение домена и SSL

Создание DNS-записи

В DNS-зоне домена создайте запись типа A со следующими параметрами:

Параметр Значение 
Тип 
Имя docker 
IPv4-адрес 203.0.113.10 
TTL Auto или значение по умолчанию 

Адрес 203.0.113.10 используется в нашем тестовом стенде. В своей конфигурации укажите Floating IP, назначенный вашей виртуальной машине.

Если DNS обслуживается через Cloudflare, для первоначальной настройки можно использовать режим DNS only, чтобы домен напрямую указывал на VPS.

После сохранения проверьте DNS с локального компьютера: nslookup docker.deploy-test-lab.com

В ответе должен отображаться IP VPS: 203.0.113.10

Также проверьте приложение по HTTP: http://docker.deploy-test-lab.com

До выпуска сертификата оно должно открываться без HTTPS.

Можно выполнить проверку через терминал: curl -I http://docker.deploy-test-lab.com

Не переходите к Certbot, пока домен не начнёт возвращать правильный IP и приложение не станет доступно по HTTP.

Выпуск сертификата Let’s Encrypt

Установите Certbot через Snap: sudo snap install --classic certbot

Создайте ссылку на исполняемый файл: sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

Если ссылка уже существует, повторно создавать её не требуется.

Запросите сертификат для поддомена:

sudo certbot --nginx \

-d docker.deploy-test-lab.com

Во время запуска Certbot предложит:

  • Указать email;
  • Принять условия использования;
  • Выбрать участие в рассылке;
  • Подтвердить изменение конфигурации Nginx.

Плагин Nginx может автоматически получить сертификат, добавить HTTPS-настройки и применить их к существующему HTTP-сайту.

Для проверки домена через HTTP-01 сервер должен быть доступен извне по порту 80. Let’s Encrypt рекомендует не закрывать его и перенаправлять обычные HTTP-запросы на HTTPS.

После успешного выпуска сертификата повторно проверьте Nginx: sudo nginx -t

Затем выполните тест автоматического обновления: sudo certbot renew --dry-run

Если проверка завершается успешно, сертификат сможет продлеваться автоматически.

Настройка перенаправления с HTTP на HTTPS

После работы Certbot в конфигурации Nginx появится отдельный блок для HTTPS и правило перенаправления с порта 80.

Проверьте HTTP-адрес: curl -I http://docker.deploy-test-lab.com

Ожидаемый ответ:

HTTP/1.1 301 Moved Permanently

Location: https://docker.deploy-test-lab.com/

Это означает, что незашифрованные запросы автоматически направляются на защищённую версию сайта.

Не удаляйте входящее правило для TCP-порта 80. Оно понадобится для HTTP-переадресации и может использоваться при последующих проверках владения доменом.

Проверка защищённого соединения

Откройте приложение в браузере: https://docker.deploy-test-lab.com

Должна загрузиться страница тестового приложения с сообщением:

Приложение успешно запущено в Docker-контейнере.

Проверьте HTTPS через терминал: curl -I https://docker.deploy-test-lab.com

Сервер должен вернуть успешный ответ: HTTP/2 200

Проверьте маршрут состояния: curl https://docker.deploy-test-lab.com/health

Ожидаемый результат: {"status":"ok","service":"Deploy Test Lab"}

На этом этапе внешний трафик проходит по HTTPS через Nginx, а само приложение продолжает работать внутри Docker-контейнера на локальном порту VPS.

Обновление приложения

Получение новой версии файлов

Способ обновления зависит от того, как исходный код попадает на VPS. Если проект хранится в Git-репозитории, перейдите в каталог приложения и получите последнюю версию:

cd /opt/docker-compose-app

git pull

Перед обновлением убедитесь, что в каталоге нет несохранённых локальных изменений: git status

Если проект передаётся архивом или отдельными файлами, сначала замените исходный код, package.json, Dockerfile и другие изменившиеся элементы, но не удаляйте .env и постоянные данные приложения.

После обновления проверьте конфигурацию Compose: docker compose config --quiet

Отсутствие вывода означает, что файл compose.yaml успешно обработан.

Повторная сборка контейнеров

Если изменился исходный код, зависимости или Dockerfile, пересоберите образ и запустите обновлённый контейнер: docker compose up -d --build

Compose сравнит текущую конфигурацию с уже запущенным проектом и при необходимости пересоздаст контейнер. Для нашего приложения этого достаточно: образ собирается из локального каталога, а сервис запускается в фоновом режиме.

После завершения проверьте состояние: docker compose ps

И последние строки журнала: docker compose logs --tail=50 app

Если в compose.yaml используются готовые образы из реестра, перед запуском новой версии можно выполнить:

docker compose pull

docker compose up -d

Команда docker compose pull загружает актуальные образы, указанные для сервисов.

Перезапуск без удаления постоянных данных

Для обычного обновления не требуется выполнять: docker compose down -v

Параметр -v удаляет именованные и анонимные тома проекта, поэтому вместе с ними могут исчезнуть базы данных, загруженные файлы и другие постоянные данные.

Безопасный вариант обновления: docker compose up -d --build

Если нужно только перезапустить уже созданный контейнер без пересборки: docker compose restart app

Однако docker compose restart не применяет изменения, внесённые в compose.yaml. После изменения конфигурации следует использовать docker compose up -d, чтобы Compose пересоздал сервис с новыми параметрами.

В текущем демонстрационном приложении тома не используются, но этот принцип особенно важен для проектов с PostgreSQL, MySQL, Redis или если сервис использует пользовательские загрузки.

Очистка неиспользуемых образов

После нескольких обновлений на VPS могут остаться старые слои и образы. Посмотреть их можно командой: docker image ls

Удалите неиспользуемые промежуточные образы: docker image prune

Docker запросит подтверждение. Для выполнения без дополнительного вопроса используется: docker image prune -f

Команда удаляет неиспользуемые dangling images, но не затрагивает образы, необходимые запущенным контейнерам.

Более агрессивная очистка: docker image prune -a

удаляет все образы, которые не связаны с существующими контейнерами. На рабочем сервере её следует применять осторожно, поскольку при следующем запуске часть образов придётся загружать или собирать заново.

Проверка после развёртывания

Проверка контейнеров и сервиса Docker

Перейдите в каталог проекта: cd /opt/docker-compose-app

Проверьте состояние Docker:

systemctl is-active docker

systemctl is-enabled docker

Ожидаемый результат:

active

enabled

Затем проверьте контейнер: docker compose ps

Сервис app должен иметь статус Up, а порт — быть опубликован только на локальном интерфейсе: 127.0.0.1:3000->3000/tcp

Дополнительно можно проверить ресурсы контейнера: docker stats --no-stream

Команда покажет использование процессора, оперативной памяти и сети на момент проверки.

Проверка Nginx и HTTPS

Проверьте состояние Nginx:

systemctl is-active nginx

systemctl is-enabled nginx

Затем проверьте конфигурацию: sudo nginx -t

Убедитесь, что HTTP перенаправляется на HTTPS: curl -I http://docker.deploy-test-lab.com

В ответе должен присутствовать заголовок: Location: https://docker.deploy-test-lab.com/

Проверьте защищённую версию: curl -I https://docker.deploy-test-lab.com

Ожидаемый результат: HTTP/2 200

Маршрут состояния приложения также должен быть доступен через Nginx: curl https://docker.deploy-test-lab.com/health

Ожидаемый ответ: {"status":"ok","service":"Deploy Test Lab"}.

Проверка журналов приложения

Посмотрите последние сообщения контейнера: docker compose logs --tail=50 app

В журнале не должно быть циклических перезапусков, необработанных исключений или сообщений о занятом порте.

Для просмотра новых событий в реальном времени используйте: docker compose logs -f app

Остановить просмотр можно сочетанием Ctrl+C. Контейнер при этом продолжит работать. Docker Compose предоставляет отдельные команды для просмотра журналов и состояния сервисов проекта.

При проблемах с reverse proxy дополнительно проверьте журналы Nginx: sudo tail -n 50 /var/log/nginx/docker-compose-app_error.log

Проверка автозапуска после reboot

Убедитесь, что сервисы включены в автозагрузку:

systemctl is-enabled docker

systemctl is-enabled nginx

Для контейнера в compose.yaml должна использоваться политика: restart: unless-stopped

Она позволяет Docker автоматически запускать контейнер после перезапуска демона или VPS, если контейнер ранее не был остановлен вручную.

Перезагрузите сервер: sudo reboot

SSH-соединение будет закрыто. Через минуту подключитесь повторно: ssh -i .\docker-compose-guide.pem ubuntu@203.0.113.10

После входа выполните:

cd /opt/docker-compose-app

printf "docker: "

systemctl is-active docker

printf "nginx: "

systemctl is-active nginx

docker compose ps

В выводе должны отображаться статусы active, а контейнер приложения должен снова иметь состояние Up.

После этого повторно проверьте маршрут состояния: curl https://docker.deploy-test-lab.com/health

Если сервер возвращает JSON со статусом ok, Docker, контейнер, Nginx и HTTPS успешно восстановили работу после reboot.

Итоговая проверка приложения

Откройте в браузере: https://docker.deploy-test-lab.com

На странице должны отображаться название Deploy Test Lab и сообщение о том, что приложение запущено в Docker-контейнере, а трафик передаётся через Nginx.

На этом развёртывание можно считать завершённым. Контейнер запускается через Docker Compose, внутренний порт недоступен напрямую из интернета, внешний трафик обслуживает Nginx, а соединение защищено сертификатом Let’s Encrypt.

Заключение

В результате на VPS с Ubuntu 24.04 LTS развёрнуто веб-приложение, упакованное в Docker-контейнер и управляемое через Docker Compose. Конфигурация проекта хранится в compose.yaml, поэтому приложение можно запускать, останавливать, пересобирать и обновлять стандартными командами Compose.

Контейнер принимает запросы только через локальный адрес 127.0.0.1:3000 и не доступен напрямую из интернета. Внешний трафик обслуживает Nginx, который работает как reverse proxy, принимает запросы по доменному имени и передаёт их приложению. HTTPS обеспечивается сертификатом Let’s Encrypt, а HTTP-запросы автоматически перенаправляются на защищённую версию сайта.

Политика restart: unless-stopped и автозапуск Docker позволяют восстановить работу контейнера после перезагрузки VPS. Для выпуска новой версии достаточно обновить файлы проекта и повторно выполнить docker compose up -d --build.

Такая схема подходит для небольших веб-приложений, API, административных панелей и внутренних сервисов. При усложнении проекта в compose.yaml можно добавить базу данных, Redis, очереди задач и другие контейнеры, сохранив единый способ управления всем стеком.

FAQ

Чем Docker Compose отличается от обычного запуска Docker-контейнера?

При обычном запуске параметры контейнера передаются длинной командой docker run. Docker Compose хранит сервисы, порты, сети, переменные окружения и политики перезапуска в файле compose.yaml.

Благодаря этому конфигурацию проще повторно использовать, обновлять и переносить на другой VPS.

Можно ли разместить несколько контейнеров в одном compose.yaml?

Да. В разделе services можно описать приложение, базу данных, Redis, очередь задач и другие компоненты.

Сервисы внутри одной Compose-сети могут обращаться друг к другу по именам, указанным в конфигурации.

Нужно ли устанавливать Nginx в отдельный контейнер?

Нет. В этом руководстве Nginx установлен непосредственно на VPS. Такой вариант упрощает выпуск сертификатов Certbot и позволяет использовать один reverse proxy для нескольких контейнерных приложений.

При необходимости и для переносимости Nginx тоже можно запустить в Docker Compose, но тогда конфигурация сертификатов, портов и томов станет сложнее.

Как обновить приложение после изменения кода?

Перейдите в каталог проекта и выполните: docker compose up -d --build

Docker пересоберёт образ и пересоздаст контейнер с новой версией приложения. После обновления следует проверить docker compose ps и журналы сервиса.

Удаляет ли docker compose down данные приложения?

Команда docker compose down удаляет контейнеры и сеть проекта, но не удаляет именованные тома без дополнительного параметра.

Команда: docker compose down -v

удаляет и тома. Для проекта с базой данных или пользовательскими файлами это может привести к потере постоянных данных.

Что произойдёт после перезагрузки VPS?

Сервис Docker и Nginx запустятся через systemd. Контейнер приложения также будет поднят автоматически благодаря политике: restart: unless-stopped

Исключением будет контейнер, который был остановлен вручную до перезагрузки.

Подходит ли Docker Compose для production?

Docker Compose можно использовать для развёртывания приложений на одном сервере, в том числе в production. Для рабочего проекта дополнительно следует настроить резервное копирование, мониторинг, ограничения ресурсов, безопасное хранение секретов и регулярное обновление образов.

Список источников

  1. Docker Docs — Install Docker Engine on Ubuntu
  2. Docker Docs — Compose file reference
  3. NGINX Documentation — ngx_http_proxy_module
  4. Certbot — Nginx instructions

Подпишитесь на нашу рассылку и получайте статьи и новости

    Ознакомьтесь с другими нашими материалами