В данной статье будет подробно рассмотрен полный цикл деплоя сайта, используюшего Vite в качестве инструмента сборки. Мы напишем многоступенчатый Dockerfile, чтобы собрать образ с готовой статикой, и поднимем контейнер с сервером Caddy, который автоматически выдаст и будет обновлять сертификаты Let’s Encrypt.
Нам понадобится
-
Арендованный или домашний сервер на Ubuntu с публичным IP
-
Домен, привязанный к IP
-
Docker
-
Готовый проект с vite
-
Терминал
Структура проекта
Подразумевается, что у вас уже есть проект использующий сборщик Vite, в качестве примера будет показан простой сайт с документацией на VitePress, конфигурация для docker и caddy находится в корневой папке проекта
root├── Caddyfile├── Dockerfile├── docker-compose.yaml├── docs│ ├── index.md│ └── .vitepress├── package-lock.json└── package.json
Сборка проекта
При выполнении команды npm run build создается папка /dist, содержащая файл index.html и плоскую структуру каталога /assets с файлами .js и .css. Сервер Node.js здесь не задействован — на выходе получаются исключительно статические файлы.
Это важно для Docker в продуктовой среде Node.js не требуется. Нужен только сервер для раздачи статических файлов. В качестве сервера в данном примере выбран Caddy — современный веб-сервер, написанный на Go, который распространяется как единый статически скомпилированный бинарник без внешних зависимостей.
Ключевые преимущества
-
Автоматический HTTPS — Caddy получает и обновляет сетрификаты Let’s Encrypt по протоколу ACME прямо из коробки, без необходимости настраивать Certbot или монтировать volume с сертификатами, Для продуктовой среды это означает, что достаточно указать домен в Caddyfile, и сервер сам позабоитстя о шифровании.
-
Простая конфигурация — синтаксис Caddyfile интуитивно понятен и лаконичен. Для раздачи статики из /dist нужна всего одна строча: root * /usr/share/caddy + file_server. В отличии от Nginx, где для базового кейса требуется 10-15 строк с location, alias, try_files и типами mime.
Из минусов можно отметить больший оверхед по ресурсам (из-за рантайма Go), но в контексте раздачи статических файлов разница в 5-10% производительности незаметна.
Конфигурируем Caddy
Ниже приведен пример конфигурации для сервера, с настройками домена, TLS и кэширования
# Привязанный домен, берется из .env файла конфигурации{$DOMAIN} { # Почта для получения сертификатов Let's Encrypt tls {$EMAIL} # Указывает, где находятся файлы сайта # * означает, что правило применяется ко всем запросам # Путь /usr/share/caddy — стандартная папка для статики в Caddy root * /usr/share/caddy # Включает сервер для отдачи файлов из корневой директории # Автоматически определяет MIME-типы file_server # Включает GZIP-сжатие для ответов сервера encode gzip # Устанавливает заголовки кеширования для статических ресурсов: header /assets/* Cache-Control: "public, max-age32536000, immutable" header /*.css Cache-Control: "public, max-age=31536000, immutable" header /*.js Cache-Control: "public, max-age=31536000, immutable" # Попытка найти файл → если нет, то папку → если ничего нет, отдать index.html try_files {path} {path}/ /index.html # Заголовки безопасности header { # Требует HTTPS в течение 2 лет (63,072,000 сек) Strict-Transport-Security "max-age=63072000" # Запрещает браузеру определять MIME-тип (защита от MIME-атак) X-Content-Type-Options "nosniff" # Включает защиту от XSS-атак X-XSS-Protection "1; mode=block" # Запрещает встраивание сайта во frame/iframe (защита от clickjacking) X-Frame-Options "DENY" }}
Конфигурируем Dockerfile
# Сборка сайта с использованием Node.jsFROM node:24-slim AS builderWORKDIR /app# Установка зависимостей (кешируется, если не менялся package.json)COPY package*.json ./RUN npm ci# Копирование исходников и сборкаCOPY docs ./docsRUN npm run docs:build # Результат: /app/docs/.vitepress/dist# Финальный образ Alpine + CaddyFROM alpine:3.23.5# Установка веб-сервера CaddyRUN apk add --no-cache caddy# Копируем собранный сайт из первого этапа в стандартную папку CaddyCOPY --from=builder /app/docs/.vitepress/dist /usr/share/caddy# Копируем конфиг CaddyCOPY Caddyfile /etc/caddy/Caddyfile# Открываем порты HTTP/HTTPSEXPOSE 80 443# Запуск CaddyCMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
Конфигурируем docker-compose.yml
version: "3.8"services:# Название сервиса (может быть любым)caddy: build: # Dockerfile находится в текущей директории (.) context: . # Используем файл с именем Dockerfile для сборки образа dockerfile: Dockerfile container_name: vitepress-site # Политика перезапуска: # - контейнер будет автоматически перезапускаться, если упадёт # - не будет перезапускаться, только если остановлен вручную restart: unless-stopped ports: # Открываем порт 80 (HTTP) - "80:80" # Открываем порт 443 (HTTPS) - "443:443" volumes: # Том для хранения данных Caddy (сертификаты, база данных) - caddy_data:/data # Том для конфигурации Caddy - caddy_config:/config environment: # Передаём переменную окружения DOMAIN из .env файла - DOMAIN=${DOMAIN} # Передаём переменную окружения EMAIL из .env файла - EMAIL=${EMAIL} networks: - webvolumes: caddy_data: caddy_config:networks: web: # Создаём сеть с драйвером bridge (стандартная сеть Docker) driver: bridge
Файл с переменными .env
Перед запуском docker compose в корне проекта нужно создать .env файл с переменными, docker автоматически подтянет их при запуске.
DOMAIN=ваш доменEMAIL=ваша почта
Перенос кода на сервер
Легче всего доставить готовый проект можно с использованием системы контроля версий(GitHub, GitLab, Bitbucket)
На удаленной машине установите git(если отсутствует)
sudo apt update && sudo apt install git -y # Для Ubuntu/Debian
Проверить можно следующей командой
git --version # git version 2.50.1
Склонируйте проект и перейдите в корневую папку
git clone http://github.com/user/site.git && \ cd site
Запуск
-
Подключаемся к серверу по ssh
-
В корне проекта выполняем команду
docker compose up --build -d
Проверить работоспособность можно со своего компьютера через curl
curl domain.com
Итог
Это решение представляет собой полноценную конфигурацию на базе Docker для развертывания React-приложений (собранных с помощью Vite). Оно включает многоэтапную сборку с использованием Node и caddy, автоматическую настройку TLS и правильные заголовки кэширования. Надеюсь данная статья оказалась полезной для вас.
ссылка на оригинал статьи https://habr.com/ru/articles/1065978/