From 8db46f40274c6ece19db71934e9dd495eb23c073 Mon Sep 17 00:00:00 2001 From: XFlow Deploy Date: Mon, 28 Sep 2026 08:59:07 +0500 Subject: [PATCH] feat(deploy): add Coolify docker-compose, multi-stage Dockerfile, and env template matching XCargo and Signage patterns --- .dockerignore | 18 ++++++ .env.example.coolify | 38 +++++++++++++ Dockerfile.coolify | 111 +++++++++++++++++++++++++++++++++++++ docker-compose.coolify.yml | 105 +++++++++++++++++++++++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.example.coolify create mode 100644 Dockerfile.coolify create mode 100644 docker-compose.coolify.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d56c42e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +node_modules +.output +.nuxt +node_modules/.cache +.git +.gitignore +.env +.env.* +!.env.example +!.env.example.coolify +dist +*.log +*.canvas + +# Exclude non-web monorepo apps from Docker build context +apps/mobile +apps/desktop +packages/shared-contracts/dist diff --git a/.env.example.coolify b/.env.example.coolify new file mode 100644 index 0000000..07a64b1 --- /dev/null +++ b/.env.example.coolify @@ -0,0 +1,38 @@ +# ======================================================== +# XFlow — Coolify Environment Variables Template +# ======================================================== +# В Coolify UI: +# 1. Set Compose file = docker-compose.coolify.yml +# 2. Set Dockerfile = Dockerfile.coolify +# 3. Скопируйте эти переменные в раздел Environment Variables проекта. + +APP_NAME=xflow +NODE_ENV=production +SITE_URL=https://xflow.yourdomain.com +TZ=Asia/Almaty + +# База данных PostgreSQL +POSTGRES_USER=xflow +POSTGRES_PASSWORD=generate-secure-password +POSTGRES_DB=xflow +DATABASE_URL=postgresql://xflow:${POSTGRES_PASSWORD}@db:5432/xflow + +# Redis (кеширование и фоновые задачи) +REDIS_URL=redis://redis:6379 + +# Безопасность и JWT +JWT_SECRET=generate-super-secret-jwt-key-minimum-32-chars + +# ИИ провайдеры (OpenRouter / Qwen / DeepSeek) +OPEN_ROUTER_API_KEY= +NUXT_OPEN_ROUTER_API_KEY= +NUXT_QWEN_API_KEY= +DEEPSEEK_API_KEY= + +# Firebase Push Notifications (Web Push PWA) +FIREBASE_API_KEY= +FIREBASE_AUTH_DOMAIN= +FIREBASE_PROJECT_ID= +FIREBASE_STORAGE_BUCKET= +FIREBASE_MESSAGING_SENDER_ID= +FIREBASE_APP_ID= diff --git a/Dockerfile.coolify b/Dockerfile.coolify new file mode 100644 index 0000000..e4fb8f7 --- /dev/null +++ b/Dockerfile.coolify @@ -0,0 +1,111 @@ +# ========================================== +# Stage 1: builder +# ========================================== +FROM node:22-alpine AS builder + +ENV HUSKY=0 +ENV NODE_ENV=development +ENV NODE_OPTIONS="--max-old-space-size=4096" + +WORKDIR /src + +RUN apk add --no-cache libc6-compat git openssl + +ARG SITE_URL="" +ARG DATABASE_URL="postgresql://xflow:xflow@db:5432/xflow" +ARG REDIS_URL="redis://redis:6379" +ARG JWT_SECRET="super-secret-jwt-key" +ARG OPEN_ROUTER_API_KEY="" +ARG NUXT_OPEN_ROUTER_API_KEY="" +ARG NUXT_QWEN_API_KEY="" +ARG DEEPSEEK_API_KEY="" +ARG FIREBASE_API_KEY="" +ARG FIREBASE_AUTH_DOMAIN="" +ARG FIREBASE_PROJECT_ID="" +ARG FIREBASE_STORAGE_BUCKET="" +ARG FIREBASE_MESSAGING_SENDER_ID="" +ARG FIREBASE_APP_ID="" +ARG TZ="Asia/Almaty" + +ENV SITE_URL=${SITE_URL} \ + DATABASE_URL=${DATABASE_URL} \ + NUXT_DATABASE_URL=${DATABASE_URL} \ + REDIS_URL=${REDIS_URL} \ + NUXT_REDIS_URL=${REDIS_URL} \ + JWT_SECRET=${JWT_SECRET} \ + NUXT_JWT_SECRET=${JWT_SECRET} \ + OPEN_ROUTER_API_KEY=${OPEN_ROUTER_API_KEY} \ + NUXT_OPEN_ROUTER_API_KEY=${NUXT_OPEN_ROUTER_API_KEY} \ + NUXT_QWEN_API_KEY=${NUXT_QWEN_API_KEY} \ + DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY} \ + FIREBASE_API_KEY=${FIREBASE_API_KEY} \ + NUXT_FIREBASE_API_KEY=${FIREBASE_API_KEY} \ + NUXT_PUBLIC_FIREBASE_API_KEY=${FIREBASE_API_KEY} \ + FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN} \ + NUXT_FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN} \ + NUXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${FIREBASE_AUTH_DOMAIN} \ + FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID} \ + NUXT_FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID} \ + NUXT_PUBLIC_FIREBASE_PROJECT_ID=${FIREBASE_PROJECT_ID} \ + FIREBASE_STORAGE_BUCKET=${FIREBASE_STORAGE_BUCKET} \ + NUXT_FIREBASE_STORAGE_BUCKET=${FIREBASE_STORAGE_BUCKET} \ + NUXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${FIREBASE_STORAGE_BUCKET} \ + FIREBASE_MESSAGING_SENDER_ID=${FIREBASE_MESSAGING_SENDER_ID} \ + NUXT_FIREBASE_MESSAGING_SENDER_ID=${FIREBASE_MESSAGING_SENDER_ID} \ + NUXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${FIREBASE_MESSAGING_SENDER_ID} \ + FIREBASE_APP_ID=${FIREBASE_APP_ID} \ + NUXT_FIREBASE_APP_ID=${FIREBASE_APP_ID} \ + NUXT_PUBLIC_FIREBASE_APP_ID=${FIREBASE_APP_ID} \ + TZ=${TZ} + +# Копируем package.json и prisma схемы из apps/web +COPY apps/web/package*.json ./ +COPY apps/web/prisma ./prisma +RUN npm ci --ignore-scripts && npx prisma generate + +# Копируем исходный код apps/web +COPY apps/web ./ +RUN HUSKY=0 NODE_ENV=development npx nuxi prepare && HUSKY=0 NODE_ENV=development npm run build + +# ========================================== +# Stage 2: runner +# ========================================== +FROM node:22-alpine AS runner + +ENV HUSKY=0 +ENV NODE_ENV=production + +WORKDIR /src + +RUN apk add --no-cache tzdata openssl + +ARG SITE_URL="" +ARG DATABASE_URL="postgresql://xflow:xflow@db:5432/xflow" +ARG REDIS_URL="redis://redis:6379" +ARG JWT_SECRET="super-secret-jwt-key" +ARG TZ="Asia/Almaty" + +ENV SITE_URL=${SITE_URL} \ + DATABASE_URL=${DATABASE_URL} \ + NUXT_DATABASE_URL=${DATABASE_URL} \ + REDIS_URL=${REDIS_URL} \ + NUXT_REDIS_URL=${REDIS_URL} \ + JWT_SECRET=${JWT_SECRET} \ + NUXT_JWT_SECRET=${JWT_SECRET} \ + TZ=${TZ} + +COPY --from=builder /src/.output ./.output +COPY --from=builder /src/prisma ./prisma +COPY --from=builder /src/package.json ./ +COPY --from=builder /src/node_modules ./node_modules + +ENV HOST=0.0.0.0 +ENV PORT=3000 +ENV NUXT_HOST=0.0.0.0 +ENV NUXT_PORT=3000 +ENV NITRO_HOST=0.0.0.0 +ENV NITRO_PORT=3000 + +EXPOSE 3000 + +CMD ["sh", "-c", "npx prisma db push --accept-data-loss --skip-generate && exec node .output/server/index.mjs"] diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml new file mode 100644 index 0000000..aa938af --- /dev/null +++ b/docker-compose.coolify.yml @@ -0,0 +1,105 @@ +# Coolify entrypoint for XFlow. In Coolify UI set Compose file = docker-compose.coolify.yml +# Do not publish host ports — Traefik reaches the container on 3000 internally. + +version: '3.8' + +services: + site-xflow: + container_name: '${APP_NAME:-xflow}' + image: '${APP_NAME:-xflow}:latest' + build: + context: . + dockerfile: Dockerfile.coolify + args: + SITE_URL: '${SITE_URL:-http://localhost:3000}' + DATABASE_URL: '${DATABASE_URL:-postgresql://xflow:${POSTGRES_PASSWORD}@db:5432/xflow}' + REDIS_URL: '${REDIS_URL:-redis://redis:6379}' + JWT_SECRET: '${JWT_SECRET:-super-secret-jwt-key}' + OPEN_ROUTER_API_KEY: '${OPEN_ROUTER_API_KEY:-}' + NUXT_OPEN_ROUTER_API_KEY: '${OPEN_ROUTER_API_KEY:-}' + NUXT_QWEN_API_KEY: '${NUXT_QWEN_API_KEY:-}' + DEEPSEEK_API_KEY: '${DEEPSEEK_API_KEY:-}' + FIREBASE_API_KEY: '${FIREBASE_API_KEY:-}' + FIREBASE_AUTH_DOMAIN: '${FIREBASE_AUTH_DOMAIN:-}' + FIREBASE_PROJECT_ID: '${FIREBASE_PROJECT_ID:-}' + FIREBASE_STORAGE_BUCKET: '${FIREBASE_STORAGE_BUCKET:-}' + FIREBASE_MESSAGING_SENDER_ID: '${FIREBASE_MESSAGING_SENDER_ID:-}' + FIREBASE_APP_ID: '${FIREBASE_APP_ID:-}' + TZ: '${TZ:-Asia/Almaty}' + depends_on: + - db + - redis + restart: unless-stopped + expose: + - '3000' + environment: + NODE_ENV: '${NODE_ENV:-development}' + HUSKY: '0' + HOST: '0.0.0.0' + PORT: '3000' + NUXT_HOST: '0.0.0.0' + NUXT_PORT: '3000' + NITRO_HOST: '0.0.0.0' + NITRO_PORT: '3000' + TZ: '${TZ:-Asia/Almaty}' + + SITE_URL: '${SITE_URL:-http://localhost:3000}' + NUXT_SITE_URL: '${SITE_URL:-http://localhost:3000}' + NUXT_PUBLIC_SITE_URL: '${SITE_URL:-http://localhost:3000}' + + POSTGRES_USER: '${POSTGRES_USER:-xflow}' + POSTGRES_DB: '${POSTGRES_DB:-xflow}' + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}' + DATABASE_URL: '${DATABASE_URL:-postgresql://xflow:${POSTGRES_PASSWORD}@db:5432/xflow}' + NUXT_DATABASE_URL: '${DATABASE_URL:-postgresql://xflow:${POSTGRES_PASSWORD}@db:5432/xflow}' + + REDIS_URL: '${REDIS_URL:-redis://redis:6379}' + NUXT_REDIS_URL: '${REDIS_URL:-redis://redis:6379}' + + JWT_SECRET: '${JWT_SECRET:-super-secret-jwt-key}' + NUXT_JWT_SECRET: '${JWT_SECRET:-super-secret-jwt-key}' + + OPEN_ROUTER_API_KEY: '${OPEN_ROUTER_API_KEY:-}' + NUXT_OPEN_ROUTER_API_KEY: '${OPEN_ROUTER_API_KEY:-}' + NUXT_QWEN_API_KEY: '${NUXT_QWEN_API_KEY:-}' + DEEPSEEK_API_KEY: '${DEEPSEEK_API_KEY:-}' + + FIREBASE_API_KEY: '${FIREBASE_API_KEY:-}' + NUXT_FIREBASE_API_KEY: '${FIREBASE_API_KEY:-}' + NUXT_PUBLIC_FIREBASE_API_KEY: '${FIREBASE_API_KEY:-}' + FIREBASE_AUTH_DOMAIN: '${FIREBASE_AUTH_DOMAIN:-}' + NUXT_FIREBASE_AUTH_DOMAIN: '${FIREBASE_AUTH_DOMAIN:-}' + NUXT_PUBLIC_FIREBASE_AUTH_DOMAIN: '${FIREBASE_AUTH_DOMAIN:-}' + FIREBASE_PROJECT_ID: '${FIREBASE_PROJECT_ID:-}' + NUXT_FIREBASE_PROJECT_ID: '${FIREBASE_PROJECT_ID:-}' + NUXT_PUBLIC_FIREBASE_PROJECT_ID: '${FIREBASE_PROJECT_ID:-}' + FIREBASE_STORAGE_BUCKET: '${FIREBASE_STORAGE_BUCKET:-}' + NUXT_FIREBASE_STORAGE_BUCKET: '${FIREBASE_STORAGE_BUCKET:-}' + NUXT_PUBLIC_FIREBASE_STORAGE_BUCKET: '${FIREBASE_STORAGE_BUCKET:-}' + FIREBASE_MESSAGING_SENDER_ID: '${FIREBASE_MESSAGING_SENDER_ID:-}' + NUXT_FIREBASE_MESSAGING_SENDER_ID: '${FIREBASE_MESSAGING_SENDER_ID:-}' + NUXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: '${FIREBASE_MESSAGING_SENDER_ID:-}' + FIREBASE_APP_ID: '${FIREBASE_APP_ID:-}' + NUXT_FIREBASE_APP_ID: '${FIREBASE_APP_ID:-}' + NUXT_PUBLIC_FIREBASE_APP_ID: '${FIREBASE_APP_ID:-}' + + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: '${POSTGRES_USER:-xflow}' + POSTGRES_PASSWORD: '${POSTGRES_PASSWORD:?}' + POSTGRES_DB: '${POSTGRES_DB:-xflow}' + volumes: + - xflow-pg-data:/var/lib/postgresql/data + + redis: + image: redis:7.2-alpine + restart: unless-stopped + command: redis-server --appendonly yes + volumes: + - xflow-redis-data:/data + +volumes: + xflow-pg-data: + xflow-redis-data: