diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9768b3b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.github +**/node_modules +**/dist +.env +.env.* +!.env.example +docker-compose.stack.env +**/*.md +.cursor +*.log diff --git a/.gitignore b/.gitignore index 4c13be9..a82fb9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules/ dist/ .env +docker-compose.stack.env *.log *plan* *PLAN* \ No newline at end of file diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 0000000..92ace8e --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,22 @@ +# Сборка из корня монорепо: docker build -f Dockerfile.backend . +FROM node:20-alpine AS deps +WORKDIR /app +COPY backend/package.json backend/package-lock.json ./ +RUN npm ci + +FROM deps AS build +COPY backend/tsconfig.json ./ +COPY backend/src ./src +RUN npm run build + +FROM node:20-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production +COPY backend/package.json backend/package-lock.json ./ +RUN npm ci --omit=dev +COPY --from=build /app/dist ./dist +COPY backend/migrations ./migrations +COPY import ./import +EXPOSE 3000 +ENV PORT=3000 +CMD ["node", "dist/index.js"] diff --git a/Dockerfile.frontend b/Dockerfile.frontend new file mode 100644 index 0000000..526f68a --- /dev/null +++ b/Dockerfile.frontend @@ -0,0 +1,15 @@ +# Сборка из корня монорепо: docker build -f Dockerfile.frontend . +# SPA дергает API по префиксу /api (nginx проксирует на сервис backend:3000). +FROM node:20-alpine AS build +WORKDIR /app +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci +COPY frontend/ ./ +ARG VITE_API_BASE_URL=/api +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL +RUN npm run build + +FROM nginx:alpine +COPY --from=build /app/dist /usr/share/nginx/html +COPY docker/nginx.frontend.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 diff --git a/docker-compose.stack.env.example b/docker-compose.stack.env.example new file mode 100644 index 0000000..ee11060 --- /dev/null +++ b/docker-compose.stack.env.example @@ -0,0 +1,11 @@ +# Скопируйте в docker-compose.stack.env и подставьте свои значения. +# Файл docker-compose.stack.env не должен попадать в git (см. .gitignore). + +DB_HOST=postgres +DB_PORT=5432 +DB_NAME=calendar_run +DB_USER=calendar_user +DB_PASSWORD=replace_with_strong_secret + +# Origin браузера для CORS (если заходите на фронт не с localhost — поменяйте) +CORS_ORIGIN=http://localhost:3033 diff --git a/docker-compose.stack.yml b/docker-compose.stack.yml new file mode 100644 index 0000000..7cefe19 --- /dev/null +++ b/docker-compose.stack.yml @@ -0,0 +1,47 @@ +# Запуск приложения в сети с уже поднятым PostgreSQL (как у family-budget). +# +# 1) Скопируйте пример переменных (не коммитьте файл с паролями): +# cp docker-compose.stack.env.example docker-compose.stack.env +# отредактируйте docker-compose.stack.env +# +# NPM / reverse proxy: проброс на порт фронта 3033 (внутри контейнера nginx слушает 80). +# Фронт в браузере ходит на /api → nginx проксирует на backend:3000. +# +# Первая инициализация БД (один раз, когда Postgres уже доступен по DB_HOST): +# docker compose -f docker-compose.stack.yml exec backend node dist/migrate.js +# docker compose -f docker-compose.stack.yml exec backend node dist/seed.js + +services: + backend: + build: + context: . + dockerfile: Dockerfile.backend + container_name: runners-calendar-backend + env_file: + - docker-compose.stack.env + environment: + - PORT=3000 + ports: + - "3001:3000" + restart: unless-stopped + networks: + - postgres_default + + frontend: + build: + context: . + dockerfile: Dockerfile.frontend + args: + VITE_API_BASE_URL: /api + container_name: runners-calendar-frontend + depends_on: + - backend + ports: + - "3033:80" + restart: unless-stopped + networks: + - postgres_default + +networks: + postgres_default: + external: true diff --git a/docker/nginx.frontend.conf b/docker/nginx.frontend.conf new file mode 100644 index 0000000..9831e27 --- /dev/null +++ b/docker/nginx.frontend.conf @@ -0,0 +1,21 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + # Браузер ходит на тот же origin: /api/* → бэкенд без префикса /api + location /api/ { + rewrite ^/api/(.*) /$1 break; + proxy_pass http://backend:3000; + proxy_http_version 1.1; + 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; + } +}