This commit is contained in:
Chu Văn Hải committed 2025-12-05 13:52:24 +07:00
commit f0d13cd510
29 files changed
+6868

No files matched your search

+228
View File
@@ -0,0 +1,228 @@
# ============================================================================
# AUTO-GENERATED ENVIRONMENT FILE FOR AUTO-SCALING DEPLOYMENT
# Application: {{ app_name }}
# Environment: {{ deploy_env }}{{ env_tag | default('') }}
# Generated: {{ ansible_date_time.iso8601 }}
# Ansible User: {{ ansible_user_id }}
# ============================================================================
# Application Configuration
NODE_ENV={{ deploy_env }}
APP_NAME={{ app_name }}
APP_VERSION={{ docker_image_tag }}
DEPLOY_ENV={{ deploy_env }}
INSTANCE_ID={{ app_name }}-{{ deploy_env }}
# Port Configuration
PORT={{ final_container_port | default(container_port) | default(3000) }}
HOST=0.0.0.0
# Auto-Scaling Configuration
{% if auto_scaling_config is defined %}
REPLICAS={{ auto_scaling_config.replicas }}
MAX_REPLICAS={{ auto_scaling_config.max_replicas }}
DEPLOYMENT_STRATEGY={{ auto_scaling_config.deployment_strategy }}
BASE_PORT={{ auto_scaling_config.base_port }}
CONTAINER_PORT={{ auto_scaling_config.container_port }}
{% else %}
REPLICAS={{ final_replicas | default(2) }}
MAX_REPLICAS={{ final_max_replicas | default(5) }}
DEPLOYMENT_STRATEGY={{ final_deployment_strategy | default('rolling') }}
BASE_PORT={{ final_app_port | default(3000) }}
CONTAINER_PORT={{ final_container_port | default(3000) }}
{% endif %}
# Framework Configuration
{% if framework is defined %}
FRAMEWORK={{ framework }}
{% endif %}
{% if node_version is defined %}
NODE_VERSION={{ node_version }}
{% endif %}
{% if package_manager is defined %}
PACKAGE_MANAGER={{ package_manager }}
{% endif %}
# Docker Configuration
DOCKER_NETWORK={{ final_docker_network | default(app_name + '-' + deploy_env + '-network') }}
RESTART_POLICY={{ final_restart_policy | default('unless-stopped') }}
MEMORY_LIMIT={{ final_memory_limit | default('1g') }}
CPU_LIMIT={{ final_cpu_limit | default('1.0') }}
# Health Check Configuration
{% if health_check is defined %}
HEALTH_CHECK_PATH={{ health_check.path | default('/health') }}
HEALTH_CHECK_TIMEOUT={{ health_check.timeout | default(10) }}
HEALTH_CHECK_INTERVAL={{ health_check.interval | default(30) }}
HEALTH_CHECK_RETRIES={{ health_check.retries | default(3) }}
{% else %}
HEALTH_CHECK_PATH={{ health_path | default('/health') }}
HEALTH_CHECK_TIMEOUT=10
HEALTH_CHECK_INTERVAL=30
HEALTH_CHECK_RETRIES=3
{% endif %}
# Git Information
{% if git_tag is defined %}
GIT_TAG={{ git_tag }}
{% endif %}
{% if git_sha is defined %}
GIT_COMMIT={{ git_sha }}
{% endif %}
{% if git_branch is defined %}
GIT_BRANCH={{ git_branch }}
{% endif %}
# Build Information
BUILD_DATE={{ ansible_date_time.iso8601 }}
PIPELINE_ID={{ pipeline_id | default('unknown') }}
# Environment-Specific Variables
{% if environment_variables is defined and environment_variables[deploy_env] is defined %}
{% for key, value in environment_variables[deploy_env].items() %}
{{ key }}={{ value }}
{% endfor %}
{% endif %}
# Legacy env_vars support (backward compatibility)
{% if env_vars is defined %}
{% for key, value in env_vars.items() %}
{{ key }}={{ value }}
{% endfor %}
{% endif %}
# Project-Specific Variables
{% if project_env_vars is defined and project_env_vars[deploy_env] is defined %}
{% for key, value in project_env_vars[deploy_env].items() %}
{{ key }}={{ value }}
{% endfor %}
{% endif %}
# Database Configuration (if applicable)
{% if database_config is defined %}
{% for key, value in database_config.items() %}
{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Queue Configuration (if applicable)
{% if queue_config is defined %}
{% for key, value in queue_config.items() %}
{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Monitoring Configuration
{% if monitoring is defined %}
{% if monitoring.enable_metrics %}
ENABLE_METRICS=true
METRICS_PORT={{ monitoring.metrics_port | default(9090) }}
METRICS_PATH={{ monitoring.metrics_path | default('/metrics') }}
{% endif %}
{% if monitoring.enable_tracing %}
ENABLE_TRACING=true
JAEGER_ENDPOINT={{ monitoring.jaeger_endpoint | default('http://jaeger:14268/api/traces') }}
{% endif %}
{% if monitoring.log_aggregation %}
LOG_AGGREGATION=true
LOG_FORMAT={{ monitoring.log_format | default('json') }}
{% endif %}
{% endif %}
# Load Balancer Information
LB_UPSTREAM_NAME={{ app_name }}_{{ deploy_env }}_backend
LB_METHOD={{ nginx_config.load_balancing_method | default('least_conn') }}
# Service Discovery
SERVICE_NAME={{ app_name }}
SERVICE_VERSION={{ docker_image_tag }}
SERVICE_ENVIRONMENT={{ deploy_env }}
# Auto-scaling Metadata for Application
AUTOSCALE_ENABLED=true
AUTOSCALE_TARGET_REPLICAS={{ final_replicas | default(2) }}
AUTOSCALE_MAX_REPLICAS={{ final_max_replicas | default(5) }}
AUTOSCALE_STRATEGY={{ final_deployment_strategy | default('rolling') }}
# Deployment Metadata
DEPLOYMENT_TIMESTAMP={{ ansible_date_time.epoch }}
DEPLOYMENT_USER={{ ansible_user_id }}
DEPLOYMENT_HOST={{ ansible_host }}
# Security Configuration
{% if security_config is defined %}
{% for key, value in security_config.items() %}
{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Feature Flags
{% if feature_flags is defined %}
{% for key, value in feature_flags.items() %}
FEATURE_{{ key | upper }}={{ value }}
{% endfor %}
{% endif %}
# Next.js Specific (if framework is nextjs)
{% if framework == 'nextjs' %}
NEXT_TELEMETRY_DISABLED=1
{% if environment_variables is defined and environment_variables[deploy_env] is defined %}
{% for key, value in environment_variables[deploy_env].items() %}
{% if key.startswith('NEXT_PUBLIC_') %}
{{ key }}={{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
# React Specific (if framework is reactjs)
{% if framework == 'reactjs' or framework == 'reactjs-vite' %}
{% if environment_variables is defined and environment_variables[deploy_env] is defined %}
{% for key, value in environment_variables[deploy_env].items() %}
{% if key.startswith('REACT_APP_') %}
{{ key }}={{ value }}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
# NestJS Specific (if framework is nestjs)
{% if framework == 'nestjs' %}
NEST_DEBUG=false
{% if deploy_env == 'development' %}
NEST_DEBUG=true
{% endif %}
{% endif %}
# Express Specific (if framework is expressjs)
{% if framework == 'expressjs' %}
EXPRESS_ENV={{ deploy_env }}
TRUST_PROXY=true
{% endif %}
# Worker Specific (if it's a worker deployment)
{% if env_tag is defined and 'worker' in env_tag %}
WORKER_CONCURRENCY={{ worker_config.concurrency | default(5) }}
WORKER_TIMEOUT={{ worker_config.timeout | default(30000) }}
QUEUE_NAME={{ worker_config.queue_name | default('default') }}
QUEUE_RETRY_ATTEMPTS={{ worker_config.retry_attempts | default(3) }}
{% endif %}
# ============================================================================
# AUTO-SCALING DEPLOYMENT INFORMATION
# ============================================================================
# This file was auto-generated for auto-scaling deployment
# Management script: /home/{{ server_user }}/scripts/manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh
#
# Scale command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh scale <replicas>
# Status command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh status
# Health command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh health
# Logs command: ./manage-{{ app_name }}-{{ deploy_env }}{{ env_tag | default('') }}.sh logs
#
# Port Pattern: {{ final_app_port | default(3000) }}{replica_number}
# Current Replicas: {{ final_replicas | default(2) }}
# Max Replicas: {{ final_max_replicas | default(5) }}
# Strategy: {{ final_deployment_strategy | default('rolling') }}
# ============================================================================
@@ -0,0 +1,93 @@
# Express.js Optimized Dockerfile
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG APP_PORT={{ framework_settings_port }}
ARG NODE_ENV=production
# ============================================================================
# Base stage
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies for native modules and health checks
RUN apk update && apk add imagemagick && apk add graphicsmagick && apk add tmux && apk add ffmpeg
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ \
curl \
dumb-init
# ============================================================================
# Production stage
# ============================================================================
FROM base AS production
COPY . ./app
WORKDIR /app
RUN npm install
# Build arguments
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown
# Set build environment
ENV NODE_ENV=production
# Create build info endpoint
RUN mkdir -p src/common && \
echo 'export const BUILD_INFO = {"version":"{{ docker_image_tag }}","commit":"'${GIT_COMMIT}'","buildDate":"'${BUILD_DATE}'","framework":"expressjs","nodeVersion":"{{ node_version }}"}' > src/common/build-info.ts
# Build app
RUN npm run build
# Create non-root user
RUN addgroup --system --gid 1001 expressjs && adduser --system --uid 1001 expressjs
# Ensure dist folder is owned by expressjs user
RUN chown -R expressjs:expressjs /app/dist
# Set environment variables
ENV NODE_ENV=production
ENV PORT=${APP_PORT}
ENV APP_PORT=${APP_PORT}
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# Create logs directory
RUN mkdir -p ./logs && chown -R expressjs:expressjs ./logs
RUN ([ -d "./src/public" ] && cp -r ./src/public ./dist && chown -R expressjs:expressjs ./dist/public) || true
# Switch to non-root user
USER expressjs
# Expose port
EXPOSE ${APP_PORT}
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
# Start the application with dumb-init for proper signal handling
#CMD ["dumb-init", "node", "dist/main"]
CMD {{ RUN_CMD }}
@@ -0,0 +1,106 @@
# Define a build-time variable for the PHP version, with a default of 8.1
ARG PHP_VERSION={{ php_version }}
# Stage 1: Build Stage (Optional but good practice)
# Use the PHP_VERSION argument in the FROM instruction
FROM gitlab.forlife.one:5050/share/php:${PHP_VERSION}-fpm-alpine AS base
# Set working directory inside the container
WORKDIR /var/www/html
# -----------------------------------------------
# 1. INSTALL SYSTEM DEPENDENCIES & PHP EXTENSIONS
# -----------------------------------------------
# Added redis extension installation via PECL.
RUN apk update && apk add --no-cache \
git \
curl \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
freetype-dev \
zip \
unzip \
libzip-dev \
oniguruma-dev \
autoconf \
g++ \
make \
nginx \
supervisor \
dcron \
postgresql-dev \
libxml2-dev \
&& pecl install redis \
&& docker-php-ext-enable redis \
# Configure and install other extensions
&& docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
&& docker-php-ext-install -j$(nproc) gd pdo_mysql pdo_pgsql opcache bcmath exif pcntl sockets zip mbstring dom \
# Clean up
&& rm -rf /var/cache/apk/*
# Setup Laravel scheduler cron job to run every minute
# This cron job will be run by the 'www-data' user
RUN echo "* * * * * cd /var/www/html && php artisan schedule:run >> /dev/null 2>&1" > /etc/crontabs/www-data
# ============================================================================
# Production stage
# ============================================================================
FROM base AS production
WORKDIR /var/www/html
# -----------------------------------------------
# 2. APPLICATION SETUP & DEPENDENCIES
# -----------------------------------------------
# Install Composer v2, suitable for modern PHP versions
#COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Copy the Laravel application files
COPY . .
# Install Composer dependencies (Assuming composer.lock is present)
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Optimize Laravel
RUN php artisan optimize && \
php artisan route:clear && \
php artisan route:cache && \
php artisan config:clear && \
php artisan config:cache && \
php artisan view:clear && \
php artisan view:cache
#RUN php artisan storage:link
# remove this line if you do not want to run migrations on each build
#RUN php artisan migrate --force
# Set appropriate permissions for Laravel (www-data is the default FPM user on Alpine)
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 775 storage bootstrap/cache
RUN ([ -d "public/cms/backend/uploads" ] && chmod -R 775 public/cms/backend/uploads) || true
# -----------------------------------------------
# 3. CONFIGURATION & ENTRYPOINT (THE FIX)
# -----------------------------------------------
RUN mkdir -p /run/nginx \
&& chown -R www-data:www-data /run/nginx \
&& chown -R www-data:www-data /var/lib/nginx
# Copy Nginx Configuration (Assuming it's in the 'docker' subdirectory)
# Nginx listens on port 80 and proxies PHP requests to FPM (port 9000).
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/laravel.conf /etc/nginx/http.d/site.conf
COPY docker/php.ini /usr/local/etc/php/php.ini
# Copy Supervisor Configuration (Assuming it's in the 'docker' subdirectory)
# Supervisor manages both Nginx and PHP-FPM processes.
COPY docker/supervisord.conf /etc/supervisord.conf
EXPOSE 3000
# Start Supervisor, which runs Nginx, PHP-FPM, and Cron
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
@@ -0,0 +1,94 @@
# Express.js Optimized Dockerfile
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG APP_PORT={{ framework_settings_port }}
ARG NODE_ENV=production
# ============================================================================
# Base stage
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies for native modules and health checks
RUN apk update && apk add imagemagick && apk add graphicsmagick && apk add tmux && apk add ffmpeg
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ \
curl \
dumb-init
# ============================================================================
# Production stage
# ============================================================================
FROM base AS production
COPY . ./app
WORKDIR /app
RUN yarn install
# Build arguments
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown
# Set build environment
ENV NODE_ENV=production
# Create build info endpoint
RUN mkdir -p src/common && \
echo 'export const BUILD_INFO = {"version":"{{ docker_image_tag }}","commit":"'${GIT_COMMIT}'","buildDate":"'${BUILD_DATE}'","framework":"nestjs","nodeVersion":"{{ node_version }}"}' > src/common/build-info.ts
# Build app
RUN npm run build
# Create non-root user
RUN addgroup --system --gid 1001 nestjs && adduser --system --uid 1001 nestjs
# Ensure dist folder is owned by nestjs user
RUN chown -R nestjs:nestjs /app/dist
# Set environment variables
ENV NODE_ENV=production
ENV PORT=${APP_PORT}
ENV APP_PORT=${APP_PORT}
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
# Create health check script
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# Create logs directory
RUN mkdir -p ./logs && chown -R nestjs:nestjs ./logs
RUN ([ -d "./src/public" ] && cp -r ./src/public ./dist && chown -R nestjs:nestjs ./dist/public) || true
# Switch to non-root user
USER nestjs
# Expose port
EXPOSE ${APP_PORT}
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
# Start the application with dumb-init for proper signal handling
#CMD ["dumb-init", "node", "dist/main"]
CMD {{ RUN_CMD }}
@@ -0,0 +1,103 @@
# Next.js Optimized Dockerfile with Dynamic Build Args
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments - Base Configuration
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG START_COMMAND={{ start_command }}
ARG APP_PORT={{ framework_settings_port }}
ARG NODE_ENV=production
ARG APP_VERSION={{ docker_image_tag }}
ARG FRAMEWORK=nextjs
# ============================================================================
# Base stage - Alpine for smaller size
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ curl
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else {{ package_manager }} install; \
fi
#RUN {{ package_manager }} install
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn build
# If using npm comment out above and use below instead
# RUN npm run build
# Production image, copy all the files and run next
FROM base AS production
WORKDIR /app
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
# Create health check script
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# You only need to copy next.config.js if you are NOT using the default configuration
# COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV APP_PORT=3000
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
#CMD ["node", "server.js"]
CMD {{ RUN_CMD }}
@@ -0,0 +1,189 @@
# React.js Optimized Dockerfile (No Nginx)
# Auto-generated for {{ app_name }}
# Node.js Version: {{ node_version }}
# Package Manager: {{ package_manager }}
# ============================================================================
# Build arguments
# ============================================================================
ARG NODE_VERSION={{ node_version }}
ARG PACKAGE_MANAGER={{ package_manager }}
ARG BUILD_COMMAND={{ build_command }}
ARG APP_PORT={{ framework_settings_port }}
# ============================================================================
# Base stage
# ============================================================================
FROM gitlab.forlife.one:5050/share/node:${NODE_VERSION}-alpine AS base
# Install dependencies
RUN apk add --no-cache \
libc6-compat \
python3 \
make \
g++ curl
WORKDIR /app
# ============================================================================
# Dependencies stage
# ============================================================================
FROM base AS deps
# Copy package files
COPY package*.json yarn.lock* pnpm-lock.yaml* ./
# Install dependencies
#RUN yarn install
RUN {{ package_manager }} install
# ============================================================================
# Builder stage
# ============================================================================
FROM base AS builder
# Copy dependencies
COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY . .
# Build arguments for runtime configuration
ARG GIT_COMMIT=unknown
ARG BUILD_DATE=unknown
ARG REACT_APP_VERSION={{ docker_image_tag }}
# Set build environment
ENV NODE_ENV=production
ENV GENERATE_SOURCEMAP=false
ENV REACT_APP_VERSION=${REACT_APP_VERSION}
ENV REACT_APP_BUILD_DATE=${BUILD_DATE}
ENV REACT_APP_GIT_COMMIT=${GIT_COMMIT}
# Create build info file
RUN echo '{"version":"{{ docker_image_tag }}","commit":"'${GIT_COMMIT}'","buildDate":"'${BUILD_DATE}'","framework":"reactjs","nodeVersion":"{{ node_version }}"}' > public/build-info.json
# Require BuildKit enabled
# Mount secret nếu tồn tại, copy vào thư mục app
RUN --mount=type=secret,id=env,target=/run/secrets/env \
cp /run/secrets/env .env || echo ".env not found"
# ============================================================================
# Build the application
# ============================================================================
RUN \
if [ -f yarn.lock ]; then \
yarn build; \
elif [ -f package-lock.json ]; then \
npm run build; \
elif [ -f pnpm-lock.yaml ]; then \
pnpm build; \
else \
npm run build; \
fi
# ============================================================================
# Production stage with serve
# ============================================================================
FROM node:${NODE_VERSION}-alpine AS production
# Install curl for health checks
RUN apk add --no-cache curl
WORKDIR /app
# Install serve globally for serving static files
RUN npm install -g serve
# Copy built React app
COPY --from=builder /app/build ./build
# Create package.json for serving
RUN echo '{ \
"name": "{{ app_name }}-server", \
"version": "{{ docker_image_tag }}", \
"scripts": { \
"start": "serve -s build -l 3000" \
}, \
"dependencies": { \
"serve": "latest" \
} \
}' > package.json
# Create serve.json configuration
RUN echo '{ \
"public": "build", \
"rewrites": [ \
{ "source": "**", "destination": "/index.html" } \
], \
"headers": [ \
{ \
"source": "**/*.@(jpg|jpeg|gif|png|svg|ico|css|js)", \
"headers": [ \
{ \
"key": "Cache-Control", \
"value": "public, max-age=31536000, immutable" \
} \
] \
}, \
{ \
"source": "**", \
"headers": [ \
{ \
"key": "X-Content-Type-Options", \
"value": "nosniff" \
}, \
{ \
"key": "X-Frame-Options", \
"value": "DENY" \
}, \
{ \
"key": "X-XSS-Protection", \
"value": "1; mode=block" \
} \
] \
} \
] \
}' > serve.json
# Create health check endpoint
RUN mkdir -p build && echo 'healthy' > build/health.txt
# Create health check script
RUN echo '#!/bin/sh\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/health)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${APP_PORT}/)\n\
if [ "$status" -eq 200 ] || [ "$status" -eq 404 ]; then exit 0; fi\n\
\n\
exit 1' > /health-check.sh && \
chmod +x /health-check.sh
# Create non-root user
RUN addgroup --system --gid 1001 reactapp && \
adduser --system --uid 1001 reactapp
# Set proper permissions
RUN chown -R reactapp:reactapp /app
# Switch to non-root user
USER reactapp
# Expose port
EXPOSE 3000
# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000
ENV APP_PORT=3000
ENV NODE_OPTIONS="--max-old-space-size={{ MEMORY_LIMIT_MB }}"
# Health check
#HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 CMD /health-check.sh
# Start the application
#CMD ["npm", "start"]
#CMD [ "serve", "-s", "build" ]
CMD {{ RUN_CMD }}
+21
View File
@@ -0,0 +1,21 @@
#!/bin/sh
set -e # Thoát ngay lập tức nếu có lệnh nào thất bại
echo "Checking /health endpoint..."
health_status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/health")
if [ "$health_status" -eq 200 ] || [ "$health_status" -eq 404 ]; then
echo "/health check passed with status ${health_status}"
exit 0
fi
echo "/health check failed with status ${health_status}. Checking / endpoint..."
root_status=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/")
if [ "$root_status" -eq 200 ] || [ "$root_status" -eq 404 ]; then
echo "/ check passed with status ${root_status}"
exit 0
fi
echo "/ check failed with status ${root_status}. Health check failed overall."
exit 1
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# method 1: crontab with parallel block
#* * * * * flock -n /var/lock/monitor-container-script.lock /www/server/scripts/monitor-container-script.sh >>/var/log/monitor-container-script.log 2>&1
set -Eeuo pipefail
# method 2: crontab with parallel block
# only use method 1 or method 2
# Khóa tiến trình (FD 9)
#~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~
exec 9>/var/lock/monitor-container-script.lock
if ! flock -n 9; then
echo "[$(date '+%F %T')] ⚠️ Another run is in progress. Exit."
exit 0
fi
trap 'flock -u 9' EXIT
#~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~+~
# === Cấu hình chung ===
CONTAINER_PREFIX="webapplication-production-"
CONTAINER_PORT=3000 # cổng bên trong container (không cần dùng ở đây)
HOST="localhost"
TIMEOUT=3
SLEEP_TIME=10
COOLDOWN=180 # giây
CPU_LIMIT=80
MEM_LIMIT=90
MANAGE_SCRIPT="/home/forlifeci/scripts/manage-webapplication-production.sh restart_single"
CONTAINER_LOCK_FILE="/var/lib/container_maps.lock"
LOCK_DIR="/tmp/container_restart_locks" && mkdir -p "$LOCK_DIR/logs"
remove_timestamp() { [[ $1 =~ ^(.*-[0-9]+)-([0-9]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "$1"; }
check_container_health() {
local container=$1
local cpu mem
echo "📡 $(date '+%Y-%m-%d %H:%M:%S') - Kiểm tra container... $container"
#cpu=$(docker stats --no-stream --format "{{.CPUPerc}}" "$container" | tr -d '%')
#mem=$(docker stats --no-stream --format "{{.MemPerc}}" "$container" | tr -d '%')
#echo "Kiểm tra CPU / RAM quá giới hạn -> $container"
#if (( $(echo "$cpu > $CPU_LIMIT" | bc -l) )) || (( $(echo "$mem > $MEM_LIMIT" | bc -l) )); then
# echo "⚠️ $container quá tải (CPU: $cpu%, RAM: $mem%)"
# return 1
#fi
#echo "Kiểm tra port 3000 bên trong container -> $container"
if ! docker exec "$container" sh -c "nc -z -w 2 127.0.0.1 3000" >/dev/null 2>&1; then
echo "❌ $container không phản hồi trên port 3000"
return 1
fi
return 0
}
# === Telegram Config ===
TELEGRAM_BOT_TOKEN="7837210802:AAHFlwXOdm3PExgIbADJ_TstNoXwJ_Aq7g4"
TELEGRAM_CHAT_ID="-1002468192435"
THREAD_ID="10248"
# === Hàm gửi cảnh báo Telegram ===
send_telegram_alert() {
local container=$1
local port=$2
local fileName=${3:-"log"}
local now=$(date '+%Y-%m-%d %H:%M:%S')
local msg="🚨 *CẢNH BÁO* 🚨
Container: \`$container\`
Port: \`$port\`
Thời gian: *$now*
Hành động: \`docker restart\`
➡️ Container không phản hồi, đã được khởi động lại.
http://42.118.103.30/logs/$container.restart.$fileName.html
"
curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-d chat_id="$TELEGRAM_CHAT_ID" \
-d parse_mode="Markdown" \
-d message_thread_id="$THREAD_ID" \
-d text="$msg" > /dev/null
}
# === Hàm kiểm tra port ===
#check_port() {
# local port=$1
# nc -z -w $TIMEOUT $HOST $port
#}
check_port() {
local port=$1
curl -s --max-time "$TIMEOUT" "http://$HOST:$port" -o /dev/null
}
should_skip_check() {
local container=$1
local lock_file="$LOCK_DIR/$container.lock"
if [[ -f "$lock_file" ]]; then
local last_restart
last_restart=$(cat "$lock_file")
local now
now=$(date +%s)
if (( now - last_restart < COOLDOWN )); then
return 0 # bỏ qua
else
rm -f "$lock_file" # hết hạn cooldown
fi
fi
return 1
}
mark_restart() {
local container=$1
date +%s > "$LOCK_DIR/$container.lock"
}
get_docker_binding_port() {
local container=$1
echo $(docker port "$container" 3000/tcp | awk -F: '{print $2}' | xargs)
}
containers=$(docker ps --format '{{.Names}}' | grep "^$CONTAINER_PREFIX")
# === Vòng lặp kiểm tra ===
#while true; do
for container in $containers; do
if [[ -f $CONTAINER_LOCK_FILE ]]; then
break
fi
container_locked=$(remove_timestamp "$container")
# 🔹 Bỏ qua nếu container đang trong cooldown
if should_skip_check "$container_locked"; then
echo "⏩ Bỏ qua $container_locked (đang cooldown sau khi restart)"
continue
fi
host_port=$(get_docker_binding_port "$container")
if [ -z "$host_port" ]; then
echo "⚠ $container không map port 3000/tcp, bỏ qua."
continue
fi
echo "";
echo "👉 Kiểm tra $container tại $HOST:$host_port..."
if ! check_port $host_port; then
echo "⚠️ $container KHÔNG phản hồi. Chờ 5s để xác nhận..."
sleep 5
if ! check_port $host_port; then
echo "❌ $container vẫn không phản hồi! Restart & gửi Telegram..."
logName=$(date +%s)
mark_restart "$container_locked" # đánh dấu cooldown
#docker restart "$container"
restart=`bash $MANAGE_SCRIPT $container`
logPath="$LOCK_DIR/logs/$container.restart.$logName.html"
if [ ! -f "$logPath" ]; then
echo "<!DOCTYPE html><html><meta charset=utf-8><style>pre{text-wrap: auto;}</style>" > "$logPath"
fi
echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>" >> "$logPath"
printf "%s" "<pre>$restart</pre>" >> "$logPath"
send_telegram_alert "$container" "$host_port" "$logName"
else
echo "✅ $container đã phản hồi lại sau khi chờ."
fi
else
echo "✅ $container phản hồi OK."
fi
done
# sleep $SLEEP_TIME
#done
#/home/forlifeci/scripts/manage-webapplication-production.sh restart_single $container
@@ -0,0 +1,140 @@
# ============================================================================
# AUTO-GENERATED NGINX UPSTREAM CONFIGURATION
# Application: {{ app_name }}
# Environment: {{ deploy_env }}{{ env_tag | default('') }}
# Generated: {{ ansible_date_time.iso8601 }}
# Replicas: {{ replica_count }}
# ============================================================================
{% if framework is defined and framework == "nextjs" %}
map $arg_url ${{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_clean_url {
~^(?<base>.+)%3Fv%3D\d+$ $base; # chỉ có ?v=xxx
~^(?<base>.+)%3Fv%3D\d+%26(?<rest>.+)$ $base?$rest; # có ?v=xxx&... → giữ lại phần sau
default $arg_url; # mặc định giữ nguyên
}
{% endif %}
upstream {{ upstream_name | regex_replace('[^A-Za-z0-9_]', '_') }} {
# Load balancing method
{% if load_balancing_method is defined %}
{{ load_balancing_method }};
{% else %}
least_conn;
{% endif %}
# Health check configuration
{% if health_check_interval is defined %}
health_check interval={{ health_check_interval }} fails={{ health_check_fails | default(1) }} passes={{ health_check_passes | default(2) }};
{% endif %}
# Upstream servers (auto-generated from active ports)
{% for port in active_ports %}
server localhost:{{ port }} weight={{ server_weight | default(1) }} max_fails={{ max_fails | default(1) }} fail_timeout={{ fail_timeout | default(10) }}s;
{% endfor %}
# Backup server (optional)
{% if backup_server is defined %}
server {{ backup_server }} backup;
{% endif %}
# Keep-alive connections
keepalive {{ keepalive_connections | default(64) }};
}
{% if framework is defined and framework == "nextjs" %}
proxy_cache_path /var/cache/nginx/{{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_cache levels=1:2 keys_zone={{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_cache:512m inactive=30d max_size=100g use_temp_path=off;
#levels=1:2: Tạo cây thư mục theo hash key để phân bổ file.
#keys_zone=tkl_web_img_cache:512m: Tăng RAM dùng cho metadata (1MB chứa được ~8000 keys) → 2G = ~16 triệu keys.
#inactive=30d: Nếu file không được truy cập trong 30 ngày → bị xóa.
#max_size=100g: Giới hạn tối đa 100GB cache disk.
#use_temp_path=off: Ghi thẳng vào cache thay vì ghi tạm → hiệu quả hơn.
{% endif %}
server {
listen {{ base_port }};
server_name localhost;
{% if framework is defined and framework == "nextjs" %}
location ~* /_next/image {
access_log off;
proxy_pass http://{{ upstream_name | regex_replace('[^A-Za-z0-9_]', '_') }}; # upstream load balancing
proxy_cache {{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_cache;
proxy_cache_key "url=${{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_clean_url&w=$arg_w&q=$arg_q";
proxy_cache_valid 200 180d;
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
# Chống cache stampede (bottleneck)
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
proxy_cache_lock_age 5s;
# Timeout & buffering
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
# Ignore upstream cache headers (Next.js gửi max-age + vary)
proxy_ignore_headers Cache-Control Vary;
proxy_hide_header Set-Cookie;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
# Chuẩn hóa Accept để tránh tạo nhiều cache key khác nhau
proxy_set_header Accept "image/webp,image/*,*/*;q=0.8";
add_header X-Cache-Status $upstream_cache_status;
#add_header X-Cache-Key "url=${{app_name | regex_replace('[^A-Za-z0-9_]', '_')}}_clean_url&w=$arg_w&q=$arg_q";
# Keepalive settings (optional)
keepalive_timeout 64;
}
{% endif %}
location / {
access_log off;
proxy_pass http://{{ upstream_name | regex_replace('[^A-Za-z0-9_]', '_') }};
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 REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_cache off;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
# Timeout & buffering
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
proxy_buffering on;
client_max_body_size 256M;
# Keepalive settings (optional)
keepalive_timeout 64;
}
}
# ============================================================================
# CONFIGURATION METADATA (for debugging)
# ============================================================================
# Configuration Details:
# - Base Port: {{ base_port }}
# - Load Balancing: {{ load_balancing_method | default('least_conn') }}
# - Max Fails: {{ max_fails | default(1) }}
# - Fail Timeout: {{ fail_timeout | default(10) }}s
# - Health Check: {{ 'enabled' if enable_health_check_endpoint | default(true) else 'disabled' }}
# ============================================================================
+63
View File
@@ -0,0 +1,63 @@
server {
listen 3000;
server_name localhost;
root /var/www/html/public; # Đây là thư mục public của Laravel
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
#access_log /dev/stdout;
#error_log /dev/stdout;
location ~\.(yml|env|example|log|prod|dev|local|production)$ {
deny all;
return 404;
}
location ~* \.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|mp4|vtt)$ {
access_log off;
log_not_found off;
expires 30d;
add_header Access-Control-Allow-Origin *;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ "^(.+\.php)($|/)" {
index index.php index.html index.htm admin.php;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param HTTPS off;
send_timeout 120;
fastcgi_read_timeout 120;
client_max_body_size 20m;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
+108
View File
@@ -0,0 +1,108 @@
#user nginx;
user www-data;
# Set number of worker processes automatically based on number of CPU cores.
worker_processes auto;
# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;
# Configures default error logger.
error_log /var/log/nginx/error.log warn;
# Includes files with directives to load dynamic modules.
include /etc/nginx/modules/*.conf;
# Include files with config snippets into the root context.
include /etc/nginx/conf.d/*.conf;
events {
# The maximum number of simultaneous connections that can be opened by
# a worker process.
worker_connections 1024;
}
http {
# Includes mapping of file name extensions to MIME types of responses
# and defines the default type.
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Name servers used to resolve names of upstream servers into addresses.
# It's also needed when using tcpsocket and udpsocket in Lua modules.
#resolver 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001];
# Don't tell nginx version to the clients. Default is 'on'.
server_tokens off;
# Specifies the maximum accepted body size of a client request, as
# indicated by the request header Content-Length. If the stated content
# length is greater than this size, then the client receives the HTTP
# error code 413. Set to 0 to disable. Default is '1m'.
client_max_body_size 100m;
# Sendfile copies data between one FD and other from within the kernel,
# which is more efficient than read() + write(). Default is off.
sendfile on;
# Causes nginx to attempt to send its HTTP response head in one packet,
# instead of using partial frames. Default is 'off'.
tcp_nopush on;
# Enables the specified protocols. Default is TLSv1 TLSv1.1 TLSv1.2.
# TIP: If you're not obligated to support ancient clients, remove TLSv1.1.
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
# Path of the file with Diffie-Hellman parameters for EDH ciphers.
# TIP: Generate with: `openssl dhparam -out /etc/ssl/nginx/dh2048.pem 2048`
#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
# Specifies that our cipher suits should be preferred over client ciphers.
# Default is 'off'.
ssl_prefer_server_ciphers on;
# Enables a shared SSL cache with size that can hold around 8000 sessions.
# Default is 'none'.
ssl_session_cache shared:SSL:2m;
# Specifies a time during which a client may reuse the session parameters.
# Default is '5m'.
ssl_session_timeout 1h;
# Disable TLS session tickets (they are insecure). Default is 'on'.
ssl_session_tickets off;
# Enable gzipping of responses.
gzip on;
# Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'.
#gzip_vary on;
# Helper variable for proxying websockets.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Specifies the main log format.
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Sets the path, format, and configuration for a buffered log write.
access_log /var/log/nginx/access.log main;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 0.0.0.0/0;
#access_log /dev/stdout;
#error_log /dev/stdout;
# Includes virtual hosts configs.
include /etc/nginx/http.d/*.conf;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,39 @@
[supervisord]
nodaemon=true
# (Tùy chọn) Ngăn Supervisor tự tạo log file của chính nó
logfile=/dev/null
logfile_maxbytes=0
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
priority=10
# Chuyển hướng stdout của Nginx ra stdout của Supervisor
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
# Chuyển hướng stderr của Nginx ra stderr của Supervisor
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:php-fpm]
command=/usr/local/sbin/php-fpm --nodaemonize --fpm-config /usr/local/etc/php-fpm.conf
autostart=true
autorestart=true
priority=5
# Chuyển hướng stdout của PHP-FPM ra stdout của Supervisor
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
# Chuyển hướng stderr của PHP-FPM ra stderr của Supervisor
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:queue-work]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/html/artisan queue:work
user=www-data
autostart=true
autorestart=true
numprocs=1
redirect_stderr=true
stdout_logfile=/dev/stdout