diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index d3fa70d..02d79e5 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -124,6 +124,80 @@ Configure these DNS A records pointing to your VPS IP: --- +## Quick Deploy (Docker Compose) + +Deploy all services in one step using Docker Compose. + +### Step 1: Create Service + +1. In `production` environment, click **+ New Resource** +2. Select **Docker Compose** (not Dockerfile) +3. Name: `placebo-stack` +4. Repository: Select your connected repo +5. Compose File: `docker-compose.coolify.yml` + +### Step 2: Set Environment Variables + +Click **Environment Variables** and add these secrets: + +| Variable | How to Generate | +|----------|-----------------| +| `DATABASE_PASSWORD` | `openssl rand -base64 32` | +| `JWT_SECRET` | `openssl rand -base64 64 \| tr -d '\n'` | +| `VAPID_PUBLIC_KEY` | Run `npm run generate-vapid` locally | +| `VAPID_PRIVATE_KEY` | Run `npm run generate-vapid` locally | +| `STRAPI_APP_KEYS` | See generation command below | +| `STRAPI_API_TOKEN_SALT` | `openssl rand -base64 32` | +| `STRAPI_ADMIN_JWT_SECRET` | `openssl rand -base64 32` | +| `STRAPI_TRANSFER_TOKEN_SALT` | `openssl rand -base64 32` | +| `STRAPI_JWT_SECRET` | `openssl rand -base64 32` | +| `STRAPI_API_TOKEN` | Get from CMS admin after first deploy | + +**Generate Strapi APP_KEYS** (4 comma-separated keys): +```bash +for i in {1..4}; do node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"; done | paste -sd "," - +``` + +**Generate VAPID Keys** (run locally): +```bash +cd backend +npm install +npm run generate-vapid +``` + +### Step 3: Configure Domains + +The compose file uses Traefik labels with these domains: +- `placebo.mk` - Frontend +- `www.placebo.mk` - Frontend (redirect) +- `api.placebo.mk` - Backend API +- `app.placebo.mk` - PWA +- `cms.placebo.mk` - CMS (Strapi) + +Ensure DNS is configured before deploying. + +### Step 4: Deploy + +1. Click **Deploy** +2. Wait for all services to start (2-5 minutes) +3. Check logs if any service fails + +### Step 5: Create CMS Admin + +1. Visit `https://cms.placebo.mk/admin` +2. Create your admin user +3. Generate API token for backend +4. Add `STRAPI_API_TOKEN` to environment variables +5. Redeploy + +--- + +## Manual Deploy (Individual Services) + +If you prefer deploying each service separately, follow these steps. + +--- + ## Database Configuration ### Create PostgreSQL Service @@ -141,7 +215,7 @@ Configure these DNS A records pointing to your VPS IP: | PostgreSQL Database | `placebo_db` | | Persistent Volume | Enabled | -4. Click **Deploy** +4. Click **Deploy` ### Note Connection Details @@ -156,7 +230,7 @@ Password: --- -## Service Deployments +## Service Deployments (Manual) ### 1. Backend (NestJS API) diff --git a/backend/Dockerfile b/backend/Dockerfile index 70e9be3..9198b7e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -9,8 +9,8 @@ WORKDIR /app COPY package*.json ./ COPY package-lock.json* ./ -# Install dependencies -RUN npm ci --only=production +# Install ALL dependencies (including devDependencies for build) +RUN npm ci # Copy source code COPY . . @@ -18,6 +18,9 @@ COPY . . # Build TypeScript RUN npm run build +# Prune devDependencies after build +RUN npm prune --production + # Production stage FROM node:20-alpine @@ -32,9 +35,6 @@ COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=nodejs:nodejs /app/package*.json ./ -# Copy environment configuration -COPY --chown=nodejs:nodejs .env.example .env - # Switch to non-root user USER nodejs @@ -46,4 +46,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ EXPOSE 3000 # Start application -CMD ["node", "dist/main.js"] \ No newline at end of file +CMD ["node", "dist/main.js"] diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml new file mode 100644 index 0000000..35ed969 --- /dev/null +++ b/docker-compose.coolify.yml @@ -0,0 +1,224 @@ +# Docker Compose for Coolify Deployment +# Deploy all services in one run +# +# Usage in Coolify: +# 1. Create new Docker Compose service +# 2. Point to this file +# 3. Set environment variables in Coolify UI +# 4. Deploy + +services: + # =========================================== + # DATABASES + # =========================================== + + postgres-backend: + image: postgres:16-alpine + container_name: placebo-postgres-backend + restart: unless-stopped + environment: + POSTGRES_DB: placebo_backend_db + POSTGRES_USER: placebo_user + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + volumes: + - placebo-postgres-backend-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U placebo_user -d placebo_backend_db'] + interval: 10s + timeout: 5s + retries: 5 + networks: + - placebo-internal + + postgres-cms: + image: postgres:16-alpine + container_name: placebo-postgres-cms + restart: unless-stopped + environment: + POSTGRES_DB: placebo_cms_db + POSTGRES_USER: placebo_user + POSTGRES_PASSWORD: ${DATABASE_PASSWORD} + volumes: + - placebo-postgres-cms-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U placebo_user -d placebo_cms_db'] + interval: 10s + timeout: 5s + retries: 5 + networks: + - placebo-internal + + # =========================================== + # BACKEND (NestJS API) + # =========================================== + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: placebo-backend + restart: unless-stopped + environment: + NODE_ENV: production + PORT: 3000 + DATABASE_TYPE: postgres + DATABASE_HOST: postgres-backend + DATABASE_PORT: 5432 + DATABASE_USERNAME: placebo_user + DATABASE_PASSWORD: ${DATABASE_PASSWORD} + DATABASE_NAME: placebo_backend_db + DATABASE_SYNCHRONIZE: 'false' + DATABASE_LOGGING: 'false' + JWT_SECRET: ${JWT_SECRET} + JWT_EXPIRATION: '86400' + CORS_ORIGIN: https://placebo.mk,https://www.placebo.mk,https://app.placebo.mk + STRAPI_URL: http://cms:1337 + STRAPI_API_TOKEN: ${STRAPI_API_TOKEN} + VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:contact@placebo.mk} + VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY} + VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY} + depends_on: + postgres-backend: + condition: service_healthy + healthcheck: + test: ['CMD', 'node', '-e', "require('http').get('http://localhost:3000/health', (r) => {if(r.statusCode !== 200) process.exit(1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - placebo-internal + labels: + - 'coolify.managed=true' + - 'traefik.enable=true' + - 'traefik.http.routers.placebo-backend.rule=Host(`api.placebo.mk`)' + - 'traefik.http.routers.placebo-backend.entrypoints=websecure' + - 'traefik.http.routers.placebo-backend.tls.certresolver=letsencrypt' + - 'traefik.http.services.placebo-backend.loadbalancer.server.port=3000' + + # =========================================== + # CMS (Strapi) + # =========================================== + + cms: + build: + context: ./cms/cms + dockerfile: Dockerfile + container_name: placebo-cms + restart: unless-stopped + environment: + NODE_ENV: production + HOST: 0.0.0.0 + PORT: 1337 + DATABASE_CLIENT: postgres + DATABASE_HOST: postgres-cms + DATABASE_PORT: 5432 + DATABASE_NAME: placebo_cms_db + DATABASE_USERNAME: placebo_user + DATABASE_PASSWORD: ${DATABASE_PASSWORD} + DATABASE_SSL: 'false' + APP_KEYS: ${STRAPI_APP_KEYS} + API_TOKEN_SALT: ${STRAPI_API_TOKEN_SALT} + ADMIN_JWT_SECRET: ${STRAPI_ADMIN_JWT_SECRET} + TRANSFER_TOKEN_SALT: ${STRAPI_TRANSFER_TOKEN_SALT} + JWT_SECRET: ${STRAPI_JWT_SECRET} + depends_on: + postgres-cms: + condition: service_healthy + volumes: + - placebo-cms-uploads:/app/public/uploads + healthcheck: + test: ['CMD', 'node', '-e', "require('http').get('http://localhost:1337/_health', (r) => {if(r.statusCode !== 200) process.exit(1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - placebo-internal + labels: + - 'coolify.managed=true' + - 'traefik.enable=true' + - 'traefik.http.routers.placebo-cms.rule=Host(`cms.placebo.mk`)' + - 'traefik.http.routers.placebo-cms.entrypoints=websecure' + - 'traefik.http.routers.placebo-cms.tls.certresolver=letsencrypt' + - 'traefik.http.services.placebo-cms.loadbalancer.server.port=1337' + + # =========================================== + # FRONTEND (React) + # =========================================== + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + VITE_API_URL: https://api.placebo.mk/api/v1 + VITE_CMS_URL: https://cms.placebo.mk + container_name: placebo-frontend + restart: unless-stopped + depends_on: + - backend + healthcheck: + test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:80/'] + interval: 30s + timeout: 10s + retries: 3 + networks: + - placebo-internal + labels: + - 'coolify.managed=true' + - 'traefik.enable=true' + - 'traefik.http.routers.placebo-frontend.rule=Host(`placebo.mk`) || Host(`www.placebo.mk`)' + - 'traefik.http.routers.placebo-frontend.entrypoints=websecure' + - 'traefik.http.routers.placebo-frontend.tls.certresolver=letsencrypt' + - 'traefik.http.services.placebo-frontend.loadbalancer.server.port=80' + + # =========================================== + # PWA (Progressive Web App) + # =========================================== + + pwa: + build: + context: ./pwa + dockerfile: Dockerfile + args: + VITE_API_URL: https://api.placebo.mk/api/v1 + VITE_CMS_URL: https://cms.placebo.mk + container_name: placebo-pwa + restart: unless-stopped + depends_on: + - backend + healthcheck: + test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:80/'] + interval: 30s + timeout: 10s + retries: 3 + networks: + - placebo-internal + labels: + - 'coolify.managed=true' + - 'traefik.enable=true' + - 'traefik.http.routers.placebo-pwa.rule=Host(`app.placebo.mk`)' + - 'traefik.http.routers.placebo-pwa.entrypoints=websecure' + - 'traefik.http.routers.placebo-pwa.tls.certresolver=letsencrypt' + - 'traefik.http.services.placebo-pwa.loadbalancer.server.port=80' + +# =========================================== +# VOLUMES (Managed by Coolify) +# =========================================== + +volumes: + placebo-postgres-backend-data: + driver: local + placebo-postgres-cms-data: + driver: local + placebo-cms-uploads: + driver: local + +# =========================================== +# NETWORKS +# =========================================== + +networks: + placebo-internal: + driver: bridge