Docker Compose — готовые стеки

Приветствую! Здесь вы наверняка найдете, что ищете. Примеры в лаборатории рассчитаны на то, что мы разбираем что-то конкретное.

Текущая статья посвящена готовым стекам Docker Compose — от Postgres и Redis до WordPress и мониторинга.

Поэтому за теорией по текущей теме вам — в энциклопедию. Если ещё не погружались, то маршрут прост:

  1. Основы
  2. Система и сеть
  3. Данные и разметка
  4. Код и разработка
  5. Языки
  6. Искусственный интеллект
  7. Проект
  8. Инфраструктура и безопасность
  9. Спин-офф

Обязательно пройдитесь.

А теперь приступим к нашему предмету.

Теория и соседние материалы

Полный разбор Compose, команд CLI и типичных ошибок — Docker Compose.

Основы Docker и образов — Docker.

Сборка своего образа — Dockerfile и галерея из 10 типовых Dockerfile.

Пошаговый стек Prometheus + Grafana — практикум.


Три слова — контейнер, образ, compose

Термин Простыми словами Аналогия
Образ (image) «Чертёж» программы с ОС и файлами Установочный диск
Контейнер Запущенный экземпляр образа Открытая программа
Compose-файл Список контейнеров и связей между ними Сценарий «поднять сайт + БД + кэш»
Сервис Имя блока в YAML (web, db) Имя участника в сценарии
Volume (том) Место на диске, где данные живут вне контейнера Флешка с файлами БД
Port mapping 8080:80 — с вашего ПК на порт внутри контейнера Дверь в комнату
  Ваш браузер                    Docker-сеть проекта
  localhost:8080  ──ports──►  [ web :80 ]
                                    │
                                    │ DB_HOST=db
                                    ▼
                               [ db :5432 ]  ◄── volume pg_data (данные на диске)

Запомните: браузер на Windows/macOS/Linux ходит на localhost + левый порт из ports. Контейнер api к PostgreSQL подключается по имени db — это DNS внутри Docker, не IP и не localhost.


Ключи compose-файла

Ключ Смысл простыми словами
services Список контейнеров проекта (web, db, cache…)
image Готовый образ из registry (nginx:1.27-alpine)
build Собрать образ из Dockerfile в указанной папке
ports Проброс хост:контейнер — браузер ходит на localhost
environment Переменные внутри контейнера
env_file Подтянуть пары ключ=значение из .env
volumes Том или bind-mount — данные переживают пересоздание контейнера
depends_on Порядок старта (без гарантии «БД уже принимает SQL»)
healthcheck Compose ждёт успешной проверки перед service_healthy
networks Отдельные виртуальные сети между сервисами
profiles Сервис поднимается только с --profile имя
restart Политика перезапуска после сбоя или reboot хоста

Два адреса — запомните в первую очередь:

Откуда Куда подключаться
Браузер на вашем ПК http://localhost:8080 — левое число в ports
Контейнер к контейнеру имя сервиса (db, redis), не localhost

Пример: в compose-сервисе api строка DB_HOST=db верна; DB_HOST=localhost укажет на loopback внутри контейнера api, где PostgreSQL не слушает.


Порядок действий

  1. Создайте пустую папку, например my-stack/.
  2. Сохраните блок YAML в compose.yaml (или docker-compose.yml — оба имени Compose понимает).
  3. При необходимости добавьте .env и вспомогательные файлы из раздела.
  4. Откройте терминал в этой папке (PowerShell, cmd, bash, WSL — без разницы).
  5. Выполните команды по порядку:
docker compose config
docker compose up -d
docker compose ps

Разбор команд:

Команда Что делает
docker compose config Читает YAML, подставляет .env, печатает итог без запуска контейнеров — ловит опечатки до up
docker compose up -d Скачивает образы (если нет), создаёт сеть и тома, запускает все сервисы; -d = detached, терминал свободен
docker compose ps Таблица: имя, статус (Up, healthy), какие порты проброшены на хост
  1. Откройте URL из раздела «Проверка» или смотрите логи: docker compose logs -f web (имя сервиса из YAML).
  2. Остановка без удаления данных БД: docker compose down.
  3. Полный сброс данных в named volumes: docker compose down -v — все таблицы Postgres «как новые».
Docker Desktop на Windows

Перед docker compose up убедитесь, что Docker Desktop запущен (иконка в трее «Running»). Ошибка Cannot connect to the Docker daemon означает, что демон не работает — не ошибка в YAML.

Пароли в примерах

Значения вроде secret и admin подходят только для локального dev. В общий Git пароли не коммитьте — используйте .env.gitignore) или Docker secrets.


Обязательный каркас проекта

Любой стек ниже можно собрать на этом фундаменте:

services:
  example:
    image: alpine:3.20
    command: ["sleep", "infinity"]

# volumes:
#   data:
Фрагмент Смысл
services: Корневая секция — без неё файл невалиден
example: Имя сервиса; по нему же резолвится DNS внутри сети
image: Образ из Docker Hub или локального кэша
command: Переопределяет CMD образа (здесь контейнер «висит» для отладки)

Что делает файл целиком:

  1. Объявляет один сервис example на базе минимального образа Alpine Linux.
  2. Команда sleep infinity не даёт контейнеру завершиться — удобно для docker compose exec example sh.
  3. Закомментированная секция volumes: — заготовка; раскомментируйте, когда понадобится постоянное хранилище.

Проверка синтаксиса до запуска:

docker compose config

Стартовые стеки

Пять минимальных конфигураций — с одного сервиса до классической связки приложение + БД.

Как устроен каждый пример ниже
Задача — что вы получите и цель
YAML — готовый файл для копирования.
Разбор по строкам — таблица «что означает каждая строка».
Проверка — URL или команда, чтобы убедиться, что стек жив.
RegEx — сначала код, потом объяснение.

1. Один nginx — «Hello»

Задача: проверить, что Docker и Compose работают. Частый docker compose nginx example, nginx docker compose yml.

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"

Разбор по строкам:

Строка Смысл
services: Начало списка контейнеров проекта
web: Имя сервиса; по нему же обращаются другие контейнеры (http://web)
image: nginx:1.27-alpine Образ с Docker Hub: веб-сервер nginx на лёгкой Alpine Linux; тег 1.27-alpine фиксирует версию
ports: Секция проброса портов на ваш ПК
- "8080:80" Формат порт_хоста:порт_контейнера. Браузер → localhost:8080 → nginx слушает :80 внутри

Что происходит при docker compose up -d:

  1. Compose создаёт виртуальную сеть проекта (имя = имя папки).
  2. Если образа nginx:1.27-alpine нет — скачивает (pull).
  3. Создаёт контейнер …-web-1, подключает к сети, пробрасывает порт 8080.
  4. Nginx стартует и отдаёт дефолтную страницу «Welcome to nginx».

Проверка: http://localhost:8080 — страница «Welcome to nginx».

Команды и разбор:

docker compose up -d
docker compose logs web
docker compose down
Команда Смысл
up -d Поднять стек в фоне
logs web Показать stdout/stderr сервиса web (ошибки конфига nginx видны здесь)
down Остановить и удалить контейнер и сеть; образ nginx на диске остаётся

Если порт 8080 занят — замените на "8888:80" и откройте http://localhost:8888.


2. Статика с диска

Задача: отдавать свой index.html без пересборки образа. Поиск: docker nginx static files, монтировать папку в контейнер.

Структура каталога:

static-site/
  compose.yaml
  html/
    index.html

Пример html/index.html:

<!DOCTYPE html>
<html lang="ru">
<head><meta charset="UTF-8"><title>Мой сайт</title></head>
<body><h1>Привет из Docker!</h1></body>
</html>

compose.yaml:

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro

Разбор по строкам:

Строка Смысл
volumes: Подключение папок или томов к файловой системе контейнера
./html:/usr/share/nginx/html:ro Bind mount: папка html рядом с YAML → стандартная папка статики nginx; :ro = read-only (контейнер не перезапишет ваши файлы)

Что делает bind mount:

  • Файлы лежат на вашем диске в static-site/html/.
  • Nginx внутри контейнера читает их как /usr/share/nginx/html/index.html.
  • Правка index.html в блокноте → F5 в браузере — без docker compose up заново.

Проверка: измените текст в html/index.html — обновите страницу в браузере.


3. Только PostgreSQL для разработки

Задача: локальная БД на порту 5432 с сохранением данных в томе. Поиск: docker compose postgres, postgresql docker-compose.yml example, поднять postgres локально docker.

Разбор по строкам:

Строка Смысл
db: Имя сервиса; другие контейнеры подключаются к хосту db, порт 5432
postgres:16-alpine Официальный образ PostgreSQL 16
POSTGRES_USER Имя суперпользователя БД (создаётся при первом старте тома)
POSTGRES_PASSWORD Пароль; без него образ не стартует
POSTGRES_DB База, которую Postgres создаст при инициализации
"5432:5432" DBeaver, pgAdmin, Python на хосте подключаются к localhost:5432
pg_data:/var/lib/postgresql/data Данные таблиц хранятся в named volume, не внутри слоя контейнера
healthcheck Периодически запускает pg_isready; статус healthy виден в docker compose ps
interval: 5s Проверка каждые 5 секунд
retries: 5 После 5 неудач — сервис unhealthy
volumes: (в корне) Объявление имени тома pg_data; Docker создаст его автоматически

Строка подключения с вашего ПК (не из контейнера):

postgresql://app:secret@localhost:5432/appdb

Что происходит при первом up:

  1. Создаётся пустой том pg_data.
  2. Postgres инициализирует кластер, создаёт пользователя app и БД appdb.
  3. При повторном up после down данные на месте — init не повторяется.

Проверка:

docker compose exec db psql -U app -d appdb -c "SELECT 1;"
Часть команды Смысл
exec db Выполнить команду внутри работающего контейнера сервиса db
psql -U app -d appdb Клиент SQL: пользователь app, база appdb
-c "SELECT 1;" Один запрос без интерактивной оболочки

Ожидаемый вывод — таблица с числом 1.

Подробнее про Postgres в контейнере — PostgreSQL в Docker.


4. Приложение + PostgreSQL

Задача: API или бэкенд подключается к БД по имени сервиса db. Поиск: docker compose node postgres, spring boot docker compose postgres, backend + database compose.

В папке app/ выполните npm init -y && npm install pg на хосте (или добавьте package.json в репозиторий).

Проверка: http://localhost:3000 — текст «API + Postgres OK»; в логах docker compose logs api нет connection refused.


5. Redis рядом с PostgreSQL

Задача: кэш или сессии в Redis, постоянные данные — в Postgres. Поиск: docker compose redis postgres, full stack docker compose.

Разбор ключевых строк:

Строка Смысл
build: . Compose соберёт образ из Dockerfile в текущей папке (ваше приложение)
REDIS_URL: redis://redis:6379/0 URL к Redis: хост redis — имя сервиса; /0 — номер логической БД Redis
service_healthy для db Ждём готовности SQL
service_started для redis Достаточно запущенного процесса; Redis поднимается быстро
redis_data:/data Персистентность Redis (RDB/AOF в томе) — опционально для dev, полезно для учебных данных

Типичное разделение ролей:

Сервис Что хранит
PostgreSQL Пользователи, заказы, всё, что нельзя потерять
Redis Сессии, кэш страниц, счётчики, очереди на время

Проверка Redis из контейнера api:

docker compose exec redis redis-cli ping

Ответ PONG — сервер жив.


Примеры стеков

Готовые конфигурации под типовые задачи — от CMS до мониторинга и S3-совместимого хранилища.


6. WordPress + MariaDB

Задача: блог или учебный сайт за пару минут. Поиск: wordpress docker compose, wordpress mysql docker-compose.yml.

Разбор по строкам:

Строка Смысл
wordpress:6.7-apache CMS + Apache PHP в одном образе
WORDPRESS_DB_HOST: db WordPress подключается к MariaDB по имени сервиса
WORDPRESS_DB_* Учётные данные должны совпадать с MARIADB_* у db
wp_uploads Загруженные картинки переживают пересоздание контейнера wordpress
mariadb:11 Форк MySQL, совместим с WordPress
MARIADB_ROOT_PASSWORD Пароль root (для админских задач, не для WP)
healthcheck.sh Встроенный скрипт образа MariaDB — ждёт инициализацию InnoDB

Что происходит при первом открытии сайта:

  1. WordPress подключается к MariaDB и создаёт таблицы.
  2. Мастер установки просит язык, заголовок сайта, логин admin.
  3. Данные таблиц — в томе mariadb_data; медиафайлы — в wp_uploads.

Проверка: http://localhost:8080 — мастер установки WordPress.


7. MongoDB + веб-админка

Задача: документная БД и UI для просмотра коллекций. Поиск: mongodb docker compose, mongo-express docker.

Разбор по строкам:

Строка Смысл
MONGO_INITDB_ROOT_* Создаёт root-пользователя при первом старте тома
mongo_data:/data/db Файлы БД на named volume
"27017:27017" Драйвер MongoDB на хосте (Compass, Node, Python) → mongodb://root:secret@localhost:27017
mongo-express Веб-UI для просмотра коллекций (только dev, не для prod)
ME_CONFIG_MONGODB_URL URL из контейнера mongo-express: хост mongo, не localhost
depends_on: - mongo Порядок старта без healthcheck

Проверка:

URL / строка Назначение
http://localhost:8081 mongo-express в браузере
mongodb://root:secret@localhost:27017 Подключение с вашего ПК
mongodb://root:secret@mongo:27017 Подключение из другого сервиса в том же compose

8. Prometheus + Grafana (минимум)

Задача: локальный мониторинг метрик. Поиск: prometheus grafana docker compose. Полный стенд с provisioning — в практикуме Prometheus.

Структура каталога:

monitoring/
  compose.yaml
  prometheus/
    prometheus.yml

compose.yaml:

Разбор по строкам:

Строка Смысл
./prometheus/prometheus.yml:..:ro Конфиг scrape с диска; :ro — контейнер не перезапишет файл
prometheus_data:/prometheus История метрик (TSDB) сохраняется между перезапусками
GF_SECURITY_ADMIN_* Логин и пароль первого входа в Grafana
grafana_data Дашборды и настройки Grafana
depends_on: prometheus Grafana стартует после Prometheus (без healthcheck)

Минимальный prometheus/prometheus.yml:

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["localhost:9090"]

Разбор prometheus.yml:

Строка Смысл
scrape_interval: 15s Как часто опрашивать targets
job_name: prometheus Имя job в UI
targets: ["localhost:9090"] Внутри контейнера prometheus он сам слушает 9090 — здесь localhost корректен
URL Назначение
http://localhost:9090 Prometheus UI
http://localhost:3000 Grafana (логин admin / admin)

Настройка datasource в Grafana: URL http://prometheus:9090 — Grafana работает в контейнере и видит Prometheus по имени сервиса. localhost:9090 в datasource Grafana не сработает — это был бы loopback самой Grafana.

Готовые PromQL-запросы с разбором — Prometheus + Grafana — запросы: up, rate, CPU, HTTP, P99, алерты.


9. MailHog — перехват писем в dev

Задача: приложение шлёт SMTP на MailHog; письма видны в браузере, реальная почта не уходит. Поиск: mailhog docker compose, тестовая почта docker.

services:
  mailhog:
    image: mailhog/mailhog:v1.0.1
    ports:
      - "1025:1025"
      - "8025:8025"

Разбор по строкам:

Строка Смысл
mailhog/mailhog Лёгкий SMTP-сервер + веб-инbox для разработки
"1025:1025" SMTP — ваше приложение на хосте шлёт на localhost:1025
"8025:8025" HTTP UI — список писем в браузере

Переменные для приложения на хосте:

Переменная Значение
SMTP_HOST localhost
SMTP_PORT 1025
TLS / auth не нужны — MailHog принимает всё

Проверка: http://localhost:8025 — пустой inbox; отправьте тестовое письмо из приложения — оно появится в UI.


10. MinIO — S3-совместимое хранилище

Задача: локальный объектный storage для загрузки файлов. Поиск: minio docker compose, s3 local docker.

Разбор по строкам:

Строка Смысл
command: server /data .. Запуск сервера MinIO; данные в /data (том minio_data)
--console-address ":9001" Админ-консоль на порту 9001 внутри контейнера
9000 S3 API — SDK и aws-cli ходят сюда
9001 Веб-консоль — buckets, ключи доступа
MINIO_ROOT_* Root-логин для консоли и API
URL Назначение
http://localhost:9000 S3 API endpoint
http://localhost:9001 Консоль MinIO (логин minioadmin / minioadmin)

11. Nginx как reverse proxy

Задача: один входной порт, два бэкенда по пути /api и /. Поиск: nginx reverse proxy docker compose, docker compose несколько сервисов один порт.

Структура:

proxy-demo/
  compose.yaml
  nginx.conf
  frontend/
    index.html

compose.yaml:

Разбор compose:

Сервис Роль
proxy Единственный с ports — «лицо» стека для браузера
frontend Внутренний nginx со статикой; без проброса портов наружу
api Учебный HTTP-сервер; слушает 5678 (дефолт http-echo)

Разбор nginx.conf:

Строка Смысл
upstream front &#123; server frontend:80; &#125; Имя frontend — DNS в Docker-сети
location /api/ Запросы с префиксом /api/ → бэкенд
proxy_pass http://back/; Проксирование на upstream back (http-echo)
location / Всё остальное → статика

Проверка:

URL Ожидание
http://localhost:8080/ Ваш frontend/index.html
http://localhost:8080/api/ Текст API OK

Подробнее про конфиги nginx — Nginx — конфиги под задачу.


12. Профили dev и tools

Задача: БД поднимается всегда; pgAdmin — только когда нужен. Поиск: docker compose profiles, pgadmin docker compose.

Разбор по строкам:

Строка Смысл
profiles: ["tools"] Сервис не стартует при обычном docker compose up
PGADMIN_DEFAULT_EMAIL Логин в UI pgAdmin
5050:80 UI pgAdmin → http://localhost:5050

Запуск и разбор команд:

docker compose up -d
docker compose --profile tools up -d pgadmin
Команда Смысл
up -d Только db — pgAdmin пропущен
--profile tools Включить сервисы с профилем tools
up -d pgadmin Поднять только pgAdmin (db уже работает)

Подключение в pgAdmin: host db, port 5432, user/password из compose Postgres (создайте server в UI).


13. Переменные из .env

Задача: порты и пароли в одном файле, не захардкожены в YAML. Поиск: docker compose env file, docker-compose .env example.

.env:

APP_PORT=3000
POSTGRES_PASSWORD=secret
POSTGRES_USER=app
POSTGRES_DB=appdb

compose.yaml:

Разбор:

Файл / строка Смысл
.env Лежит рядом с compose; Compose подставляет значения автоматически
`${POSTGRES_USER}` Плейсхолдер — заменится на app из .env
.gitignore Файл .env не коммитьте; в репозиторий — .env.example без секретов

Что делает docker compose config:

  1. Читает compose.yaml.
  2. Подставляет переменные из .env и из окружения ОС.
  3. Печатает итоговый YAML — удобно проверить, что пароль подставился.

Шаблон .env.example для README:

POSTGRES_USER=app
POSTGRES_PASSWORD=change_me
POSTGRES_DB=appdb

14. Стек для CI-тестов

Задача: поднять БД, прогнать тесты в одноразовом контейнере, остановить всё. Поиск: docker compose run test, ci postgres docker compose.

Разбор по строкам:

Строка Смысл
db без ports БД только внутри Docker-сети — с хоста порт не нужен
DATABASE_URL Стандартная строка для ORM; хост db, пароль test
command: ["npm", "test"] Переопределяет CMD образа — запуск тестов
retries: 10 В CI БД иногда дольше стартует — больше попыток healthcheck

Типичный сценарий в пайплайне:

docker compose up -d db
docker compose run --rm test
docker compose down -v

Разбор команд:

Команда Смысл
up -d db Поднять только сервис db, не test
run --rm test Создать контейнер test, выполнить npm test, удалить контейнер после exit
down -v Очистить тома — следующий прогон CI с чистой БД

Отличие run от up: up держит сервисы постоянно; run — одноразовая команда (миграции, тесты, seed).


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

Задача Команда Что происходит под капотом
Поднять стек docker compose up -d pull/build → сеть → тома → контейнеры → healthcheck
Пересобрать образы docker compose up -d --build docker build для каждого build: перед стартом
Статус docker compose ps Имена контейнеров, Up (healthy), проброшенные порты
Логи docker compose logs -f api stdout/stderr; -f = поток, как tail -f
Shell в контейнере docker compose exec db sh Новый процесс внутри уже работающего контейнера
Одноразовая команда docker compose run --rm test npm test Новый контейнер, команда, удаление после exit
Проверка YAML docker compose config Merge файлов + подстановка .env, без изменений на хосте
Остановка docker compose down Stop + remove контейнеров и сети; тома остаются
Сброс данных docker compose down -v Удаляет named volumes — БД «с нуля»
Только один сервис docker compose up -d db Остальные сервисы не трогаются

Частые вопросы из поиска

Вопрос Короткий ответ Где пример
Как поднять PostgreSQL локально? compose.yaml + docker compose up -d стек №3
docker-compose.yml nginx Один сервис web, ports: "8080:80" стек №1
WordPress docker compose wordpress + mariadb + volumes стек №6
app + postgres + redis три сервиса, DB_HOST=db, REDIS_URL=redis://redis:6379 стек №5
Cannot connect to Docker daemon Запустите Docker Desktop / systemctl start docker раздел «Как работать»
connection refused к БД В контейнере app — хост db, не localhost стек №4
Куда делись данные после down? Нужен named volume; down -v удаляет тома стек №3
version: '3' obsolete В Compose V2 ключ version: часто опускают теория

Типичные ошибки новичка

Ошибка Что происходит Как исправить
DB_HOST=localhost в сервисе app API не видит Postgres указать имя сервиса db
Нет healthcheck, только depends_on app падает при старте БД condition: service_healthy + pg_isready
Забыли том для БД данные пропали после down named volume на /var/lib/postgresql/data
Порт занят на хосте Bind for 0.0.0.0:5432 failed сменить левую часть в ports, например 15432:5432
.env в Git утечка паролей .gitignore + пример .env.example
docker compose down -v на учебной БД все таблицы «с нуля» бэкап или без -v

Практика — задания для лабораторной

  1. Nginx. Поднимите стек №1, замените порт 8080 на 8888, выполните docker compose ps — в колонке PORTS должно быть 8888->80/tcp.
  2. Postgres. Подключитесь к стеку №3 через DBeaver или psql: host localhost, port 5432, user app, password secret, database appdb.
  3. Сеть. В стеке №4 выполните docker compose exec api getent hosts db — в ответе IP контейнера db.
  4. Том. Создайте таблицу в Postgres, выполните docker compose down и снова up -d — таблица на месте. Затем down -v и up -d — таблицы нет (данные сброшены).
  5. Профиль. В стеке №12 убедитесь, что после up -d pgAdmin не запущен (docker compose ps), а после --profile tools — появился.

Разбор команд и архитектуры — в Docker Compose.


Оглавление стеков — быстрый выбор

Стек Когда брать
1 nginx Проверка Docker, первая лабораторная
2 nginx + папка html Статический сайт, фронт без сборки
3 PostgreSQL Локальная SQL-база для Java, Python, C#
4 Node + PostgreSQL Backend + БД, учебный fullstack
5 app + Postgres + Redis Кэш, сессии, «как в проде»
6 WordPress + MariaDB CMS, блог, курсовой сайт
7 MongoDB + mongo-express NoSQL, документы JSON
8 Prometheus + Grafana Метрики, мониторинг
9 MailHog Тест email без реальной отправки
10 MinIO Файлы, S3 API
11 nginx reverse proxy Один порт, несколько бэкендов
12 Postgres + pgAdmin (profile) БД всегда, GUI по необходимости
13 .env + Postgres Секреты вне YAML
14 db + test (CI) GitHub Actions, GitLab CI

Чек-лист перед сдачей лабораторной

  • docker compose config проходит без ошибок.
  • Для связи между сервисами используются имена сервисов, не localhost.
  • У PostgreSQL/MySQL/MariaDB есть named volume.
  • Пароли вынесены в .env или secrets, не в публичный репозиторий.
  • В README указаны URL, логины и команды up / down.

Связанные материалы

Материал Зачем открыть
Docker Compose теория, healthcheck, secrets, CI
Docker образы, тома, сеть
Dockerfile build: в compose
Dockerfile — 10 типовых образов готовые Dockerfile под Node, Python, Go…
Манифесты зависимостей compose в структуре проекта
Шаблоны минимальный Dockerfile и прочие каркасы
Практикум Prometheus полный observability-стек
PostgreSQL в Docker init-скрипты и PGDATA
Nginx — конфиги под задачу proxy, SPA, PHP-FPM, TLS вне compose