Files
claude-skills/docker-compose-architect/SKILL.md
T
creator 68edc524e3 Add web stack skills bundle: 6 skills for production self-hosted web services
- 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.
2026-05-13 08:41:20 +00:00

318 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: docker-compose-architect
version: 0.1.0
description: 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.
command: /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+.
## Жёсткие инварианты
1. **Имя файла**: ВСЕГДА `compose.yaml` (не `docker-compose.yml`, не `docker-compose.yaml`).
2. **Версия spec**: НЕ указывать `version:` (deprecated в Compose v2).
3. **Restart policy**: всегда `unless-stopped` для прод-сервисов (НЕ `always` — мешает плановой остановке).
4. **Healthcheck**: обязателен у каждого долгоживущего сервиса.
5. **Логи**: `logging.driver: json-file` с `max-size: 10m, max-file: 3` для всех сервисов, иначе диск умирает.
6. **Образы**: пиннить minor-версию (`postgres:16.5`, не `postgres:latest` и не `postgres:16`).
7. **Сети**: явные `networks:` блоки, никогда не дефолтная сеть. Для проксируемых через Traefik — внешняя `traefik_proxy`.
8. **Секреты**: НИКОГДА в `environment:` plaintext. Только через `secrets:` (Docker secrets) или `env_file:` (с файлом вне git).
9. **Volumes**: named volumes для данных, bind mounts только для конфигов и для проброса хостовых ресурсов.
10. **Корневые пути на хосте**: `/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 не подходят
```
## Базовый шаблон сервиса
```yaml
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
```yaml
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
```yaml
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
```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 контейнеров, которым нужен прямой доступ к интерфейсу:
```yaml
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 через временный контейнер:
```bash
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)
```bash
# === Required ===
DB_PASSWORD=
JWT_SECRET=
API_KEY=
# === Optional ===
LOG_LEVEL=info
TZ=Europe/Moscow
# === Traefik ===
DOMAIN=example.abelentsev.pro
```
`.gitignore`:
```
.env
secrets/
data/
acme/
```
## Команды
```bash
# Поднять стек в фоне
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-cd` skill).
- Прокси через единственный Traefik (см. `traefik-architect` skill).
- Бэкап volumes через restic в S3-совместимое хранилище (см. `backup-restore` skill).
- Мониторинг через Prometheus + cAdvisor + node_exporter (см. `observability` skill).