Update health check endpoints and refine TypeScript configuration

- Changed health check endpoint from `/api/health` to `/_health` in Dockerfile and docker-compose.yml for consistency.
- Removed the now obsolete health check route file from the API.
- Updated TypeScript configuration to exclude the `.next` directory, improving build performance and clarity.
- Enhanced documentation in proxy.ts to reflect the updated health check endpoint.
This commit is contained in:
barsa 2025-12-12 16:02:21 +09:00
parent 2266167467
commit 3d56f2895f
9 changed files with 78 additions and 30 deletions

View File

@ -98,7 +98,7 @@ ENV NODE_ENV=production \
# Health check for container orchestration # Health check for container orchestration
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "fetch('http://localhost:3000/api/health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))" CMD node -e "fetch('http://localhost:3000/_health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))"
ENTRYPOINT ["dumb-init", "--"] ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "apps/portal/server.js"] CMD ["node", "apps/portal/server.js"]

View File

@ -84,10 +84,10 @@ export const config = {
* - _next/static (static files) * - _next/static (static files)
* - _next/image (image optimization files) * - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files) * - favicon.ico, sitemap.xml, robots.txt (metadata files)
* - api/health (health check endpoint) * - _health (health check endpoint)
*/ */
{ {
source: "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|api/health).*)", source: "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|_health).*)",
missing: [ missing: [
{ type: "header", key: "next-router-prefetch" }, { type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" }, { type: "header", key: "purpose", value: "prefetch" },

View File

@ -9,6 +9,6 @@
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"] "exclude": ["node_modules", ".next"]
} }

View File

@ -23,7 +23,7 @@ docker images | grep portal
2. Name: `customer-portal` 2. Name: `customer-portal`
3. Paste contents of `docker-compose.yml` 3. Paste contents of `docker-compose.yml`
4. Scroll down to **Environment Variables** 4. Scroll down to **Environment Variables**
5. Copy variables from `stack.env.example` and fill in your production values 5. Copy variables from `env.example` and fill in your production values
### 3. Generate Secrets ### 3. Generate Secrets
@ -65,11 +65,11 @@ This setup uses `network_mode: bridge` with Docker `links` to avoid the iptables
### Service URLs (internal) ### Service URLs (internal)
| Service | Internal URL | | Service | Internal URL |
|----------|-------------------------| | -------- | -------------------------- |
| Backend | http://backend:4000 | | Backend | http://backend:4000 |
| Database | postgresql://database:5432 | | Database | postgresql://database:5432 |
| Redis | redis://cache:6379 | | Redis | redis://cache:6379 |
### Nginx/Plesk Proxy ### Nginx/Plesk Proxy
@ -83,6 +83,7 @@ Configure your domain to proxy to:
## Updating the Stack ## Updating the Stack
1. Load new images: 1. Load new images:
```bash ```bash
docker load < portal-frontend-latest.tar docker load < portal-frontend-latest.tar
docker load < portal-backend-latest.tar docker load < portal-backend-latest.tar

View File

@ -25,7 +25,13 @@ services:
links: links:
- backend - backend
healthcheck: healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:3000/api/health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))"] test:
[
"CMD",
"node",
"-e",
"fetch('http://localhost:3000/_health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))",
]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
start_period: 40s start_period: 40s
@ -46,36 +52,40 @@ services:
- APP_BASE_URL=${APP_BASE_URL} - APP_BASE_URL=${APP_BASE_URL}
- BFF_PORT=4000 - BFF_PORT=4000
- PORT=4000 - PORT=4000
# Database - use "database" as host (via links) # Database - use "database" as host (via links)
- DATABASE_URL=postgresql://${POSTGRES_USER:-portal}:${POSTGRES_PASSWORD}@database:5432/${POSTGRES_DB:-portal_prod}?schema=public - DATABASE_URL=postgresql://${POSTGRES_USER:-portal}:${POSTGRES_PASSWORD}@database:5432/${POSTGRES_DB:-portal_prod}?schema=public
# Redis - use "cache" as host (via links) # Redis - use "cache" as host (via links)
- REDIS_URL=redis://cache:6379/0 - REDIS_URL=redis://cache:6379/0
# Security # Security
- JWT_SECRET=${JWT_SECRET} - JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-7d} - JWT_EXPIRES_IN=${JWT_EXPIRES_IN:-7d}
- JWT_SECRET_PREVIOUS=${JWT_SECRET_PREVIOUS}
- JWT_ISSUER=${JWT_ISSUER}
- JWT_AUDIENCE=${JWT_AUDIENCE}
- BCRYPT_ROUNDS=${BCRYPT_ROUNDS:-12} - BCRYPT_ROUNDS=${BCRYPT_ROUNDS:-12}
- CORS_ORIGIN=${CORS_ORIGIN} - CORS_ORIGIN=${CORS_ORIGIN}
- TRUST_PROXY=true - TRUST_PROXY=true
- CSRF_SECRET_KEY=${CSRF_SECRET_KEY} - CSRF_SECRET_KEY=${CSRF_SECRET_KEY}
# Auth # Auth
- AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=${AUTH_ALLOW_REDIS_TOKEN_FAILOPEN:-false} - AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=${AUTH_ALLOW_REDIS_TOKEN_FAILOPEN:-false}
- AUTH_REQUIRE_REDIS_FOR_TOKENS=${AUTH_REQUIRE_REDIS_FOR_TOKENS:-false} - AUTH_REQUIRE_REDIS_FOR_TOKENS=${AUTH_REQUIRE_REDIS_FOR_TOKENS:-false}
- AUTH_BLACKLIST_FAIL_CLOSED=${AUTH_BLACKLIST_FAIL_CLOSED:-false}
- AUTH_MAINTENANCE_MODE=${AUTH_MAINTENANCE_MODE:-false} - AUTH_MAINTENANCE_MODE=${AUTH_MAINTENANCE_MODE:-false}
# Rate Limiting # Rate Limiting
- RATE_LIMIT_TTL=${RATE_LIMIT_TTL:-60} - RATE_LIMIT_TTL=${RATE_LIMIT_TTL:-60}
- RATE_LIMIT_LIMIT=${RATE_LIMIT_LIMIT:-100} - RATE_LIMIT_LIMIT=${RATE_LIMIT_LIMIT:-100}
- EXPOSE_VALIDATION_ERRORS=false - EXPOSE_VALIDATION_ERRORS=false
# WHMCS # WHMCS
- WHMCS_BASE_URL=${WHMCS_BASE_URL} - WHMCS_BASE_URL=${WHMCS_BASE_URL}
- WHMCS_API_IDENTIFIER=${WHMCS_API_IDENTIFIER} - WHMCS_API_IDENTIFIER=${WHMCS_API_IDENTIFIER}
- WHMCS_API_SECRET=${WHMCS_API_SECRET} - WHMCS_API_SECRET=${WHMCS_API_SECRET}
# Salesforce # Salesforce
- SF_LOGIN_URL=${SF_LOGIN_URL} - SF_LOGIN_URL=${SF_LOGIN_URL}
- SF_CLIENT_ID=${SF_CLIENT_ID} - SF_CLIENT_ID=${SF_CLIENT_ID}
@ -83,25 +93,25 @@ services:
- SF_EVENTS_ENABLED=${SF_EVENTS_ENABLED:-true} - SF_EVENTS_ENABLED=${SF_EVENTS_ENABLED:-true}
- SF_PRIVATE_KEY_BASE64=${SF_PRIVATE_KEY_BASE64} - SF_PRIVATE_KEY_BASE64=${SF_PRIVATE_KEY_BASE64}
- SF_PRIVATE_KEY_PATH=/app/secrets/sf-private.key - SF_PRIVATE_KEY_PATH=/app/secrets/sf-private.key
# Freebit # Freebit
- FREEBIT_BASE_URL=${FREEBIT_BASE_URL:-https://i1.mvno.net/emptool/api} - FREEBIT_BASE_URL=${FREEBIT_BASE_URL:-https://i1.mvno.net/emptool/api}
- FREEBIT_OEM_ID=${FREEBIT_OEM_ID:-PASI} - FREEBIT_OEM_ID=${FREEBIT_OEM_ID:-PASI}
- FREEBIT_OEM_KEY=${FREEBIT_OEM_KEY} - FREEBIT_OEM_KEY=${FREEBIT_OEM_KEY}
# Email # Email
- EMAIL_ENABLED=${EMAIL_ENABLED:-true} - EMAIL_ENABLED=${EMAIL_ENABLED:-true}
- EMAIL_FROM=${EMAIL_FROM:-no-reply@asolutions.jp} - EMAIL_FROM=${EMAIL_FROM:-no-reply@asolutions.jp}
- EMAIL_FROM_NAME=${EMAIL_FROM_NAME:-Assist Solutions} - EMAIL_FROM_NAME=${EMAIL_FROM_NAME:-Assist Solutions}
- SENDGRID_API_KEY=${SENDGRID_API_KEY} - SENDGRID_API_KEY=${SENDGRID_API_KEY}
# Portal # Portal
- PORTAL_PRICEBOOK_ID=${PORTAL_PRICEBOOK_ID} - PORTAL_PRICEBOOK_ID=${PORTAL_PRICEBOOK_ID}
- PORTAL_PRICEBOOK_NAME=${PORTAL_PRICEBOOK_NAME:-Portal} - PORTAL_PRICEBOOK_NAME=${PORTAL_PRICEBOOK_NAME:-Portal}
# Logging # Logging
- LOG_LEVEL=${LOG_LEVEL:-info} - LOG_LEVEL=${LOG_LEVEL:-info}
# Enable automatic database migrations on startup # Enable automatic database migrations on startup
- RUN_MIGRATIONS=true - RUN_MIGRATIONS=true
restart: unless-stopped restart: unless-stopped
@ -117,7 +127,13 @@ services:
# - Database migration when RUN_MIGRATIONS=true # - Database migration when RUN_MIGRATIONS=true
# - Waiting for dependencies (nc checks in entrypoint) # - Waiting for dependencies (nc checks in entrypoint)
healthcheck: healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:4000/health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))"] test:
[
"CMD",
"node",
"-e",
"fetch('http://localhost:4000/health').then(r=>r.ok||process.exit(1)).catch(()=>process.exit(1))",
]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
start_period: 60s start_period: 60s
@ -151,7 +167,19 @@ services:
cache: cache:
image: redis:7-alpine image: redis:7-alpine
container_name: portal-cache container_name: portal-cache
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning", "--maxmemory", "256mb", "--maxmemory-policy", "noeviction"] command:
[
"redis-server",
"--save",
"60",
"1",
"--loglevel",
"warning",
"--maxmemory",
"256mb",
"--maxmemory-policy",
"noeviction",
]
volumes: volumes:
- redis_data:/data - redis_data:/data
restart: unless-stopped restart: unless-stopped

View File

@ -33,13 +33,17 @@ POSTGRES_PASSWORD=<GENERATE_WITH_openssl_rand_base64_24>
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Generate with: openssl rand -base64 32 # Generate with: openssl rand -base64 32
JWT_SECRET=<GENERATE_WITH_openssl_rand_base64_32> JWT_SECRET=<GENERATE_WITH_openssl_rand_base64_32>
JWT_SECRET_PREVIOUS=
JWT_EXPIRES_IN=7d JWT_EXPIRES_IN=7d
JWT_ISSUER=customer-portal
JWT_AUDIENCE=portal
BCRYPT_ROUNDS=12 BCRYPT_ROUNDS=12
CSRF_SECRET_KEY=<GENERATE_WITH_openssl_rand_base64_32> CSRF_SECRET_KEY=<GENERATE_WITH_openssl_rand_base64_32>
# Auth Settings # Auth Settings
AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=false AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=false
AUTH_REQUIRE_REDIS_FOR_TOKENS=false AUTH_REQUIRE_REDIS_FOR_TOKENS=false
AUTH_BLACKLIST_FAIL_CLOSED=false
AUTH_MAINTENANCE_MODE=false AUTH_MAINTENANCE_MODE=false
# Rate Limiting # Rate Limiting

View File

@ -34,6 +34,8 @@ echo ""
# Look for image files # Look for image files
FRONTEND_TAR="${IMAGES_DIR}/portal-frontend-${TAG}.tar" FRONTEND_TAR="${IMAGES_DIR}/portal-frontend-${TAG}.tar"
BACKEND_TAR="${IMAGES_DIR}/portal-backend-${TAG}.tar" BACKEND_TAR="${IMAGES_DIR}/portal-backend-${TAG}.tar"
FRONTEND_TARGZ="${IMAGES_DIR}/portal-frontend-${TAG}.tar.gz"
BACKEND_TARGZ="${IMAGES_DIR}/portal-backend-${TAG}.tar.gz"
# Also check alternative naming # Also check alternative naming
if [[ ! -f "$FRONTEND_TAR" ]]; then if [[ ! -f "$FRONTEND_TAR" ]]; then
@ -43,20 +45,33 @@ if [[ ! -f "$BACKEND_TAR" ]]; then
BACKEND_TAR="${IMAGES_DIR}/portal-backend.${TAG}.tar" BACKEND_TAR="${IMAGES_DIR}/portal-backend.${TAG}.tar"
fi fi
if [[ ! -f "$FRONTEND_TARGZ" ]]; then
FRONTEND_TARGZ="${IMAGES_DIR}/portal-frontend.${TAG}.tar.gz"
fi
if [[ ! -f "$BACKEND_TARGZ" ]]; then
BACKEND_TARGZ="${IMAGES_DIR}/portal-backend.${TAG}.tar.gz"
fi
# Load frontend # Load frontend
if [[ -f "$FRONTEND_TAR" ]]; then if [[ -f "$FRONTEND_TARGZ" ]]; then
log "Loading frontend image from: $FRONTEND_TARGZ"
gunzip -c "$FRONTEND_TARGZ" | docker load
elif [[ -f "$FRONTEND_TAR" ]]; then
log "Loading frontend image from: $FRONTEND_TAR" log "Loading frontend image from: $FRONTEND_TAR"
docker load -i "$FRONTEND_TAR" docker load -i "$FRONTEND_TAR"
else else
warn "Frontend tarball not found: $FRONTEND_TAR" warn "Frontend tarball not found: $FRONTEND_TAR or $FRONTEND_TARGZ"
fi fi
# Load backend # Load backend
if [[ -f "$BACKEND_TAR" ]]; then if [[ -f "$BACKEND_TARGZ" ]]; then
log "Loading backend image from: $BACKEND_TARGZ"
gunzip -c "$BACKEND_TARGZ" | docker load
elif [[ -f "$BACKEND_TAR" ]]; then
log "Loading backend image from: $BACKEND_TAR" log "Loading backend image from: $BACKEND_TAR"
docker load -i "$BACKEND_TAR" docker load -i "$BACKEND_TAR"
else else
warn "Backend tarball not found: $BACKEND_TAR" warn "Backend tarball not found: $BACKEND_TAR or $BACKEND_TARGZ"
fi fi
echo "" echo ""

View File

@ -16,7 +16,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Default paths (override via env vars) # Default paths (override via env vars)
REPO_PATH="${REPO_PATH:-/var/www/vhosts/yourdomain.com/git/customer-portal}" REPO_PATH="${REPO_PATH:-/var/www/vhosts/yourdomain.com/git/customer-portal}"
COMPOSE_FILE="${COMPOSE_FILE:-$PROJECT_ROOT/docker/portainer/docker-compose.yml}" COMPOSE_FILE="${COMPOSE_FILE:-$PROJECT_ROOT/docker/Prod - Portainer/docker-compose.yml}"
ENV_FILE="${ENV_FILE:-$PROJECT_ROOT/.env}" ENV_FILE="${ENV_FILE:-$PROJECT_ROOT/.env}"
# Image settings # Image settings