
Можно ли на один поставить несколько сервисов? Можно, все так делают — у меня на личном
HTTP-запрос содержит доменное имя внутри, в заголовке Host. Когда пользователь открывает tracker.example.com и bot.example.com, оба запроса прилетают на один IP и один порт 443, но с разными именами. Значит, на входе нужен диспетчер — это называется обратный прокси. Сразу отмечу, не копируйте слепо мои команды, они с моими примерами, адаптируйте или адаптируйтесь.
Способ первый. Диспетчер на входе
В роли диспетчера обычно выступает nginx, Caddy или Traefik. Сервисы слушают свои внутренние порты, а наружу смотрит только прокси. Он же занимается TLS-сертификатами, редиректом с HTTP на HTTPS и отдаёт статику.
Если хотите минимум возни, обратите внимание именно на Caddy. Он легкий и у него короткий конфиг:
reverse_proxy localhost:3000
}
reverse_proxy localhost:8080
}
Это реально вся конфигурация — Caddy сам настраивает HTTPS, получает сертификаты, например Let's Encrypt или ZeroSSL (ограничивают выпуск сертификатов .ru/.su), и продлевает их. Для этого DNS-записи доменов должны указывать на сервер, а необходимые порты должны быть доступны извне.
С nginx конфигурация длиннее, зато гибче и привычнее многим. Для каждого домена тут создается отдельный блок server, в котором указываются порт, доменное имя и адрес приложения:
server {
listen 80;
server_name tracker.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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 {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
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;
}
}
Здесь listen 80 указывает порт, на котором nginx принимает обычные HTTP-запросы, а server_name определяет, какому домену соответствует каждый блок. Директива proxy_pass передаёт запросы нужному приложению, причём оба сервиса слушают только локальный интерфейс 127.0.0.1.
Два таких блока в конфиге, и вы получите два независимых сервиса на одной машине, каждый со своим доменом. Конфиг кладём в /etc/nginx/sites-available/ или conf.d/, дальше проверка синтаксиса и аккуратный перезапуск:
sudo nginx -t && sudo systemctl reload nginx
Если nginx обнаружит ошибку, исправьте указанную проблему и повторите проверку. Для подключения HTTPS можно использовать Certbot с плагином nginx — он получит TLS-сертификат, добавит необходимые настройки для порта 443 и сможет автоматически продлевать сертификат:
sudo certbot --nginx -d tracker.example.com -d api.example.com
Для этого DNS-записи обоих доменов должны указывать на сервер, входящие подключения к порту 80 должны быть разрешены, а Certbot и его плагин nginx — установлены. После проверьте автоматическое продление сертификатов:
sudo certbot renew --dry-run
Затем посмотрите, кто вообще что слушает на машине, и убедитесь, что наружу торчат только 80, 443 и SSH, а сервисы сидят на localhost:
sudo ss -tulpn
В выводе у tracker и api (это мои примеры) в колонке Local Address должно быть 127.0.0.1:3000 и 127.0.0.1:3001, а не 0.0.0.0, иначе их порты открыты всему интернету в обход прокси и файрвола.
Если приложение умеет биндиться на адрес, пропишите в его конфиге 127.0.0.1. И, пожалуйста, разносите сервисы по поддоменам, а не по путям вроде example.com/tracker. С поддоменами меньше головной боли с куками, редиректами и путями статики внутри приложений.
Способ второй. Каждому сервису по контейнеру
Когда у сервисов разные зависимости, лучше упаковать каждый в свой docker-compose стек, а поверх поставить один общий прокси, который слушает внешние порты и раскидывает запросы по контейнерам. Для этого нужно создать общую внутреннюю сеть (это делается один раз):
docker network create front
Дальше один compose-файл с nginx или Caddy как входной точкой, и рядом по одному compose на каждый сервис, все они подключены к этой сети:
services:
proxy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
networks:
- front
tracker:
image: my-tracker:latest
mem_limit: 512m
restart: unless-stopped
networks:
- front
networks:
front:
external: true
volumes:
caddy_data:
caddy_config:
Обратите внимание, что наружу опубликованы только порты прокси. Таск-трекер доступен другим контейнерам в сети front на своем внутреннем порту 3000, а mem_limit ограничивает потребление памяти, чтобы утечка в приложении не съела всю машину.
Конфиг Caddy писал выше. Для контейнеров его нужно немного изменить — вместо localhost использовать имя сервиса внутри Docker-сети. В файл Caddyfile, который лежит рядом с compose.yaml, добавляем:
reverse_proxy tracker:3000
}
Запуск и проверка, что всё поднялось так:
docker compose up -d
docker compose ps
Сумму жёстких лимитов держите ниже физической памяти машины, иначе вы просто отложили проблему с ее нехваткой. И не забудьте оставить запас ядру, SSH, Docker-демону и прокси. Сколько каждый контейнер реально ест памяти и CPU можно посмотреть через команду:
docker stats --no-stream
Плюс подхода в изоляции, зависимости одного сервиса не ломают другой, обновляете каждый отдельно. Минус — у каждого контейнера свой оверхед по памяти, ну и добавляется управление контейнерами, образами, сетями и томами.
Способ третий. Обычные процессы под разными пользователями
Самый старый вариант — это запустить каждый сервис обычным процессом, но под своим пользователем и под надзором systemd, который перезапустит упавшее и запишет логи. Создаём системного пользователя без шелла и домашней папки:
sudo useradd -r -U -s /usr/sbin/nologin tracker
sudo install -d -o tracker -g tracker /opt/tracker
После размещения приложения в /opt/tracker проверьте права на исполняемый файл run.sh, каталоги с данными и используемые конфигурационные файлы. Сервисный пользователь должен иметь доступ только к необходимым ресурсам:
И пишем юнит /etc/systemd/system/tracker.service:
[Unit]
Description=My tracker
After=network.target
[Service]
User=tracker
WorkingDirectory=/opt/tracker
ExecStart=/opt/tracker/run.sh
Restart=always
MemoryMax=512M
OOMScoreAdjust=500
[Install]
WantedBy=multi-user.target
MemoryMax — это жёсткий потолок памяти для сервиса, аналог mem_limit из контейнерного мира, а OOMScoreAdjust=500 повышает вероятность выбора процесса системным OOM killer. Также помните про MemoryHigh. Включается всё командами:
sudo systemctl daemon-reload
sudo systemctl enable --now tracker
sudo systemctl status tracker
Вывод сервиса попадает в системный журнал с фильтрацией. Например, команда ниже показывает сообщения tracker:
journalctl -u tracker -f
К слову, разные пользователи заодно дают базовую изоляцию по файлам, один сервис физически не может прочитать конфиги другого без лишних прав. Для двух-трёх своих сервисов на одном
Сколько сервисов влезет и как проверить
Четкий ответ не дать, зависит от вашей
Итого на
ps -eo pid,comm,rss --sort=-rss | head -15
free -h
Для первоначальной оценки смотрите на ps и free -h, а в более точном анализе учитывайте rss и потребление групп процессов через cgroups. Грубо говоря, прикиньте rss сервиса под нагрузкой, плюс запас в 30% на пики, плюс память базе, плюс саму систему с её кэшами. Если сумма влезает в объём машины с запасом, заселяйте, если впритык — либо апгрейд тарифа, либо расселение.
Также важно соседство по времени. Ночной бэкап одного сервиса упрётся в диск и задушит сайт другого. Лечится расписаниями и понижением приоритета фоновых задач, тот же pg_dump в кроне запускается с низким приоритетом по диску и процессору:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/backups
DATE=$(date +%F)
ionice -c 3 nice -n 19
pg_dump -U app -d appdb
-Fc -f "$BACKUP_DIR/appdb_$DATE.dump"
Предварительно создайте каталог /backups, настройте права доступа и аутентификацию пользователя PostgreSQL, а скрипт сохраните в /usr/local/bin/backup-appdb.sh с разрешением на выполнение. После этого его можно запускать через cron:
0 3 * * * /usr/local/bin/backup-appdb.sh
Для регулярной эксплуатации нужны контроль успешного завершения, ротация архивов и отправка резервной копии в другое хранилище.
Про безопасность
Общая машина — это общий периметр. Контейнеры и разные пользователи снижают риски, но ядро у всех одно, поэтому взлом одного дырявого сервиса может открыть дорогу к соседям. Минимально сделайте так, чтобы файрвол пропускал только веб и SSH, а всё остальное было закрыто:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 22/tcp
sudo ufw default deny incoming
sudo ufw enable
Если вы подключены к
Также помните, что при публикации контейнерных портов Docker сам создает правила сетевой фильтрации и перенаправления трафика — правила UFW могут не блокировать доступ к опубликованным портам. В связи с этим, контейнеры с БД и внутренними API вообще не публикуйте наружу, а ограничения доступа сверьте с сетевой конфигурацией.
Из ИБ-правил побольше я придерживаюсь двух:
-
Тестовые и экспериментальные поделки не должны жить на той же машине, что и сервис с реальными пользователями и их данными. Для экспериментов нужна отдельная дешёвая виртуалка, которую не жалко снести целиком.
-
Чем старше и неизвестнее софт, тем сильнее изоляция. Пятилетний WordPress с самописными плагинами и свежий Go-сервис — это разные уровни доверия, и сажать их рядом я не советую.
Бэкапы и мониторинг лучше отправить к соседям
Снапшот
Для PostgreSQL дополнительно используйте pg_dump или pg_dumpall, помня, что второй сохраняет и роли. Для файлов подойдут Restic, Borg или Kopia — главное восстановить копию на тестовой машине.
Мониторинг на том же
Когда пора расселять
Среди логичных причин разъехаться:
-
разные владельцы или уровни доверия;
-
ресурсы, когда один сервис регулярно мешает остальным даже после настройки лимитов;
-
деньги и обещания клиентам — если простой одного приложения останавливает оплату в другом, общая машина не поможет сэкономить.
Также обратите внимание на требования к обновлениям. Если одному проекту нужно часто менять Docker и ядро, а другой должен месяцами работать без перезагрузок, лучше развести.
Если упёрлись только в память или диск, второй сервер тоже не всегда нужен. Иногда проще увеличить текущий. У RUVDS параметры
Делить лучше по последствиям
В итоге на одном
А сколько сервисов крутится на вашей основной
© 2026 ООО «МТ ФИНАНС»
Автор: SrvTrantor
