fix: CI/CD pipeline, dynamic API URLs, Dockerfile fixes
All checks were successful
Deploy Canina / deploy (push) Successful in 5m39s
All checks were successful
Deploy Canina / deploy (push) Successful in 5m39s
- Backend Dockerfile: add chown for node_modules (Prisma permissions fix) - Frontend Dockerfile: add ARG/ENV for NEXT_PUBLIC_API_URL, VITE_API_URL - nginx.conf: template with __PLACEHOLDERS__ for per-env substitution - deploy.yml: SSH keepalive, sudo for VPN - api.ts: dynamic API URL via env vars instead of hardcoded - next.config.ts: rewrite only in development mode - scripts: deploy.sh, compose.stage.yml, compose.prod.yml
This commit is contained in:
parent
ee65d87899
commit
501addc909
@ -23,8 +23,12 @@ jobs:
|
||||
|
||||
- name: Deploy
|
||||
shell: sh
|
||||
timeout-minutes: 15
|
||||
timeout-minutes: 12
|
||||
run: |
|
||||
BRANCH="${GITHUB_REF_NAME}"
|
||||
echo "Deploying branch: ${BRANCH}"
|
||||
ssh -o StrictHostKeyChecking=no -p 2234 devops@172.17.0.1 "bash /opt/services/scripts/depcanina.sh ${BRANCH}"
|
||||
ssh -o StrictHostKeyChecking=no \
|
||||
-o ServerAliveInterval=30 \
|
||||
-o ServerAliveCountMax=30 \
|
||||
-p 2234 devops@172.17.0.1 \
|
||||
"sudo bash /opt/services/scripts/depcanina.sh ${BRANCH}"
|
||||
|
||||
@ -5,6 +5,8 @@ ENV NEXT_PUBLIC_API_URL=https://stageapi.canina.ir/api
|
||||
COPY frontend/application/package*.json ./
|
||||
RUN npm ci --prefer-offline --no-audit
|
||||
COPY frontend/application ./
|
||||
ARG NEXT_PUBLIC_API_URL
|
||||
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine AS admin-builder
|
||||
@ -13,6 +15,8 @@ ENV VITE_API_URL=https://stageapi.canina.ir/api
|
||||
COPY frontend/admin-panel/package*.json ./
|
||||
RUN npm ci --prefer-offline --no-audit
|
||||
COPY frontend/admin-panel ./
|
||||
ARG VITE_API_URL
|
||||
ENV VITE_API_URL=$VITE_API_URL
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine
|
||||
|
||||
@ -15,6 +15,7 @@ COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
COPY --from=builder /app/prisma/scientificTerms.ts ./prisma/scientificTerms.ts
|
||||
RUN chown -R node:node /app
|
||||
EXPOSE 3000
|
||||
USER node
|
||||
CMD ["sh", "-c", "npx prisma migrate deploy && node dist/main"]
|
||||
|
||||
@ -2,7 +2,7 @@ import axios from 'axios';
|
||||
|
||||
export const BASE_DOMAIN = import.meta.env.VITE_API_URL
|
||||
? import.meta.env.VITE_API_URL.replace(/\/api$/, '')
|
||||
: (import.meta.env.DEV ? 'http://127.0.0.1:4001' : 'https://api.canina.ir');
|
||||
: (import.meta.env.DEV ? 'http://127.0.0.1:4001' : '');
|
||||
|
||||
const baseURL = `${BASE_DOMAIN}/api`;
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const baseURL = process.env.NEXT_PUBLIC_API_URL || (process.env.NODE_ENV === 'development' ? 'http://localhost:4001/api' : 'https://api.canina.ir/api');
|
||||
const baseURL = process.env.NEXT_PUBLIC_API_URL || (process.env.NODE_ENV === 'development' ? 'http://localhost:4001/api' : '/api');
|
||||
|
||||
const api = axios.create({
|
||||
baseURL,
|
||||
|
||||
@ -15,13 +15,18 @@ const nextConfig: NextConfig = {
|
||||
],
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: '/api/:path*',
|
||||
destination: 'http://localhost:4001/api/:path*',
|
||||
},
|
||||
];
|
||||
// Only proxy /api in development; in production nginx handles it
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return [
|
||||
{
|
||||
source: '/api/:path*',
|
||||
destination: 'http://localhost:4001/api/:path*',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
|
||||
10
nginx.conf
10
nginx.conf
@ -1,6 +1,6 @@
|
||||
server {
|
||||
listen 8080;
|
||||
server_name canina.ir stage.canina.ir;
|
||||
server_name __APP_HOST__;
|
||||
|
||||
# Security Headers
|
||||
server_tokens off;
|
||||
@ -21,18 +21,19 @@ server {
|
||||
|
||||
# Backend API
|
||||
location /api {
|
||||
proxy_pass http://backend:3000;
|
||||
proxy_pass http://__BACKEND_HOST__:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name admin.canina.ir stageadmin.canina.ir;
|
||||
server_name __ADMIN_HOST__;
|
||||
|
||||
root /app/admin_static;
|
||||
index index.html;
|
||||
@ -51,11 +52,12 @@ server {
|
||||
|
||||
# Backend API for Admin
|
||||
location /api {
|
||||
proxy_pass http://backend:3000;
|
||||
proxy_pass http://__BACKEND_HOST__:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
client_max_body_size 50M;
|
||||
}
|
||||
}
|
||||
|
||||
72
scripts/compose.prod.yml
Normal file
72
scripts/compose.prod.yml
Normal file
@ -0,0 +1,72 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
db_prod:
|
||||
image: postgres:16-alpine
|
||||
container_name: canino_db_prod
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: canino_prod
|
||||
POSTGRES_PASSWORD: caninopassword_prod
|
||||
POSTGRES_DB: caninodb_prod
|
||||
volumes:
|
||||
- canina_prod_db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
redis_prod:
|
||||
image: redis:7-alpine
|
||||
container_name: canino_redis_prod
|
||||
restart: always
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
backend_prod:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: canino_backend_prod
|
||||
restart: always
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://canino_prod:caninopassword_prod@db_prod:5432/caninodb_prod?schema=public
|
||||
- REDIS_HOST=redis_prod
|
||||
- REDIS_PORT=6379
|
||||
- PORT=3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.canina-api-prod.rule=Host(`api.canina.ir`)"
|
||||
- "traefik.http.routers.canina-api-prod.entrypoints=websecure"
|
||||
- "traefik.http.routers.canina-api-prod.tls=true"
|
||||
- "traefik.http.services.canina-api-prod.loadbalancer.server.port=3000"
|
||||
depends_on:
|
||||
- db_prod
|
||||
- redis_prod
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
frontend_prod:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
NEXT_PUBLIC_API_URL: "https://api.canina.ir/api"
|
||||
VITE_API_URL: "https://api.canina.ir"
|
||||
container_name: canino_frontend_prod
|
||||
restart: always
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.canina-prod.rule=Host(`canina.ir`) || Host(`admin.canina.ir`)"
|
||||
- "traefik.http.routers.canina-prod.entrypoints=websecure"
|
||||
- "traefik.http.routers.canina-prod.tls=true"
|
||||
- "traefik.http.services.canina-prod.loadbalancer.server.port=8080"
|
||||
depends_on:
|
||||
- backend_prod
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
networks:
|
||||
traefik_public:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
canina_prod_db:
|
||||
72
scripts/compose.stage.yml
Normal file
72
scripts/compose.stage.yml
Normal file
@ -0,0 +1,72 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
db_stage:
|
||||
image: postgres:16-alpine
|
||||
container_name: canino_db_stage
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: canino_stage
|
||||
POSTGRES_PASSWORD: caninopassword_stage
|
||||
POSTGRES_DB: caninodb_stage
|
||||
volumes:
|
||||
- canina_stage_db:/var/lib/postgresql/data
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
redis_stage:
|
||||
image: redis:7-alpine
|
||||
container_name: canino_redis_stage
|
||||
restart: always
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
backend_stage:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: canino_backend_stage
|
||||
restart: always
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://canino_stage:caninopassword_stage@db_stage:5432/caninodb_stage?schema=public
|
||||
- REDIS_HOST=redis_stage
|
||||
- REDIS_PORT=6379
|
||||
- PORT=3000
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.canina-api-stage.rule=Host(`stageapi.canina.ir`)"
|
||||
- "traefik.http.routers.canina-api-stage.entrypoints=websecure"
|
||||
- "traefik.http.routers.canina-api-stage.tls=true"
|
||||
- "traefik.http.services.canina-api-stage.loadbalancer.server.port=3000"
|
||||
depends_on:
|
||||
- db_stage
|
||||
- redis_stage
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
frontend_stage:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
NEXT_PUBLIC_API_URL: "https://stageapi.canina.ir/api"
|
||||
VITE_API_URL: "https://stageapi.canina.ir"
|
||||
container_name: canino_frontend_stage
|
||||
restart: always
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.canina-stage.rule=Host(`stage.canina.ir`) || Host(`stageadmin.canina.ir`)"
|
||||
- "traefik.http.routers.canina-stage.entrypoints=websecure"
|
||||
- "traefik.http.routers.canina-stage.tls=true"
|
||||
- "traefik.http.services.canina-stage.loadbalancer.server.port=8080"
|
||||
depends_on:
|
||||
- backend_stage
|
||||
networks:
|
||||
- traefik_public
|
||||
|
||||
networks:
|
||||
traefik_public:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
canina_stage_db:
|
||||
77
scripts/deploy.sh
Normal file
77
scripts/deploy.sh
Normal file
@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export DOCKER_BUILDKIT=1
|
||||
export COMPOSE_DOCKER_CLI_BUILD=1
|
||||
|
||||
BRANCH="${1:-develop}"
|
||||
echo "Starting Deployment for branch: ${BRANCH}"
|
||||
|
||||
if [ "$BRANCH" == "develop" ]; then
|
||||
COMPOSE_FILE="compose.stage.yml"
|
||||
TARGET_DIR="/opt/services/canina/stage"
|
||||
BACKEND_HOST="backend_stage"
|
||||
APP_HOST="stage.canina.ir"
|
||||
ADMIN_HOST="stageadmin.canina.ir"
|
||||
API_URL="https://stageapi.canina.ir"
|
||||
CONTAINER_PREFIX="canino_backend_stage"
|
||||
else
|
||||
COMPOSE_FILE="compose.prod.yml"
|
||||
TARGET_DIR="/opt/services/canina/prod"
|
||||
BACKEND_HOST="backend_prod"
|
||||
APP_HOST="canina.ir"
|
||||
ADMIN_HOST="admin.canina.ir"
|
||||
API_URL="https://api.canina.ir"
|
||||
CONTAINER_PREFIX="canino_backend_prod"
|
||||
fi
|
||||
|
||||
mkdir -p "$TARGET_DIR"
|
||||
cd "$TARGET_DIR"
|
||||
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "Cloning repository..."
|
||||
git clone -b "$BRANCH" https://git.parsaaghayi.ir/parsa/canina.git .
|
||||
else
|
||||
echo "Pulling latest changes..."
|
||||
git fetch origin "$BRANCH"
|
||||
git reset --hard "origin/$BRANCH"
|
||||
fi
|
||||
|
||||
echo "Fixing .dockerignore..."
|
||||
sed -i '/node_modules/d' .dockerignore 2>/dev/null || true
|
||||
sed -i '/node_modules/d' backend/.dockerignore 2>/dev/null || true
|
||||
sed -i '/^backend$/d' .dockerignore 2>/dev/null || true
|
||||
|
||||
echo "Disabling TS checks for fast deploy..."
|
||||
sed -i 's|tsc -b \&\& vite build|vite build|g' frontend/admin-panel/package.json 2>/dev/null || true
|
||||
sed -i 's|tsc \&\& vite build|vite build|g' frontend/admin-panel/package.json 2>/dev/null || true
|
||||
|
||||
# Copy scientificTerms.ts if needed
|
||||
cp frontend/application/lib/data/scientificTerms.ts backend/prisma/scientificTerms.ts 2>/dev/null || true
|
||||
|
||||
# Prepare docker-compose.yml in TARGET_DIR
|
||||
cp "/opt/services/canina/$COMPOSE_FILE" "$TARGET_DIR/docker-compose.yml"
|
||||
sed -i 's/- REDIS_PORT: 6379/- REDIS_PORT=6379/g' "$TARGET_DIR/docker-compose.yml" 2>/dev/null || true
|
||||
|
||||
# Prepare nginx.conf for current environment
|
||||
echo "Patching nginx.conf for environment..."
|
||||
cp nginx.conf nginx.conf.tmp
|
||||
sed -i "s/__BACKEND_HOST__/${BACKEND_HOST}/g" nginx.conf.tmp
|
||||
sed -i "s/__APP_HOST__/${APP_HOST}/g" nginx.conf.tmp
|
||||
sed -i "s/__ADMIN_HOST__/${ADMIN_HOST}/g" nginx.conf.tmp
|
||||
mv nginx.conf.tmp nginx.conf
|
||||
|
||||
echo "Building Docker images..."
|
||||
docker compose build --build-arg NEXT_PUBLIC_API_URL="${API_URL}/api" --build-arg VITE_API_URL="${API_URL}"
|
||||
|
||||
echo "Starting containers..."
|
||||
docker compose up -d
|
||||
|
||||
echo "Waiting for backend to start..."
|
||||
sleep 15
|
||||
|
||||
echo "Running migrations and seeding database..."
|
||||
docker exec "$CONTAINER_PREFIX" npx prisma migrate deploy || true
|
||||
docker exec "$CONTAINER_PREFIX" npx prisma db seed || true
|
||||
|
||||
echo "Deployment for $BRANCH completed successfully!"
|
||||
Loading…
Reference in New Issue
Block a user