diff --git a/.env.example b/.env.example index 466aaa7..680ca28 100755 --- a/.env.example +++ b/.env.example @@ -1,44 +1,34 @@ -# Nomina Pegaso - Configuración -APP_NAME="Nomina Pegaso" +# Application - GENERAR CON: php artisan key:generate +APP_NAME="Nomina Ventas" APP_ENV=local -APP_KEY=base64:OKIa4LjLBL6WSZ7HZXKDVpFwJ/KMI6bljtmxWQEcHKY= +APP_KEY= APP_DEBUG=true -APP_URL=http://nomina-pegaso.casa - -APP_LOCALE=es -APP_FALLBACK_LOCALE=es -APP_FAKER_LOCALE=es_MX - -APP_MAINTENANCE_DRIVER=file - -BCRYPT_ROUNDS=12 +APP_URL=http://localhost:80 LOG_CHANNEL=stack -LOG_STACK=single -LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug +# Database - Docker (OBLIGATORIOS - sin valores por defecto) DB_CONNECTION=mysql -DB_HOST=10.10.4.17 -DB_PORT=3391 -DB_DATABASE=nomina_pegaso -DB_USERNAME=nickpons666 -DB_PASSWORD=MiPo6425@@ +DB_HOST=db +DB_PORT=3306 +DB_DATABASE=nomina_ventas +DB_USERNAME= +DB_PASSWORD= +# MySQL Root (OBLIGATORIO) +MYSQL_ROOT_PASSWORD= + +# Session & Cache SESSION_DRIVER=database SESSION_LIFETIME=120 -SESSION_ENCRYPT=true -SESSION_PATH=/ -SESSION_DOMAIN=null - -BROADCAST_CONNECTION=log -FILESYSTEM_DISK=local -QUEUE_CONNECTION=database - CACHE_STORE=database -VITE_APP_NAME="${APP_NAME}" - -# Telegram Bot -TELEGRAM_BOT_TOKEN=8324407449:AAF2awMeZ9pgSIp0MvV1r5owu8lO7SEK70E +# Telegram Bot ( OPCIONAL ) +TELEGRAM_BOT_TOKEN= TELEGRAM_WEBHOOK_URL= + +# Docker Build +APP_PORT=80 +PUID=1000 +PGID=1000 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5c6164f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,106 @@ +# ============================================ +# Laravel PHP-FPM 8.3 - Production Optimized +# ============================================ + +# Stage 1: Builder with Composer +FROM composer:2 AS composer-builder + +WORKDIR /app + +# Copy composer files first for better layer caching +COPY composer.json composer.lock* ./ +RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist + +# Stage 2: PHP-FPM Production Image +FROM php:8.3-fpm-bookworm + +# Build arguments for dynamic user UID/GID +ARG PUID=1000 +ARG PGID=1000 + +# Labels +LABEL maintainer="dev@local.dev" \ + description="Laravel PHP-FPM 8.3 production container" + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + libzip-dev \ + libxml2-dev \ + libcurl4-openssl-dev \ + libgd-dev \ + libfreetype6-dev \ + libjpeg-dev \ + libpng-dev \ + libonig-dev \ + libmcrypt-dev \ + libsqlite3-dev \ + unzip \ + git \ + && rm -rf /var/lib/apt/lists/* + +# Install PHP extensions +RUN docker-php-ext-configure gd --with-freetype --with-jpeg \ + && docker-php-ext-install -j$(nproc) \ + pdo \ + pdo_mysql \ + mbstring \ + xml \ + curl \ + zip \ + gd \ + opcache + +# Configure PHP for production +RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "opcache.memory_consumption=128" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "opcache.interned_strings_buffer=8" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "opcache.max_accelerated_files=10000" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "opcache.revalidate_freq=2" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "opcache.fast_shutdown=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "upload_max_filesize=100M" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \ + && echo "post_max_size=100M" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini + +# Copy Composer from builder stage +COPY --from=composer-builder /usr/bin/composer /usr/bin/composer +COPY --from=composer-builder /root/.composer /root/.composer + +# Create Laravel storage directories with proper permissions +RUN mkdir -p /var/www/html/storage/framework/{cache,sessions,views} \ + && mkdir -p /var/www/html/storage/logs \ + && mkdir -p /var/www/html/bootstrap/cache \ + && chmod -R 775 /var/www/html/storage \ + && chmod -R 775 /var/www/html/bootstrap/cache + +# Create system user for Laravel (dynamic UID/GID) +RUN groupadd -g ${PGID} laravel && \ + useradd -u ${PUID} -g laravel -m -s /bin/bash laravel + +# Set working directory +WORKDIR /var/www/html + +# Copy application files +COPY --chown=laravel:laravel . /var/www/html + +# Fix storage permissions +RUN chown -R laravel:laravel /var/www/html/storage \ + && chown -R laravel:laravel /var/www/html/bootstrap/cache \ + && chmod -R 775 /var/www/html/storage \ + && chmod -R 775 /var/www/html/bootstrap/cache + +# Switch to non-root user +USER laravel + +# Expose port 9000 (PHP-FPM) +EXPOSE 9000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD php-fpm-healthcheck || exit 1 + +# Create healthcheck script +RUN echo '#!/bin/sh' > /usr/local/bin/php-fpm-healthcheck \ + && echo 'SCRIPT_NAME=/health.php SCRIPT_FILENAME=/health.php REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 > /dev/null 2>&1' >> /usr/local/bin/php-fpm-healthcheck \ + && chmod +x /usr/local/bin/php-fpm-healthcheck + +CMD ["php-fpm"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..53e633e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,95 @@ +services: + nginx: + image: nginx:alpine + container_name: nomina_nginx + ports: + - "${APP_PORT:-80}:80" + volumes: + - ./public:/var/www/html/public:ro + - ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - app + networks: + - app_network + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: '0.5' + memory: 256M + + app: + build: + context: . + dockerfile: Dockerfile + args: + PUID: ${PUID:-1000} + PGID: ${PGID:-1000} + container_name: nomina_app + environment: + - APP_ENV=${APP_ENV:-production} + - APP_DEBUG=${APP_DEBUG:-false} + - APP_KEY=${APP_KEY} + - APP_URL=${APP_URL:-http://localhost} + - DB_CONNECTION=${DB_CONNECTION:-mysql} + - DB_HOST=${DB_HOST:-db} + - DB_PORT=${DB_PORT:-3306} + - DB_DATABASE=${DB_DATABASE:-nomina_ventas} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + - SESSION_DRIVER=${SESSION_DRIVER:-database} + - CACHE_STORE=${CACHE_STORE:-database} + - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} + - TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL} + volumes: + - ./:/var/www/html:delegated + - storage_data:/var/www/html/storage + depends_on: + db: + condition: service_healthy + networks: + - app_network + restart: unless-stopped + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M + + db: + image: mysql:8.0 + container_name: nomina_mysql + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} + - MYSQL_DATABASE=${DB_DATABASE:-nomina_ventas} + - MYSQL_USER=${DB_USERNAME} + - MYSQL_PASSWORD=${DB_PASSWORD} + volumes: + - mysql_data:/var/lib/mysql + networks: + - app_network + restart: unless-stopped + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + deploy: + resources: + limits: + cpus: '1.0' + memory: 1G + +networks: + app_network: + driver: bridge + +volumes: + mysql_data: + storage_data: \ No newline at end of file diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 0000000..9004c96 --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,57 @@ +server { + listen 80; + server_name localhost; + root /var/www/html/public; + index index.php index.html; + + client_max_body_size 100M; + client_body_timeout 300s; + proxy_read_timeout 300s; + proxy_connect_timeout 300s; + send_timeout 300s; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + + location ~ \.php$ { + fastcgi_pass app:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + include fastcgi_params; + } + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; + + location ~ /\.(?!well-known).* { + deny all; + return 404; + } + + location ~ /(\.env|composer\.json|composer\.lock|storage|\.git) { + deny all; + return 404; + } + + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml; + + autoindex off; +} \ No newline at end of file