Skip to content

Docker-деплой

Развёртывание можно. в Docker для продакшен-окружения: полный docker-compose.yml, переменные окружения, проверки здоровья, ресурсные ограничения, безопасность и сетевые настройки.

Образ

Официальный образ публикуется на Docker Hub:

mozhnodev/mozhno:latest

Образ собирается по трёхэтапному Dockerfile:

ЭтапБазовый образНазначение
web-buildernode:24-alpineСборка React 19 SPA фронтенда
java-buildereclipse-temurin:25-jdk-alpineКомпиляция Spring Boot JAR
runtimeeclipse-temurin:25-jre-nobleФинальный образ: только JRE

Финальный образ содержит только JRE, собранный JAR и статические файлы фронтенда — без JDK и Node.js.

Полный docker-compose.yml

yaml
services:
  postgres:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: feature_flags
      POSTGRES_USER: flags_user
      POSTGRES_PASSWORD: ${DB_PASSWORD:-flags_password}
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - mozhno-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U flags_user -d feature_flags"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: '1.0'
        reservations:
          memory: 256M
          cpus: '0.25'

  mozhno:
    image: mozhnodev/mozhno:latest
    restart: unless-stopped
    ports:
      - '${MOZHNO_SERVER_PORT:-8080}:8080'
    user: '1000:1000'
    read_only: true
    tmpfs:
      - /tmp:size=128M,mode=1777
    environment:
      MOZHNO_SERVER_PORT: '8080'
      MOZHNO_BASE_URL: ${MOZHNO_BASE_URL:-http://localhost:8080}

      MOZHNO_DB_URL: jdbc:postgresql://postgres:5432/feature_flags
      MOZHNO_DB_USERNAME: ${DB_USERNAME:-flags_user}
      MOZHNO_DB_PASSWORD: ${DB_PASSWORD:-flags_password}

      MOZHNO_DB_POOL_MAX_SIZE: '30'
      MOZHNO_DB_POOL_MIN_IDLE: '5'
      MOZHNO_DB_POOL_CONNECTION_TIMEOUT: '10000'

      MOZHNO_JWT_SECRET: ${MOZHNO_JWT_SECRET}
      MOZHNO_JWT_ACCESS_TOKEN_TTL_MINUTES: '15'
      MOZHNO_JWT_REFRESH_TOKEN_TTL_DAYS: '30'
      MOZHNO_JWT_ISSUER: 'mozhno'

      MOZHNO_INIT_EMAIL: ${MOZHNO_INIT_EMAIL:-}
      MOZHNO_INIT_PASSWORD: ${MOZHNO_INIT_PASSWORD:-}

      JAVA_TOOL_OPTIONS: >
        -XX:+UseZGC
        -XX:MaxRAMPercentage=75.0
        -XX:+ExitOnOutOfMemoryError
        -Djava.security.egd=file:/dev/./urandom
    networks:
      - mozhno-net
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://localhost:9090/actuator/health | grep -q UP"]
      interval: 15s
      timeout: 5s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '2.0'
        reservations:
          memory: 512M
          cpus: '0.25'

volumes:
  pgdata:
    driver: local

networks:
  mozhno-net:
    driver: bridge

Запуск:

bash
# JWT-секрет опционален для dev — без него генерируется случайный ключ
# Для продакшена задайте явно:
# Один из вариантов — сгенерировать Base64-ключ:
MOZHNO_JWT_SECRET=$(openssl rand -base64 32) docker compose up -d
# Либо любая строка >= 32 символов:
# export MOZHNO_JWT_SECRET="your-strong-plain-text-secret" && docker compose up -d

Переменные окружения

База данных

ПеременнаяПо умолчаниюОписание
MOZHNO_DB_URLjdbc:postgresql://localhost:5432/feature_flagsJDBC URL подключения к PostgreSQL
MOZHNO_DB_USERNAMEflags_userПользователь базы данных
MOZHNO_DB_PASSWORDflags_passwordПароль базы данных
MOZHNO_DB_POOL_MAX_SIZE20Максимальное число соединений. Для продакшена — 30
MOZHNO_DB_POOL_MIN_IDLE5Минимальное число простаивающих соединений
MOZHNO_DB_POOL_CONNECTION_TIMEOUT10000Таймаут ожидания соединения из пула (мс)

Сервер

ПеременнаяПо умолчаниюОписание
MOZHNO_SERVER_PORT8080Порт HTTP-сервера
MOZHNO_MANAGEMENT_PORT9090Порт management-эндпоинтов (actuator: health, metrics, prometheus)
MOZHNO_BASE_URLhttp://localhost:8080Публичный URL. Влияет на CORS и генерацию ссылок
MOZHNO_CACHE_TTL_MINUTES5Время жизни кеша правил в минутах
MOZHNO_CLIENT_MAX_METRICS_PER_KEY1000Максимум хранимых метрик на API-ключ
MOZHNO_CLIENT_MAX_METRICS_BATCH_SIZE1000Максимум записей в одном запросе /api/client/metrics

JWT

ПеременнаяПо умолчаниюОписание
MOZHNO_JWT_SECRET— (опционально для dev)Секретный ключ подписи JWT. Минимум 256 бит. Если не задан — генерируется случайный ключ (все токены инвалидируются при рестарте)
MOZHNO_JWT_ACCESS_TOKEN_TTL_MINUTES15Время жизни access-токена в минутах
MOZHNO_JWT_REFRESH_TOKEN_TTL_DAYS30Время жизни refresh-токена в днях

Flyway

ПеременнаяПо умолчаниюОписание
MOZHNO_FLYWAY_ENABLEDtrueАвтоматический запуск миграций при старте
MOZHNO_FLYWAY_LOCATIONSclasspath:db/migrationПуть к SQL-файлам миграций

SMTP (почта)

ПеременнаяПо умолчаниюОписание
MOZHNO_SMTP_HOSTlocalhostХост SMTP-сервера
MOZHNO_SMTP_PORT587Порт SMTP-сервера
MOZHNO_SMTP_USERNAMEИмя пользователя SMTP
MOZHNO_SMTP_PASSWORDПароль SMTP
MOZHNO_MAIL_FROMnoreply@mozhno.devАдрес отправителя писем

Прочее

ПеременнаяПо умолчаниюОписание
MOZHNO_CACHE_TYPEcaffeineТип кеша
MOZHNO_SECURITY_CORS_ALLOWED_ORIGINS""Разрешённые origin для CORS
MOZHNO_JWT_ISSUERmozhnoИздатель JWT-токенов
MOZHNO_AUDIT_RETENTION_DAYS365Срок хранения записей аудита

Инициализация (первый запуск)

При первом запуске с пустой базой данных сервер автоматически создаёт:

ПеременнаяПо умолчаниюОписание
MOZHNO_INIT_EMAILEmail администратора. Если не задана — администратор не создаётся
MOZHNO_INIT_PASSWORDПароль администратора
  • Администратора — если заданы обе переменные и в БД нет пользователей
  • Проект «Default Project» — если в БД нет проектов

Если переменные не заданы — сервер стартует без пользователей. Войти будет невозможно. Для первого входа задайте их в .env или environment.

Проверки здоровья (Health Checks)

Контейнер можно. предоставляет эндпоинт /actuator/health на management-порту 9090 для проверки готовности:

bash
curl http://localhost:9090/actuator/health
# {"status":"UP","components":{"db":{"status":"UP"},"diskSpace":{"status":"UP"}}}

В docker-compose.yml настроен healthcheck с проверкой этого эндпоинта каждые 15 секунд. Контейнер считается готовым, когда:

  • Приложение запущено и слушает порт 8080 (SPA + REST API) и порт 9090 (actuator)
  • Подключение к PostgreSQL установлено и проверено (db: UP)
  • Flyway-миграции успешно применены
  • Статические ресурсы (React SPA) доступны

PostgreSQL проверяется утилитой pg_isready каждые 10 секунд.

Ресурсные ограничения

КомпонентПараметрDevelopmentProduction
PostgreSQLCPU limit1.02.0
PostgreSQLMemory limit1G2G
можно.CPU limit2.04.0
можно.Memory limit2G4G
можно.Memory reservation512M1G

Настройки JVM для контейнера:

-XX:+UseZGC                           — Z Garbage Collector (низкие паузы)
-XX:MaxRAMPercentage=75.0             — JVM использует не более 75% памяти контейнера
-Djava.security.egd=file:/dev/./urandom  — ускорение генерации случайных чисел

Сетевая конфигурация

Сервисы объединены в изолированную сеть mozhno-net типа bridge. Приложение обращается к PostgreSQL по контейнерному имени postgres:

jdbc:postgresql://postgres:5432/feature_flags

Если PostgreSQL развёрнут на отдельном хосте, замените postgres на IP-адрес или домен хоста и удалите сервис postgres из docker-compose.yml.

Для продакшен-окружения с обратным прокси (Nginx, Traefik, Caddy) выставьте порт 8080 только на 127.0.0.1:

yaml
ports:
  - '127.0.0.1:8080:8080'

Безопасность

Не-root пользователь

Контейнер запускается от непривилегированного пользователя mozhno (UID 1000):

yaml
user: '1000:1000'

Файловая система только для чтения

Корневая файловая система монтируется как read-only:

yaml
read_only: true

Для временных файлов выделяется tmpfs:

yaml
tmpfs:
  - /tmp:size=128M,mode=1777

Секреты

Для продакшена не задавайте MOZHNO_JWT_SECRET и пароли базы данных напрямую в docker-compose.yml. Используйте:

  • Переменные окружения хоста (${MOZHNO_JWT_SECRET})
  • Docker Secrets (в Swarm-режиме)
  • Внешний менеджер секретов (HashiCorp Vault, AWS Secrets Manager)

Генерация криптографически стойкого JWT-секрета (один из вариантов; также подойдёт простая строка >= 32 символов):

bash
openssl rand -base64 32

Сканирование уязвимостей

Перед развёртыванием проверьте образ сканером:

bash
docker scout quickview mozhnodev/mozhno:latest
trivy image mozhnodev/mozhno:latest

Сборка образа локально

Если требуется собрать образ из исходников:

bash
make docker-build

Или вручную:

bash
docker build -t mozhnodev/mozhno:latest .

Dockerfile использует многоэтапную сборку (multi-stage build), поэтому итоговый образ не содержит Node.js, npm-зависимости или JDK — только JRE и артефакты.

Миграции при старте

Flyway-миграции запускаются автоматически при старте контейнера (MOZHNO_FLYWAY_ENABLED=true). Если база данных недоступна, контейнер завершится с ошибкой и Docker перезапустит его (restart: unless-stopped). Миграции идемпотентны — повторный запуск не повредит данных.

Где брать образ

  • Docker Hub: mozhnodev/mozhno:latest
  • Теги версий: mozhnodev/mozhno:v1.0.0
  • Digest (для неизменяемости): mozhnodev/mozhno@sha256:...

Рекомендуется фиксировать конкретную версию или digest для продакшен-окружения, чтобы избежать неожиданных изменений.

Обновление версии

Для обновления на новую версию:

bash
# 1. Обновить тег образа в docker-compose.yml
#    image: mozhnodev/mozhno:v1.1.0

# 2. Загрузить новый образ и перезапустить
docker compose pull mozhno
docker compose up -d mozhno

# 3. Flyway автоматически применит новые миграции при старте

Процесс безопасен: старый контейнер работает до готовности нового. Health check гарантирует, что трафик пойдёт только после успешного старта.

Откат версии

bash
# Вернуть тег старой версии в docker-compose.yml
docker compose pull mozhno
docker compose up -d mozhno

Flyway-миграции не откатываются автоматически. Если новая версия добавила миграции, откат кода безопасен (миграции совместимы вперёд).

Обратный прокси и TLS

Для продакшена всегда размещайте можно. за обратным прокси с HTTPS.

Nginx

nginx
server {
    listen 443 ssl http2;
    server_name flags.example.com;

    ssl_certificate     /etc/letsencrypt/live/flags.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/flags.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }
}

В docker-compose.yml закройте порт от внешнего доступа:

yaml
ports:
  - '127.0.0.1:8080:8080'

И установите MOZHNO_BASE_URL на ваш домен:

yaml
MOZHNO_BASE_URL: https://flags.example.com

Forwarded-заголовки и реальный IP клиента

Сервер определяет IP клиента (для rate-limit) через forwarded-заголовки. По умолчанию MOZHNO_FORWARD_HEADERS_STRATEGY=native: X-Forwarded-For учитывается только если запрос пришёл от доверенного прокси из приватных диапазонов (Tomcat RemoteIpValve). Так прямой атакующий не подделает свой IP заголовком.

Если ваш обратный прокси работает на публичном IP, укажите его диапазон явно:

yaml
SERVER_TOMCAT_REMOTEIP_INTERNAL_PROXIES: '203\.0\.113\.\d{1,3}'

Если прокси нет и клиенты ходят напрямую — поставьте MOZHNO_FORWARD_HEADERS_STRATEGY=none, чтобы заголовки X-Forwarded-* полностью игнорировались.

Caddy (автоматический TLS)

flags.example.com {
    reverse_proxy localhost:8080
}

Чеклист для продакшена

#ДействиеКоманда / переменная
0Создать администратора (первый запуск)MOZHNO_INIT_EMAIL, MOZHNO_INIT_PASSWORD → после входа сменить пароль
1Сгенерировать JWT-секретopenssl rand -base64 32 (один из вариантов; простая строка >= 32 символов тоже подойдёт) → MOZHNO_JWT_SECRET
2Сложный пароль БДMOZHNO_DB_PASSWORD
3Указать реальный доменMOZHNO_BASE_URL=https://flags.example.com
4Настроить CORSMOZHNO_SECURITY_CORS_ALLOWED_ORIGINS=https://app.example.com
5Закрыть порт от внешнего доступаports: ['127.0.0.1:8080:8080']
6Поставить TLS через Nginx/Caddy/TraefikСм. секцию выше
7Увеличить пул соединенийMOZHNO_DB_POOL_MAX_SIZE=30
8Настроить SMTP для писемMOZHNO_SMTP_HOST, MOZHNO_SMTP_PORT, MOZHNO_SMTP_USERNAME, MOZHNO_SMTP_PASSWORD
9Фиксировать версию образаimage: mozhnodev/mozhno:v1.0.0
10Настроить бэкап PostgreSQLpg_dump или WAL-архивация, см. База данных
11Настроить мониторингPrometheus, алерты — см. Мониторинг

Что дальше?

Released under the BSL 1.1 License.