# Deploy: Production Readiness Checklist **Контекст**: В `deploy/` есть `docker-compose.prod.yml`, `deploy.sh`, `env.prod`. Локально — `docker-compose.yml` + `Makefile`. **Обновлено 2026-06 — добавлен Caddy reverse proxy, но есть ещё проблемы.** ## Суть Проект разворачивается, Caddy настроен как reverse proxy с health headers и безопасными заголовками. Однако есть риски безопасности, надёжности и операционности. ## Критические проблемы (P0) | Проблема | Файл | Риск | |---|---|---| | **Secrets в plain text** | `env.prod`, `docker-compose.yml` | Утечка БД, JWT, S3 при коммите / доступе к серверу | | **Миграции не гарантированно накатываются** | `docker-compose.prod.yml:52` | `migrate/migrate` sidecar без retry логики | | **Single instance БД/Redis/MinIO** | `docker-compose.prod.yml` | SPOF, нет HA, нет бэкапов | | **Hardcoded IP в env.prod** | `env.prod` | Не переносимо, не работает за доменом | | **CSP с `unsafe-inline` и `unsafe-eval`** | `Caddyfile:40` | XSS-защита ослаблена (см. [[atomic-csp-hardening]]) | | **Redis — SPOF для rate limiter** | fail-closed в production (503 при падении) | [[atomic-redis-rate-limiter-failopen-failclosed]] | ## Текущая конфигурация Caddy ```caddyfile # deploy/Caddyfile — уже работает как reverse proxy http://api.{$DOMAIN} { reverse_proxy backend:8080 header { ... } } http://{$DOMAIN} { reverse_proxy frontend:3000 header { Content-Security-Policy "..." X-Content-Type-Options "nosniff" X-Frame-Options "DENY" } encode zstd gzip } ``` **Что уже хорошо**: - ✅ Reverse Proxy для frontend + backend - ✅ X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy - ✅ Content-Security-Policy (хотя и с `unsafe-inline`) - ✅ Cache-Control для статики - ✅ zstd + gzip encoding **Что нужно исправить**: - ❌ Используется `http://`, а не HTTPS (Caddy умеет авто-HTTPS через Let's Encrypt) - ❌ CSP с `unsafe-inline` и `unsafe-eval` (см. [[atomic-csp-hardening]]) - ❌ Нет HSTS заголовка ## Рекомендуемые изменения ### 1. Secrets Management ```bash # Не хранить секреты в репозитории! # Использовать: # - Docker secrets (swarm mode) # - HashiCorp Vault / AWS Secrets Manager / 1Password CLI # - .env файл только на сервере (chmod 600), в .gitignore ``` ### 2. HTTPS (убрать `http://` из Caddyfile) ```caddyfile # Caddyfile — авто-HTTPS через Let's Encrypt photoplaces.ru { reverse_proxy frontend:3000 } api.photoplaces.ru { reverse_proxy backend:8080 } ``` Caddy автоматически получит сертификаты для перечисленных доменов. ### 3. Healthchecks для всех сервисов ```yaml backend: healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/api/v1/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s ``` ### 4. Database Backups ```yaml postgres-backup: image: prodrigestivill/postgres-backup-local environment: SCHEDULE: "@daily" BACKUP_KEEP_DAYS: 30 ``` ### 5. Логирование и мониторинг - Loki + Promtail для логов - Prometheus + Grafana для метрик - Настроить алерты: падение Redis >5min, error rate >1%, p99 latency >500ms ### 6. Healthcheck зависимость (fix) В текущем `docker-compose.yml` есть проблема: backend healthcheck использует `curl` к самому себе на `/api/v1/health`, но этот эндпоинт доступен только после старта Go приложения. Это может создать циклическое ожидание с `depends_on` из frontend. Рекомендуется: - Добавить простой healthcheck для backend (TCP check на порт 8080) - Или сделать `/health` на уровне Caddy/Router перед Go-обработчиками ## Чек-лист перед продакшеном - [ ] Все секреты вынесены из репозитория - [ ] Настроен HTTPS (Let's Encrypt / Caddy авто-HTTPS) - [ ] Убрать `http://` из Caddyfile — перейти на домены - [ ] Healthchecks на всех сервисах (working!) - [ ] Redis Sentinel/Cluster для HA (чтобы не было SPOF) - [ ] Автоматические бэкапы БД (проверено restore) - [ ] Логи агрегируются (Loki/ELK) - [ ] Метрики собираются (Prometheus + Grafana) - [ ] Настроены алерты (down, high error rate, high latency) - [ ] CSP hardening: nonce вместо `unsafe-inline`, strict-dynamic ([[atomic-csp-hardening]]) - [ ] Rate limiting: fail-open/closed стратегия ([[atomic-redis-rate-limiter-failopen-failclosed]]) - [ ] CORS настроен только для продакшен доменов - [ ] Security headers (CSP, HSTS, X-Frame-Options) - [ ] Нагрузочное тестирование пройдено - [ ] Graceful shutdown проверен (освобождение портов, закрытие соединений) - [ ] Падение Redis: что происходит с rate limiter (должен быть alarm + fallback) ## Связанные заметки - [[backend-rate-limiting]] — rate limiter с fail-closed в production - [[atomic-csp-hardening]] — CSP hardening - [[atomic-redis-rate-limiter-failopen-failclosed]] — Fail-open/closed стратегия - [[architecture-overview]] — общий обзор и таблица P0-P2 - [[MOC-security-patterns]] — карта безопасности - [[decision-validation-error-production]] — скрытие ошибок ## Источник Code review PhotoPlaces 2026-06. Проверка production readiness + обновление после внедрения Caddy. ## Теги #deploy #docker #production #security #monitoring #best-practice