Розгортання Caddy як зворотного проксі для Docker-контейнерів на VPS з автоматичним HTTPS
TL;DR
У цьому детальному посібнику ми налаштуємо Caddy як високоефективний зворотний проксі-сервер для ваших Docker-контейнерів на віртуальному приватному сервері (VPS), забезпечивши автоматичне отримання та оновлення SSL/TLS-сертифікатів через Let's Encrypt. Ви дізнаєтеся, як розгорнути та зв'язати між собою Docker, Docker Compose і Caddy для забезпечення безпечного та масштабованого доступу до ваших веб-додатків, що працюють у контейнерах, з мінімальними зусиллями з конфігурування.
- Встановлення та базове налаштування операційної системи (Ubuntu 24.04/26.04 LTS).
- Розгортання Docker та Docker Compose для керування контейнерами.
- Встановлення та налаштування Caddy як зворотного проксі.
- Конфігурація Caddy для автоматичної видачі HTTPS-сертифікатів.
- Приклад розгортання програми в Docker та її проксіювання через Caddy.
- Рекомендації щодо безпеки, резервного копіювання та обслуговування системи.
Що ми налаштовуємо і навіщо
У цьому посібнику ми займемося створенням надійної та безпечної інфраструктури для хостингу веб-додатків на власному VPS. Основне завдання — забезпечити доступ до додатків, що працюють всередині Docker-контейнерів, ззовні через доменне ім'я, при цьому автоматично захистивши з'єднання за допомогою HTTPS. Для цього ми використовуємо зв'язку з трьох ключових компонентів:
- Docker: Платформа для контейнеризації додатків. Docker дозволяє упаковувати додатки з усіма їхніми залежностями в ізольовані "контейнери", що забезпечує їхню переносимість та передбачуваність роботи в будь-якому середовищі. Ми будемо використовувати Docker Compose для оркестрації кількох контейнерів, що складають один додаток або сервіс.
- Caddy: Сучасний веб-сервер та зворотний проксі. Caddy виділяється своєю простотою конфігурації і, що найголовніше, вбудованою підтримкою автоматичної видачі та оновлення SSL/TLS-сертифікатів через Let's Encrypt. Це позбавляє від необхідності вручну налаштовувати Certbot або інші інструменти для HTTPS, значно спрощуючи процес.
- VPS (Virtual Private Server): Віртуальний приватний сервер, який надасть нам необхідну обчислювальну потужність та мережеві ресурси для розміщення нашої інфраструктури.
У підсумку, читач отримає повністю налаштовану систему, здатну безпечно та ефективно розміщувати один або кілька веб-додатків (наприклад, GitLab, Mattermost, WordPress, або кастомні SaaS-додатки) у Docker-контейнерах, доступних за доменними іменами з автоматичним HTTPS. Це ідеальне рішення для розробників, стартапів та ентузіастів, які бажають мати повний контроль над своєю інфраструктурою без високих витрат на хмарні managed-сервіси.
Альтернативи: Cloud-managed vs. Self-hosted на VPS
Існує два основні підходи до розгортання веб-додатків:
-
Cloud-managed сервіси: Такі платформи, як Heroku, Netlify, AWS Elastic Beanstalk, Google App Engine, пропонують високу автоматизацію, масштабованість та мінімальні зусилля з адміністрування. Ви просто завантажуєте свій код, а платформа дбає про сервери, масштабування, бази даних та HTTPS.
Переваги: Простота, висока доступність, автоматичне масштабування.
Недоліки: Висока вартість при зростанні, менший контроль над інфраструктурою, прив'язка до конкретного провайдера, іноді обмежені можливості кастомізації. -
Self-hosted на VPS/Dedicated сервері: Цей підхід передбачає оренду "голого" сервера (віртуального або фізичного) та самостійне налаштування всього стеку — операційної системи, веб-сервера, бази даних, контейнеризації тощо.
Переваги: Повний контроль над кожним аспектом системи, значно нижча вартість у довгостроковій перспективі (особливо при помірних навантаженнях), гнучкість у виборі технологій та конфігурацій, конфіденційність даних.
Недоліки: Вимагає технічних знань та часу на налаштування й обслуговування, відповідальність за безпеку та резервне копіювання лежить на вас.
Вибір self-hosted на VPS з Caddy та Docker є золотою серединою для тих, хто шукає баланс між контролем, вартістю та простотою використання. Caddy значно знижує бар'єр входу для самостійного налаштування HTTPS, а Docker спрощує керування додатками.
Який VPS-конфіг потрібен для цього завдання
Вибір відповідного VPS-конфігу критичний для стабільної роботи ваших додатків. Вимоги залежатимуть від кількості та ресурсоємності Docker-контейнерів, які ви плануєте запускати.
Мінімальні вимоги для базової установки (Caddy + 1-2 легких контейнера)
- CPU: 1-2 ядра (наприклад, Intel Xeon E3/E5 або AMD EPYC). Для більшості веб-додатків одного ядра достатньо, але два дадуть запас продуктивності та кращу чуйність.
- RAM: 2 ГБ. Docker та Caddy самі по собі споживають небагато, але кожен додаток у контейнері вимагає своєї пам'яті. 2 ГБ — це комфортний мінімум для ОС, Docker-демона та пари невимогливих сервісів (наприклад, невеликий блог на WordPress без важкої СУБД або простий API-сервіс).
- Диск: 40-60 ГБ NVMe SSD. SSD значно прискорює операції введення-виведення, що важливо для баз даних та швидкого запуску контейнерів. NVMe пропонує ще більшу швидкість. 40-60 ГБ достатньо для ОС, Docker-образів, контейнерів та невеликого обсягу даних. Якщо плануються великі обсяги даних (наприклад, файлові сховища, логи, резервні копії), знадобиться більше місця.
- Мережа: 100-200 Мбіт/с. Для більшості завдань цього більш ніж достатньо. Важливіша стабільність каналу та низька затримка.
Конкретний VPS-план для завдання
Для розгортання Caddy з 3-5 Docker-контейнерами (наприклад, GitLab, Mattermost або кілька мікросервісів) рекомендується наступний конфігураційний план:
- CPU: 2-4 ядра (наприклад, Intel Xeon E5-2690v4 або AMD EPYC 7002 series). Це забезпечить достатню продуктивність для обробки запитів та фонових завдань контейнерів.
- RAM: 4-8 ГБ. Для більш вимогливих додатків, таких як GitLab, Mattermost або Minecraft-сервер, 4 ГБ — це мінімум, 8 ГБ дадуть значно більше свободи та стабільності.
- Диск: 80-160 ГБ NVMe SSD. GitLab, Minecraft та інші подібні додатки можуть займати значне місце на диску. NVMe SSD забезпечить високу швидкість роботи з даними.
- Мережа: 500 Мбіт/с - 1 Гбіт/с. Висока пропускна здатність буде корисна для додатків з великою кількістю користувачів або інтенсивним трафіком.
Можна розглянути VPS із зазначеними характеристиками, щоб отримати оптимальне співвідношення ціни та продуктивності для такого завдання. Переконайтеся, що обраний план включає публічну IPv4-адресу, яка необхідна для доступу до ваших сервісів з інтернету та роботи Let's Encrypt.
Коли потрібен dedicated, а не VPS
Виділений сервер (dedicated server) стає кращим вибором, коли:
- Висока продуктивність та стабільність: Потрібна максимальна та передбачувана продуктивність без "шумних сусідів" на одному фізичному сервері.
- Великі обсяги даних: Планується зберігання сотень гігабайт або терабайт даних.
- Складні обчислення: Потрібні спеціалізовані процесори (наприклад, GPU для машинного навчання) або дуже велика кількість ядер.
- Суворі вимоги до безпеки/комплаєнсу: Необхідний повний фізичний контроль над обладнанням.
- Масштабний трафік: Очікується дуже високий мережевий трафік (десятки ТБ на місяць).
Для більшості завдань, описаних у цьому посібнику (GitLab для команди, Mattermost, Minecraft для друзів), потужний VPS буде достатнім. Однак, якщо ви запускаєте великий SaaS-проект з тисячами користувачів або високонавантажену криптоноду, розгляньте відповідний dedicated сервер.
Локація: на що впливає
Вибір локації VPS має кілька ключових аспектів:
- Затримка (Latency): Чим ближче сервер до вашої основної аудиторії, тим нижчою буде затримка і швидше завантажуватимуться сторінки для кінцевих користувачів. Для європейської аудиторії вибирайте сервер у Європі, для американської — у Північній Америці.
- Регуляторні вимоги: У деяких юрисдикціях існують суворі закони щодо зберігання даних (наприклад, GDPR в ЄС). Вибір локації сервера може впливати на відповідність цим вимогам.
- Вартість: Ціни на VPS можуть незначно відрізнятися залежно від регіону.
Для більшості завдань, якщо ваша аудиторія розподілена, вибирайте локацію, яка знаходиться географічно близько до вас або до вашої основної групи користувачів.
Підготовка сервера
Після отримання доступу до вашого нового VPS необхідно виконати низку початкових налаштувань для забезпечення безпеки та зручності роботи. Ми будемо використовувати Ubuntu Server 24.04/26.04 LTS як операційну систему.
Передбачається, що ви отримали доступ до сервера по SSH під користувачем root або іншим користувачем з правами sudo.
1. Оновлення системи
Насамперед оновимо всі пакети до актуальних версій.
sudo apt update # Оновлюємо списки доступних пакетів
sudo apt upgrade -y # Оновлюємо всі встановлені пакети
sudo apt autoremove -y # Видаляємо непотрібні пакети
2. Створення нового користувача з правами sudo (якщо ви працюєте під root)
Робота під користувачем root небезпечна. Створимо нового користувача та надамо йому права sudo.
sudo adduser username # Замініть 'username' на бажане ім'я користувача. Дотримуйтесь інструкцій для створення пароля.
sudo usermod -aG sudo username # Додаємо користувача до групи sudo для отримання прав адміністратора.
Тепер вийдіть з поточної SSH-сесії (exit) та увійдіть під новим користувачем:
ssh username@your_vps_ip # Замініть 'username' та 'your_vps_ip'
3. Налаштування SSH-ключів для безпечного доступу
Використання SSH-ключів набагато безпечніше, ніж паролів. Якщо у вас ще немає SSH-ключа, згенеруйте його на вашій локальній машині:
ssh-keygen -t ed25519 -C "[email protected]" # Створюємо новий SSH-ключ (ed25519 безпечніший)
Потім скопіюйте публічний ключ на ваш VPS:
ssh-copy-id username@your_vps_ip # Копіюємо публічний ключ на сервер
Тепер ви можете входити на сервер без пароля. Після перевірки роботи SSH-ключів рекомендується відключити вхід за паролем для root та вашого нового користувача, а також заборонити вхід для root.
sudo nano /etc/ssh/sshd_config # Відкриваємо конфігураційний файл SSH-сервера
Знайдіть та змініть наступні рядки:
PermitRootLogin no
PasswordAuthentication no
Збережіть зміни (Ctrl+X, Y, Enter) та перезапустіть SSH-сервер:
sudo systemctl restart sshd # Перезапускаємо службу SSH
Важливо: Переконайтеся, що ви можете увійти за SSH-ключем, перш ніж відключати вхід за паролем! Інакше ви можете втратити доступ до сервера.
4. Встановлення та налаштування міжмережевого екрана (UFW)
UFW (Uncomplicated Firewall) — це простий у використанні інтерфейс для налаштування iptables. Він необхідний для обмеження доступу до сервера лише за необхідними портами.
sudo apt install ufw -y # Встановлюємо UFW
sudo ufw allow OpenSSH # Дозволяємо SSH (порт 22)
sudo ufw allow http # Дозволяємо HTTP (порт 80)
sudo ufw allow https # Дозволяємо HTTPS (порт 443)
sudo ufw enable # Вмикаємо UFW. Підтвердіть 'y'.
sudo ufw status verbose # Перевіряємо статус UFW
5. Встановлення Fail2ban для захисту від брутфорсу
Fail2ban сканує логи сервісів (SSH, веб-серверів тощо) на предмет підозрілої активності (наприклад, багаторазові невдалі спроби входу) та тимчасово або перманентно блокує IP-адреси зловмисників за допомогою правил фаєрволу.
sudo apt install fail2ban -y # Встановлюємо Fail2ban
sudo systemctl enable fail2ban # Вмикаємо автозапуск Fail2ban під час завантаження
sudo systemctl start fail2ban # Запускаємо службу Fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Створюємо локальну копію конфігурації для змін
sudo nano /etc/fail2ban/jail.local # Редагуємо конфігурацію
У файлі jail.local ви можете налаштувати параметри, такі як bantime (час блокування), findtime (період, протягом якого враховуються невдалі спроби) та maxretry (максимальна кількість спроб). Переконайтеся, що секція [sshd] увімкнена (enabled = true). Можете збільшити bantime, наприклад, до 1h або навіть 1d.
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
Збережіть зміни та перезапустіть Fail2ban:
sudo systemctl restart fail2ban # Перезапускаємо Fail2ban для застосування змін
sudo fail2ban-client status sshd # Перевіряємо статус захисту SSH
Встановлення ПЗ — покроково
Тепер, коли сервер підготовлений, перейдемо до встановлення основного програмного забезпечення: Docker, Docker Compose та Caddy.
1. Встановлення Docker Engine (актуально на 2026 рік)
Ми будемо встановлювати Docker з офіційного репозиторію Docker, щоб завжди мати актуальні версії. Актуальні версії Docker Engine на 2026 рік будуть, ймовірно, в діапазоні 25.x-26.x.
# Оновлюємо пакети та встановлюємо залежності
sudo apt update && sudo apt install -y ca-certificates curl gnupg lsb-release
# Додаємо офіційний GPG ключ Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Додаємо репозиторій Docker до джерел APT
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Оновлюємо списки пакетів з новим репозиторієм
sudo apt update
# Встановлюємо Docker Engine, Docker CLI та containerd
sudo apt install -y docker-ce docker-ce-cli containerd.io
# Додаємо поточного користувача до групи docker для роботи без sudo
sudo usermod -aG docker "$USER"
# Застосовуємо зміни для групи Docker (потрібно перелогінитися або перезавантажити)
# Але краще просто перелогінитися або виконати 'newgrp docker'
newgrp docker
# Перевіряємо встановлення Docker
docker run hello-world
Команда docker run hello-world повинна успішно завантажити та запустити тестовий контейнер, підтверджуючи коректне встановлення Docker.
2. Встановлення Docker Compose
Docker Compose — це інструмент для визначення та запуску багатоконтейнерних Docker-додатків. Він постачається як частина Docker Engine з версії 2.x.
# Перевіряємо версію Docker Compose (Docker Compose v2.x постачається як плагін Docker CLI)
docker compose version
Якщо команда docker compose version повертає помилку або дуже стару версію, можливо, вам знадобиться встановити його окремо. Однак, для Ubuntu 24.04/26.04 та Docker Engine 25.x+ він має бути доступний за замовчуванням.
3. Встановлення Caddy (актуально на 2026 рік)
Caddy можна встановити з офіційного репозиторію Caddy, що гарантує отримання останніх стабільних версій (Caddy 2.x).
# Встановлюємо залежності для додавання репозиторію HTTPS
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
# Додаємо GPG ключ Caddy
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
# Додаємо репозиторій Caddy до джерел APT
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Оновлюємо списки пакетів з новим репозиторієм
sudo apt update
# Встановлюємо Caddy
sudo apt install -y caddy
# Перевіряємо статус служби Caddy
sudo systemctl status caddy
Служба Caddy має бути встановлена та запущена. Якщо вона не запущена, запустіть її: sudo systemctl start caddy.
Конфігурація
Тепер, коли всі компоненти встановлені, настав час налаштувати їхню взаємодію. Ми налаштуємо Caddy як зворотний проксі для Docker-контейнерів, використовуючи Docker Compose для визначення наших застосунків.
1. Підготовка мережевої інфраструктури Docker
Для того щоб Caddy міг взаємодіяти з вашими Docker-контейнерами за їхніми внутрішніми іменами, їх потрібно помістити в одну користувацьку мережу Docker.
# Створюємо користувацьку мережу Docker для Caddy та застосунків
docker network create caddy_network
Цю мережу ми будемо вказувати у файлах docker-compose.yml для наших застосунків та для самого Caddy.
2. Конфігурація Caddyfile
Caddy використовує файл Caddyfile для своєї конфігурації. Ми налаштуємо його для проксіювання трафіку до наших Docker-контейнерів.
sudo nano /etc/caddy/Caddyfile # Відкриваємо основний конфігураційний файл Caddy
Видаліть існуючий вміст і вставте наступне. Замініть your-app.example.com та another-app.example.com на ваші реальні доменні імена, а app_container_name та another_app_container_name на імена ваших Docker-контейнерів.
# Глобальні налаштування Caddy
{
# Вказуємо, що Caddy повинен використовувати DNS-сервери Google для Let's Encrypt
# Це може бути корисно, якщо у вашого VPS є проблеми з розпізнаванням DNS
# acme_dns google # Розкоментуйте, якщо використовуєте DNS-провайдера, що підтримує ACME DNS challenge
# email [email protected] # Вкажіть ваш email для сповіщень Let's Encrypt
}
# Конфігурація для першого застосунку
your-app.example.com {
# Проксіюємо весь трафік на Docker-контейнер
reverse_proxy app_container_name:80
# Додаткові заголовки для кращої сумісності та безпеки
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options DENY
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
}
# Логування запитів
log {
output file /var/log/caddy/your-app.log
format json
}
# Стиснення Gzip (опціонально, але рекомендується)
encode gzip
}
# Конфігурація для другого застосунку (приклад)
another-app.example.com {
reverse_proxy another_app_container_name:8080 # Вкажіть порт, на якому слухає ваш другий застосунок
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Frame-Options DENY
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
}
log {
output file /var/log/caddy/another-app.log
format json
}
encode gzip
}
# Ви також можете додати директиву для статичних файлів, якщо Caddy буде їх обслуговувати
# example.com {
# root /var/www/html
# file_server
# }
Пояснення до Caddyfile:
your-app.example.com: Це доменне ім'я, за яким буде доступний ваш застосунок. Caddy автоматично отримає для нього HTTPS-сертифікат. Переконайтеся, що DNS-запис (A-запис) для цього домену вказує на IP-адресу вашого VPS.reverse_proxy app_container_name:80: Caddy перенаправлятиме всі вхідні запити на внутрішню IP-адресу Docker-контейнера з ім'ямapp_container_nameна порту80(або іншому, на якому слухає ваш застосунок всередині контейнера). Важливо, щоapp_container_name— це ім'я сервісу у вашому файліdocker-compose.yml.header: Додає HTTP-заголовки для підвищення безпеки.log: Налаштовує логування запитів. Переконайтеся, що директорія/var/log/caddy/існує і Caddy має до неї права.encode gzip: Вмикає стиснення Gzip для переданих даних.
Створимо директорію для логів Caddy:
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy # Надаємо права користувачу caddy
sudo systemctl restart caddy # Перезапускаємо Caddy для застосування нового Caddyfile
sudo systemctl status caddy # Перевіряємо, що Caddy успішно запустився
3. Приклад розгортання застосунку з Docker Compose
Давайте створимо приклад простого веб-застосунку (наприклад, Nginx з тестовою сторінкою), який буде доступний через Caddy.
mkdir ~/my_web_app
cd ~/my_web_app
nano docker-compose.yml
Вміст docker-compose.yml:
version: '3.8'
services:
# Ім'я сервісу, яке використовуватиме Caddy для проксіювання
# Переконайтеся, що воно збігається з ім'ям у Caddyfile (наприклад, 'app_container_name')
mywebapp:
image: nginx:latest # Використовуємо актуальний образ Nginx
container_name: mywebapp_container # Явне ім'я контейнера
restart: unless-stopped
ports:
- "80" # Nginx слухає на порту 80 всередині контейнера
networks:
- caddy_network # Підключаємо контейнер до мережі Caddy
networks:
caddy_network:
external: true # Вказуємо, що мережа вже існує і була створена вручну
Збережіть файл (Ctrl+X, Y, Enter) і запустіть застосунок:
docker compose up -d # Запускаємо контейнер у фоновому режимі
Тепер відредагуйте ваш Caddyfile, щоб він проксіював на mywebapp:
sudo nano /etc/caddy/Caddyfile
Додайте або змініть секцію для вашого домену:
# ... інші налаштування ...
# Конфігурація для вашого нового застосунку
my-nginx-app.example.com { # Замініть на ваш реальний домен
reverse_proxy mywebapp:80 # Ім'я сервісу з docker-compose.yml та внутрішній порт
log {
output file /var/log/caddy/my-nginx-app.log
format json
}
encode gzip
}
# ... інші налаштування ...
Перезапустіть Caddy:
sudo systemctl reload caddy # Перезавантажуємо конфігурацію Caddy без зупинки сервісу
Переконайтеся, що DNS-запис для my-nginx-app.example.com вказує на IP-адресу вашого VPS. Через кілька секунд (або хвилин, поки Caddy отримає сертифікат) ваш застосунок має бути доступний за HTTPS.
4. Налаштування змінних оточення (.env) для секретів
Ніколи не зберігайте конфіденційні дані (паролі, API-ключі) безпосередньо в docker-compose.yml. Використовуйте змінні оточення через файл .env.
Створіть файл .env у тій самій директорії, що й docker-compose.yml:
nano ~/my_web_app/.env
Приклад вмісту .env:
DB_PASSWORD=MyStrongPassword123
API_KEY=supersecretkeyabc123
Потім у docker-compose.yml ви можете посилатися на ці змінні:
version: '3.8'
services:
mywebapp:
image: mycustomapp:latest
container_name: mywebapp_container
restart: unless-stopped
environment:
- DB_PASSWORD=${DB_PASSWORD} # Передаємо змінну з .env
- API_KEY=${API_KEY}
networks:
- caddy_network
networks:
caddy_network:
external: true
При запуску docker compose up -d Docker Compose автоматично підставить значення з .env.
5. Перевірка працездатності
Після всіх налаштувань необхідно переконатися, що все працює коректно.
-
Перевірка Caddy:
sudo systemctl status caddy # Переконайтеся, що Caddy працює sudo journalctl -u caddy --since "10 minutes ago" # Перевірте логи Caddy на наявність помилок -
Перевірка Docker-контейнерів:
docker ps # Переконайтеся, що ваші контейнери запущені docker logs mywebapp_container # Перевірте логи конкретного контейнера -
Перевірка доступності через домен:
На вашій локальній машині використовуйте
curlдля перевірки HTTPS-з'єднання:curl -vI https://my-nginx-app.example.com # Перевіряємо заголовки HTTP та статус SSL-сертифікатаВи повинні побачити статус
HTTP/2 200та інформацію про SSL-сертифікат, виданий Let's Encrypt. Якщо є проблеми, переконайтеся, що DNS-запис коректний і порти 80/443 відкриті в UFW.