From 1126877ef53aabae82869b5ecf1b2169e070a296 Mon Sep 17 00:00:00 2001 From: I-Fun Date: Wed, 1 Apr 2026 11:44:46 +0700 Subject: [PATCH] Add Docker support with Dockerfile, docker-compose, and entrypoint script; update .gitignore and add .dockerignore --- .dockerignore | 51 +++++++++++++++++ .gitignore | 1 + Dockerfile | 118 ++++++++++++++++++++++++++++++++++++++++ How-To.txt | 6 ++ docker-compose.yml | 38 +++++++++++++ docker/entrypoint.sh | 59 ++++++++++++++++++++ docker/nginx.conf | 74 +++++++++++++++++++++++++ docker/supervisord.conf | 56 +++++++++++++++++++ 8 files changed, 403 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 How-To.txt create mode 100644 docker-compose.yml create mode 100644 docker/entrypoint.sh create mode 100644 docker/nginx.conf create mode 100644 docker/supervisord.conf diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a2cc395 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,51 @@ +# ── Git ────────────────────────────────────── +.git +.gitignore +.gitattributes + +# ── Env files (use .env.docker instead) ───── +.env +.env.local +.env.*.local + +# ── Node / build artifacts ─────────────────── +node_modules +npm-debug.log* + +# ── Laravel local storage (mounted as volume) +storage/logs/* +storage/framework/cache/* +storage/framework/sessions/* +storage/framework/views/* +storage/app/private/* + +# ── Laravel bootstrap cache ────────────────── +bootstrap/cache/*.php + +# ── SQLite database (mounted as volume) ────── +database/database.sqlite + +# ── Compiled assets (rebuilt in Dockerfile) ── +public/build +public/hot + +# ── Tests / dev tooling ────────────────────── +tests +phpunit.xml +.editorconfig +.php-cs-fixer* + +# ── IDE / OS ───────────────────────────────── +.idea +.vscode +*.DS_Store +Thumbs.db + +# ── Docker config itself ───────────────────── +Dockerfile +docker-compose*.yml +docker/ + +# ── Claude / project meta ──────────────────── +.claude +README.md diff --git a/.gitignore b/.gitignore index 0eeb578..754cb09 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .env .env.backup .env.production +.env.docker .phpactor.json .phpunit.result.cache /.fleet diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2a4b704 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,118 @@ +# ───────────────────────────────────────────── +# Stage 1 – Build frontend assets (Node) +# ───────────────────────────────────────────── +FROM node:22-alpine AS node-builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts + +COPY vite.config.js ./ +COPY resources/ resources/ +COPY public/ public/ + +RUN npm run build + +# ───────────────────────────────────────────── +# Stage 2 – PHP runtime +# ───────────────────────────────────────────── +FROM php:8.3-fpm-alpine AS runtime + +# ── System packages ────────────────────────── +RUN apk add --no-cache \ + nginx \ + supervisor \ + sqlite \ + curl \ + git \ + unzip \ + libpng-dev \ + libjpeg-turbo-dev \ + freetype-dev \ + libxml2-dev \ + libzip-dev \ + icu-dev \ + oniguruma-dev + +# ── PHP extensions ─────────────────────────── +# GD (needed by dompdf for image rendering) +RUN docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + && docker-php-ext-install -j"$(nproc)" \ + pdo \ + pdo_sqlite \ + mbstring \ + fileinfo \ + xml \ + dom \ + bcmath \ + zip \ + intl \ + gd \ + opcache \ + pcntl + +# ── Composer ───────────────────────────────── +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer + +# ── App directory ──────────────────────────── +WORKDIR /var/www/html + +# Install PHP dependencies first (better layer caching) +COPY composer.json composer.lock ./ +RUN composer install \ + --no-dev \ + --no-interaction \ + --no-scripts \ + --optimize-autoloader \ + --prefer-dist + +# Copy full application +COPY . . + +# Inject compiled frontend assets from Stage 1 +COPY --from=node-builder /app/public/build public/build/ + +# ── Docker helper files ─────────────────────── +COPY docker/nginx.conf /etc/nginx/nginx.conf +COPY docker/supervisord.conf /etc/supervisord.conf +COPY docker/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# ── File system ────────────────────────────── +RUN mkdir -p \ + storage/app/private/talent-assets \ + storage/logs \ + storage/framework/cache \ + storage/framework/sessions \ + storage/framework/views \ + bootstrap/cache \ + database \ + && chown -R www-data:www-data \ + storage \ + bootstrap/cache \ + database \ + && chmod -R 775 \ + storage \ + bootstrap/cache \ + database + +# ── PHP-FPM: run as www-data ───────────────── +RUN sed -i 's/user = www-data/user = www-data/' /usr/local/etc/php-fpm.d/www.conf 2>/dev/null || true \ + && sed -i 's/group = www-data/group = www-data/' /usr/local/etc/php-fpm.d/www.conf 2>/dev/null || true + +# ── OPcache tuning ─────────────────────────── +RUN { \ + echo "opcache.enable=1"; \ + echo "opcache.memory_consumption=128"; \ + echo "opcache.interned_strings_buffer=8"; \ + echo "opcache.max_accelerated_files=10000"; \ + echo "opcache.validate_timestamps=0"; \ + echo "opcache.fast_shutdown=1"; \ +} > /usr/local/etc/php/conf.d/opcache.ini + +EXPOSE 80 + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/How-To.txt b/How-To.txt new file mode 100644 index 0000000..8cfe76e --- /dev/null +++ b/How-To.txt @@ -0,0 +1,6 @@ +git clone this repository +composer install +#php artisan key:generate +touch database/database.sqlite && php artisan migrate +npm install +npm run build \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..865a926 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + image: kol-manager:latest + container_name: kol-manager + restart: unless-stopped + + ports: + - "${APP_PORT:-8080}:80" + + # Mount .env.docker as the runtime .env + env_file: + - .env.docker + + volumes: + # Persist the SQLite database across container rebuilds + - kol_database:/var/www/html/database + + # Persist uploaded files (talent assets, etc.) + - kol_storage:/var/www/html/storage + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/up"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + +volumes: + # Named volume for SQLite database + kol_database: + driver: local + + # Named volume for all file uploads / logs / cache + kol_storage: + driver: local diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..922c367 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,59 @@ +#!/bin/sh +set -e + +APP_DIR=/var/www/html + +echo "──────────────────────────────────────────" +echo " KOL Manager – Container Start" +echo "──────────────────────────────────────────" + +cd "$APP_DIR" + +# ── 1. Bootstrap .env ─────────────────────── +if [ ! -f .env ]; then + if [ -f .env.docker ]; then + cp .env.docker .env + echo "[env] Copied .env.docker → .env" + else + echo "[env] WARNING: No .env or .env.docker found!" + fi +fi + +# ── 2. Generate APP_KEY if empty ──────────── +if grep -qE '^APP_KEY=$' .env 2>/dev/null; then + echo "[key] Generating application key…" + php artisan key:generate --force --quiet +fi + +# ── 3. Ensure SQLite database file exists ─── +if [ ! -f database/database.sqlite ]; then + echo "[db] Creating SQLite database file…" + touch database/database.sqlite +fi + +# ── 4. Fix ownership / permissions ────────── +chown -R www-data:www-data \ + storage \ + bootstrap/cache \ + database +chmod -R 775 \ + storage \ + bootstrap/cache \ + database + +# ── 5. Run migrations ──────────────────────── +echo "[migrate] Running database migrations…" +php artisan migrate --force --no-interaction + +# ── 6. Cache config / routes / views ──────── +echo "[optimize] Caching configuration…" +php artisan config:cache --quiet +php artisan route:cache --quiet +php artisan view:cache --quiet + +echo "──────────────────────────────────────────" +echo " All checks passed. Starting services…" +echo "──────────────────────────────────────────" + +# ── 7. Hand off to Supervisor ──────────────── +exec supervisord -c /etc/supervisord.conf diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..157befd --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,74 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +error_log /dev/stderr warn; + +events { + worker_connections 1024; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging to stdout/stderr so Docker captures it + access_log /dev/stdout; + error_log /dev/stderr warn; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml; + + # Upload size limit (matches file uploads in the app) + client_max_body_size 64M; + + server { + listen 80; + server_name _; + + root /var/www/html/public; + index index.php; + + charset utf-8; + + # Laravel health-check endpoint + location = /up { + try_files $uri /index.php?$query_string; + } + + # Main catch-all → Laravel router + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + # PHP-FPM handler + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + + # Increase timeouts for long-running requests (PDF export, uploads) + fastcgi_read_timeout 300; + fastcgi_connect_timeout 60; + fastcgi_send_timeout 300; + } + + # Deny access to hidden files (.env, .git, etc.) + location ~ /\.(?!well-known).* { + deny all; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|otf)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + } +} diff --git a/docker/supervisord.conf b/docker/supervisord.conf new file mode 100644 index 0000000..457465b --- /dev/null +++ b/docker/supervisord.conf @@ -0,0 +1,56 @@ +[supervisord] +nodaemon=true +user=root +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/run/supervisord.pid + +[unix_http_server] +file=/run/supervisor.sock + +[supervisorctl] +serverurl=unix:///run/supervisor.sock + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +; ── Nginx ───────────────────────────────────────────────────────────────── +[program:nginx] +command=nginx -g "daemon off;" +autostart=true +autorestart=true +priority=10 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── PHP-FPM ─────────────────────────────────────────────────────────────── +[program:php-fpm] +command=php-fpm --nodaemonize +autostart=true +autorestart=true +priority=5 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +; ── Laravel Queue Worker ────────────────────────────────────────────────── +[program:queue] +command=php /var/www/html/artisan queue:work \ + --sleep=3 \ + --tries=3 \ + --max-time=3600 \ + --queue=default +directory=/var/www/html +user=www-data +autostart=true +autorestart=true +priority=20 +numprocs=1 +startsecs=5 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0