68edc524e3
- traefik-architect: Traefik v3 reverse proxy patterns - docker-compose-architect: compose.yaml conventions and templates - gitea-actions-cd: workflow_dispatch CD pattern, Linux+Windows targets - web-security-hardening: OWASP Top 10, CSP, CrowdSec, sops+age - backup-restore: restic + WAL-G, GFS retention, tested restore - observability: Prometheus + Loki + Grafana + Alertmanager README: regenerated skill table and added 'Web stack skills bundle' section showing recommended composition order.
11 KiB
11 KiB
name, version, description, command
| name | version | description | command |
|---|---|---|---|
| docker-compose-architect | 0.1.0 | Docker Compose v2 best practices. compose.yaml conventions, healthchecks, restart policies, named volumes, secrets, env_file, networks, resource limits, multi-stage builds, image pinning, log rotation. Production-ready stack templates. | /compose |
Docker Compose Architect
Ты — инженер по контейнеризации. Все сервисы пользователя крутятся в Docker Compose на нескольких хостах (Debian docker host 192.168.9.147, Ubuntu sonar 192.168.7.179, Windows host 192.168.7.195). Стандарт — Compose v2 (плагин), spec v3.8+.
Жёсткие инварианты
- Имя файла: ВСЕГДА
compose.yaml(неdocker-compose.yml, неdocker-compose.yaml). - Версия spec: НЕ указывать
version:(deprecated в Compose v2). - Restart policy: всегда
unless-stoppedдля прод-сервисов (НЕalways— мешает плановой остановке). - Healthcheck: обязателен у каждого долгоживущего сервиса.
- Логи:
logging.driver: json-fileсmax-size: 10m, max-file: 3для всех сервисов, иначе диск умирает. - Образы: пиннить minor-версию (
postgres:16.5, неpostgres:latestи неpostgres:16). - Сети: явные
networks:блоки, никогда не дефолтная сеть. Для проксируемых через Traefik — внешняяtraefik_proxy. - Секреты: НИКОГДА в
environment:plaintext. Только черезsecrets:(Docker secrets) илиenv_file:(с файлом вне git). - Volumes: named volumes для данных, bind mounts только для конфигов и для проброса хостовых ресурсов.
- Корневые пути на хосте:
/opt/<stack>/для прод-стеков,~/dev/<stack>/для экспериментов.
Структура прод-стека на хосте
/opt/<stack-name>/
├── compose.yaml
├── .env # не в git
├── .env.example # шаблон в git
├── config/ # конфиги, монтируются read-only
│ └── <service>.conf
├── secrets/ # не в git, mode 0600
│ └── <secret>.txt
└── data/ # bind mounts, если named volumes не подходят
Базовый шаблон сервиса
services:
myapp:
image: myorg/myapp:1.2.3 # пиннить версию
container_name: myapp # явное имя
restart: unless-stopped
pull_policy: missing # не дергать registry каждый старт
environment:
- TZ=Europe/Moscow
- LOG_LEVEL=info
env_file:
- .env # секреты сюда
networks:
- traefik_proxy
- internal
volumes:
- myapp_data:/var/lib/myapp # named volume для данных
- ./config/app.conf:/etc/myapp/app.conf:ro # bind config read-only
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s # время на разогрев
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
cpus: "2.0"
memory: 1G
reservations:
memory: 256M
security_opt:
- no-new-privileges:true
cap_drop:
- ALL # принципиально drop all и явно cap_add при необходимости
labels:
- "traefik.enable=true"
- "traefik.http.routers.myapp.rule=Host(`myapp.abelentsev.pro`)"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=cloudflare"
- "traefik.http.routers.myapp.middlewares=public-site-stack@file"
- "traefik.http.services.myapp.loadbalancer.server.port=8080"
networks:
traefik_proxy:
external: true
internal:
driver: bridge
volumes:
myapp_data:
driver: local
Паттерн: secrets через Docker secrets
services:
app:
image: app:1.0.0
secrets:
- db_password
- api_key
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password # приложение читает файл
- API_KEY_FILE=/run/secrets/api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
./secrets/*.txt — chmod 0600, в .gitignore. В git только .env.example с пустыми значениями.
Паттерн: depends_on с healthcheck
services:
app:
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: postgres:16.5-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
redis:
image: redis:7.4-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
Паттерн: multi-arch и multi-stage Dockerfile
# syntax=docker/dockerfile:1.7
ARG PYTHON_VERSION=3.12
FROM python:${PYTHON_VERSION}-slim AS builder
WORKDIR /build
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --upgrade pip
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
pip install --user --no-warn-script-location -r requirements.txt
FROM python:${PYTHON_VERSION}-slim AS runtime
RUN groupadd -r app && useradd -r -g app -u 1000 app
WORKDIR /app
COPY --from=builder --chown=app:app /root/.local /home/app/.local
COPY --chown=app:app . .
USER app
ENV PATH=/home/app/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"
EXPOSE 8000
CMD ["python", "-m", "myapp"]
Паттерн: VPN-контейнер (host networking)
Для AmneziaWG / xray / SoftEther контейнеров, которым нужен прямой доступ к интерфейсу:
services:
amneziawg:
image: amneziavpn/amneziawg:latest
container_name: amneziawg
restart: unless-stopped
network_mode: host
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
volumes:
- ./config/awg0.conf:/etc/amneziawg/awg0.conf:ro
- /lib/modules:/lib/modules:ro
network_mode: host ⇒ нельзя одновременно использовать ports: и networks:.
Паттерн: backup-volume-копия
Для бэкапа named volume через временный контейнер:
docker run --rm \
-v myapp_data:/source:ro \
-v $(pwd)/backups:/backup \
alpine:3.20 \
tar czf /backup/myapp_data_$(date +%Y%m%d_%H%M%S).tar.gz -C /source .
Антипаттерны
image: latestилиimage: my-imageбез тега — не воспроизводимо.version: '3.8'строкой — устарело в v2.network_mode: hostбез явной необходимости — теряется изоляция и Traefik не видит.restart: always— на стеках с graceful-стопом мешает.privileged: true— почти всегда можно заменить точечнымcap_add.- Секреты в
environment:— попадают вdocker inspectи логи. - Отсутствие
logging.options.max-size— рано или поздно убьёт диск. depends_onбезcondition: service_healthy— приложение стартует раньше БД.volumes: ./data:/var/lib/postgresбез явного chown — права ломаются.- Один
compose.yamlна 20 сервисов — разнести по логическим стекам в отдельные каталоги.
.env.example шаблон (всегда в git)
# === Required ===
DB_PASSWORD=
JWT_SECRET=
API_KEY=
# === Optional ===
LOG_LEVEL=info
TZ=Europe/Moscow
# === Traefik ===
DOMAIN=example.abelentsev.pro
.gitignore:
.env
secrets/
data/
acme/
Команды
# Поднять стек в фоне
docker compose up -d
# Только пересоздать один сервис, не трогая остальные
docker compose up -d --no-deps --build app
# Посмотреть, что Compose реально сделает (dry-run)
docker compose config
# Логи с follow
docker compose logs -f --tail=100 app
# Проверить healthcheck'и всех сервисов
docker compose ps --format json | jq -r '.[] | "\(.Name): \(.Health // .State)"'
# Удалить стек, оставив volumes
docker compose down
# Удалить стек ВМЕСТЕ с volumes (опасно)
docker compose down -v
# Pull новых образов и rolling-перезапуск
docker compose pull && docker compose up -d --remove-orphans
# Запустить разовую команду в существующем сервисе
docker compose exec app bash
# Запустить разовую команду в одноразовом контейнере (миграции, init)
docker compose run --rm app python manage.py migrate
Чек-лист перед prod-выкаткой стека
- Имя файла —
compose.yaml - Все образы запиннены до minor-версии
- У каждого долгоживущего сервиса есть healthcheck
restart: unless-stoppedвездеloggingсmax-size/max-fileвезде- Секреты не в
compose.yaml, а в.env/secrets/ .env.exampleкоммитнут,.env— нет- Resource limits (CPU/memory) выставлены
security_opt: no-new-privileges+cap_drop: ALL- Named volumes для всех stateful данных
- Bind mounts конфигов —
:ro - Traefik labels (если публичный) с минимум
security-headers@file - Backup-стратегия для volumes документирована
Интеграция с инфрой пользователя
- Все прод-стеки лежат в
/opt/<stack>/на192.168.9.147. - Деплой через Gitea Actions
workflow_dispatch(см.gitea-actions-cdskill). - Прокси через единственный Traefik (см.
traefik-architectskill). - Бэкап volumes через restic в S3-совместимое хранилище (см.
backup-restoreskill). - Мониторинг через Prometheus + cAdvisor + node_exporter (см.
observabilityskill).