From 7e382d460be296b6c88e17084fff17bb7728f6ea Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 16 Sep 2026 09:42:13 +0300 Subject: [PATCH] feat: add containerized application delivery #11 --- .env.compose.example | 12 +++++++++ .env.development.example | 10 ++++++++ .gitignore | 4 +++ README.md | 31 ++++++++++++++++++++++- compose.development.yaml | 53 ++++++++++++++++++++++++++++++++++++++++ compose.yaml | 51 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 .env.compose.example create mode 100644 .env.development.example create mode 100644 compose.development.yaml create mode 100644 compose.yaml diff --git a/.env.compose.example b/.env.compose.example new file mode 100644 index 0000000..d9639ab --- /dev/null +++ b/.env.compose.example @@ -0,0 +1,12 @@ +# Copy to a restricted, untracked file. Never commit filled values. +# Full release image references; use published version tags, not latest. +FRONTEND_IMAGE=git.personal.ksv741.ru/wealthledger/frontend:0.1.0 +BACKEND_IMAGE=git.personal.ksv741.ru/wealthledger/backend:0.1.0 +FRONTEND_PORT=8080 +FRONTEND_ORIGIN=https://wealthledger.example.com +# Absolute directory, pre-created with owner UID/GID 1000:1000 and mode 0700. +DATA_DIRECTORY=/srv/wealthledger/data +# Generate once and keep across updates. Store an independent key backup. +JWT_SECRET= +TOKEN_ENCRYPTION_KEY= +LOG_LEVEL=info diff --git a/.env.development.example b/.env.development.example new file mode 100644 index 0000000..6ea0252 --- /dev/null +++ b/.env.development.example @@ -0,0 +1,10 @@ +# Local-only fixture values. Never use this file for a server deployment. +# Base-file interpolation happens before overrides; these references are replaced. +FRONTEND_IMAGE=wealthledger-frontend:development +BACKEND_IMAGE=wealthledger-backend:development +DATA_DIRECTORY=/unused-development-volume +FRONTEND_PORT=5173 +FRONTEND_ORIGIN=http://localhost:5173 +JWT_SECRET=local-development-only-not-a-server-secret +TOKEN_ENCRYPTION_KEY=MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY= +LOG_LEVEL=info diff --git a/.gitignore b/.gitignore index b09f6dd..1dc0617 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ !/.gitignore !/AGENTS.md !/README.md +!/compose.yaml +!/compose.development.yaml +!/.env.compose.example +!/.env.development.example !/.ai/ /.ai/* !/.ai/conventions/ diff --git a/README.md b/README.md index afb1ce0..b2f54da 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/specs.git specs Root отслеживает входные документы, собственные engineering conventions и исторический migration inventory в `.ai/`, npm/release-конфигурацию, changelog, -один CI workflow с commitlint. Точный allowlist — в `.gitignore`. +Compose configuration, безопасные env examples и один CI workflow с commitlint. Точный allowlist — в `.gitignore`. Остальные root-пути игнорируются по умолчанию: вложенные repos, `.env`, базы и WAL, импорты выписок, PDF, `references/`, `design/`, `todo/` и локальные настройки инструментов. @@ -53,6 +53,35 @@ inventory в `.ai/`, npm/release-конфигурацию, changelog, Не запускайте очистку workspace через `git clean -fdx`: ignored-пути содержат локальные данные. Правила tracking не удаляют файлы и вложенные `.git`. +## Запуск через Docker Compose + +Нужны Docker Engine и Compose >= 2.24.4, а для development — checkout +`frontend/` и `backend/` рядом с root. Компоненты собираются независимо: + +```bash +docker compose --env-file .env.development.example \ + -f compose.yaml -f compose.development.yaml up -d --build --wait --wait-timeout 180 +``` + +Откройте `http://localhost:5173`: Vite HMR и `/api/` используют один origin, +Nest работает в watch mode. Host `node_modules` и `.env` не монтируются. +Данные development хранятся отдельно в named volume `development-data` проекта `wealthledger-dev`; +обычные restart/down их сохраняют. Изменение dependencies/config требует rebuild. + +Production использует `compose.yaml` и restricted env по `.env.compose.example`: +две ссылки на опубликованные версии images, persistent absolute host data path, +постоянные JWT/encryption secrets и публичный HTTPS origin. Каталог данных +должен существовать и принадлежать UID/GID 1000:1000. Только frontend публикует +`127.0.0.1:8080`; серверный Caddy направляет туда весь домен. Backend port +остаётся внутренним, migration service использует тот же backend image. + +Полная процедура установки, независимых обновлений, stopped-backend backup +и explicit restore — [документация](https://git.personal.ksv741.ru/WealthLedger/docs), +страница `architecture/deployment.md`. +Перед backend update обязательны остановка, backup и успешная migration. +Image rollback не восстанавливает старую базу. Публикация component images +принадлежит их Gitea workflows; root CI остаётся только commitlint. + ## Подготовка и запуск Нужны nvm и Node из `.nvmrc` каждого npm-repo (сейчас 24.21.0, npm 11.19). diff --git a/compose.development.yaml b/compose.development.yaml new file mode 100644 index 0000000..7aa8755 --- /dev/null +++ b/compose.development.yaml @@ -0,0 +1,53 @@ +# Requires Compose >= 2.24.4 for !override. Always use a separate project name. +name: wealthledger-dev + +x-development-backend: &development-backend + image: wealthledger-backend:development + pull_policy: build + build: + context: ./backend + target: development + read_only: false + environment: + NODE_ENV: development + volumes: !override + - development-data:/data + +services: + migration: + <<: *development-backend + + backend: + <<: *development-backend + healthcheck: + test: [CMD, node, -e, 'fetch("http://127.0.0.1:3000/api/v1/auth/me").then(async r=>{await r.arrayBuffer();process.exit(r.status===401?0:1)}).catch(()=>process.exit(1))'] + interval: 10s + timeout: 3s + start_period: 20s + retries: 6 + volumes: !override + - development-data:/data + - ./backend/src:/app/src:ro + + frontend: + image: wealthledger-frontend:development + pull_policy: build + build: + context: ./frontend + target: development + read_only: false + volumes: + - ./frontend/src:/app/src:ro + - ./frontend/public:/app/public:ro + environment: + VITE_API_BASE_URL: '' + API_PROXY_TARGET: http://backend:3000 + healthcheck: + test: [CMD, node, -e, 'fetch("http://127.0.0.1:8080/").then(async r=>{await r.arrayBuffer();process.exit(r.ok?0:1)}).catch(()=>process.exit(1))'] + interval: 10s + timeout: 3s + start_period: 10s + retries: 6 + +volumes: + development-data: diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..654ff20 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,51 @@ +name: wealthledger + +x-backend: &backend + image: ${BACKEND_IMAGE:?Set versioned backend image} + environment: + NODE_ENV: production + PORT: '3000' + DATABASE_URL: file:/data/wealthledger.db + FRONTEND_ORIGIN: ${FRONTEND_ORIGIN:?Set browser-visible origin} + JWT_SECRET: ${JWT_SECRET:?Set persistent JWT secret} + TOKEN_ENCRYPTION_KEY: ${TOKEN_ENCRYPTION_KEY:?Set persistent encryption key} + LOG_LEVEL: ${LOG_LEVEL:-info} + volumes: + - type: bind + source: ${DATA_DIRECTORY:?Set persistent absolute host data directory} + target: /data + bind: + create_host_path: false + read_only: true + init: true + security_opt: [no-new-privileges:true] + cap_drop: [ALL] + +services: + migration: + <<: *backend + command: [node, node_modules/prisma/build/index.js, migrate, deploy] + restart: 'no' + healthcheck: + disable: true + + backend: + <<: *backend + scale: 1 + restart: unless-stopped + depends_on: + migration: + condition: service_completed_successfully + + frontend: + image: ${FRONTEND_IMAGE:?Set versioned frontend image} + ports: + - '127.0.0.1:${FRONTEND_PORT:-8080}:8080' + read_only: true + tmpfs: [/tmp] + security_opt: [no-new-privileges:true] + cap_drop: [ALL] + restart: unless-stopped + depends_on: + backend: + condition: service_healthy -- 2.54.0