feat: Docker Compose setup untuk development & production
- docker/php/Dockerfile: PHP 8.4-FPM + GD + imagick (PECL) + semua extension Laravel - docker/php/php.ini: upload 20MB, memory 512MB, opcache, Asia/Kuala_Lumpur - docker/php/php-dev.ini: validate_timestamps=1, display_errors=On (dev) - docker/nginx/default.conf: gzip, security headers, static asset caching - docker/entrypoint.sh: tunggu MySQL → migrate → seed AdminSeeder → cache (prod) - docker-compose.yml: dev stack — port 8003, DB host 33060, queue worker - docker-compose.prod.yml: production overrides — storage volume, no DB port exposed - .env.docker: template env untuk Docker (DB_HOST=db) - .dockerignore: exclude node_modules, vendor, .env, logs fix: testGenerate try/catch kembalikan JSON error (bukan HTML 500) fix: loadPreview() semak r.ok, tunjuk error alert, loading spinner Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
82
docker/entrypoint.sh
Normal file
82
docker/entrypoint.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# eCert MBIP — Container Entrypoint
|
||||
# Jalankan sebelum php-fpm bermula:
|
||||
# 1. Tunggu MySQL
|
||||
# 2. Install Composer deps (dev sahaja)
|
||||
# 3. Generate APP_KEY jika tiada
|
||||
# 4. Migrate + seed AdminSeeder
|
||||
# 5. Storage link
|
||||
# 6. Cache (prod sahaja)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
set -e
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════╗"
|
||||
echo "║ eCert MBIP — Container Start ║"
|
||||
echo "╚══════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# ── 1. Tunggu MySQL bersedia ──────────────────────────────────────────────────
|
||||
DB_HOST="${DB_HOST:-db}"
|
||||
DB_PORT="${DB_PORT:-3306}"
|
||||
DB_DATABASE="${DB_DATABASE:-ecert_mbip}"
|
||||
DB_USERNAME="${DB_USERNAME:-root}"
|
||||
DB_PASSWORD="${DB_PASSWORD:-secret}"
|
||||
|
||||
echo "⏳ Menunggu MySQL di ${DB_HOST}:${DB_PORT}..."
|
||||
|
||||
until mysqladmin ping \
|
||||
-h "${DB_HOST}" \
|
||||
-P "${DB_PORT}" \
|
||||
-u "${DB_USERNAME}" \
|
||||
--password="${DB_PASSWORD}" \
|
||||
--silent 2>/dev/null; do
|
||||
printf "."
|
||||
sleep 2
|
||||
done
|
||||
echo ""
|
||||
echo "✓ MySQL bersedia."
|
||||
|
||||
# ── 2. Pasang Composer dependencies (development sahaja) ─────────────────────
|
||||
if [ "${APP_ENV}" != "production" ] && [ ! -d /var/www/vendor ]; then
|
||||
echo "📦 Memasang Composer dependencies (dev)..."
|
||||
composer install \
|
||||
--no-interaction \
|
||||
--no-progress \
|
||||
--prefer-dist
|
||||
fi
|
||||
|
||||
# ── 3. Generate APP_KEY jika kosong ───────────────────────────────────────────
|
||||
if [ -z "${APP_KEY}" ] || [ "${APP_KEY}" = "" ]; then
|
||||
echo "🔑 Menjana APP_KEY..."
|
||||
php artisan key:generate --force
|
||||
fi
|
||||
|
||||
# ── 4. Database migration ─────────────────────────────────────────────────────
|
||||
echo "🗄 Menjalankan migration..."
|
||||
php artisan migrate --force
|
||||
|
||||
# Seed admin account (idempotent — guna firstOrCreate)
|
||||
echo "👤 Seeding AdminSeeder..."
|
||||
php artisan db:seed --class=AdminSeeder --force
|
||||
|
||||
# ── 5. Storage symbolic link ──────────────────────────────────────────────────
|
||||
php artisan storage:link 2>/dev/null || true
|
||||
|
||||
# ── 6. Cache (production sahaja) ──────────────────────────────────────────────
|
||||
if [ "${APP_ENV}" = "production" ]; then
|
||||
echo "⚡ Caching config, routes, views..."
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan event:cache
|
||||
# Opcache: matikan validate_timestamps untuk prestasi
|
||||
# (sudah dikonfigur dalam php.ini prod)
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Aplikasi bersedia."
|
||||
echo ""
|
||||
|
||||
exec "$@"
|
||||
78
docker/nginx/default.conf
Normal file
78
docker/nginx/default.conf
Normal file
@@ -0,0 +1,78 @@
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# eCert MBIP — Nginx Server Block
|
||||
# Document root: /var/www/public | PHP-FPM upstream: app:9000
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_types
|
||||
text/plain text/css text/javascript application/javascript
|
||||
application/json application/xml image/svg+xml font/woff2;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /var/www/public;
|
||||
index index.php;
|
||||
|
||||
# Max upload (kena sama dengan php.ini: post_max_size)
|
||||
client_max_body_size 25M;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
# ── Security headers ──────────────────────────────────────────────────────
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# ── Laravel routes ────────────────────────────────────────────────────────
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
# ── PHP-FPM ───────────────────────────────────────────────────────────────
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
|
||||
fastcgi_pass app:9000;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
|
||||
fastcgi_read_timeout 120s;
|
||||
fastcgi_connect_timeout 10s;
|
||||
fastcgi_buffer_size 16k;
|
||||
fastcgi_buffers 8 16k;
|
||||
}
|
||||
|
||||
# ── Static assets — cache 1 tahun ─────────────────────────────────────────
|
||||
location ~* \.(jpg|jpeg|png|gif|ico|svg|css|js|woff2?|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
access_log off;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# ── Halang akses fail tersembunyi ─────────────────────────────────────────
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# ── Halang akses terus ke fail sensitif ───────────────────────────────────
|
||||
location ~* \.(env|log|htaccess|htpasswd|ini|sh|sql|bak)$ {
|
||||
deny all;
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────────
|
||||
access_log /var/log/nginx/ecert-access.log;
|
||||
error_log /var/log/nginx/ecert-error.log warn;
|
||||
}
|
||||
70
docker/php/Dockerfile
Normal file
70
docker/php/Dockerfile
Normal file
@@ -0,0 +1,70 @@
|
||||
###############################################################################
|
||||
# eCert MBIP — PHP-FPM Runtime Image
|
||||
# PHP 8.4 + GD + imagick + semua extension Laravel
|
||||
###############################################################################
|
||||
FROM php:8.4-fpm
|
||||
|
||||
LABEL org.opencontainers.image.title="eCert MBIP" \
|
||||
org.opencontainers.image.description="Sistem Pengurusan Sijil Digital MBIP"
|
||||
|
||||
# ── System libraries ──────────────────────────────────────────────────────────
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
curl \
|
||||
zip \
|
||||
unzip \
|
||||
# zip extension
|
||||
libzip-dev \
|
||||
# GD: PNG, JPEG, WebP, FreeType
|
||||
libpng-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libfreetype6-dev \
|
||||
libwebp-dev \
|
||||
# mbstring
|
||||
libonig-dev \
|
||||
# xml / intl
|
||||
libxml2-dev \
|
||||
libicu-dev \
|
||||
# imagick
|
||||
libmagickwand-dev \
|
||||
# mysql client (wait-for-db in entrypoint)
|
||||
default-mysql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ── GD (untuk Intervention Image — GD driver) ─────────────────────────────────
|
||||
RUN docker-php-ext-configure gd \
|
||||
--with-freetype \
|
||||
--with-jpeg \
|
||||
--with-webp \
|
||||
&& docker-php-ext-install -j$(nproc) gd
|
||||
|
||||
# ── Core PHP extensions ───────────────────────────────────────────────────────
|
||||
RUN docker-php-ext-install -j$(nproc) \
|
||||
pdo_mysql \
|
||||
mbstring \
|
||||
exif \
|
||||
pcntl \
|
||||
bcmath \
|
||||
zip \
|
||||
intl \
|
||||
opcache
|
||||
|
||||
# ── imagick (PECL) — tersedia sebagai driver alternatif ───────────────────────
|
||||
RUN pecl install imagick \
|
||||
&& docker-php-ext-enable imagick \
|
||||
&& rm -rf /tmp/pear
|
||||
|
||||
# ── Composer ──────────────────────────────────────────────────────────────────
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# ── Working directory ─────────────────────────────────────────────────────────
|
||||
WORKDIR /var/www
|
||||
|
||||
# ── Entrypoint ────────────────────────────────────────────────────────────────
|
||||
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
CMD ["php-fpm"]
|
||||
13
docker/php/php-dev.ini
Normal file
13
docker/php/php-dev.ini
Normal file
@@ -0,0 +1,13 @@
|
||||
; ──────────────────────────────────────────────────────────────────────────────
|
||||
; Development overrides — dilengkap di atas php.ini
|
||||
; Hanya dimuat dalam docker-compose.yml (dev), tidak dalam prod
|
||||
; ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
; Reload fail PHP tanpa restart container
|
||||
opcache.validate_timestamps = 1
|
||||
opcache.revalidate_freq = 0
|
||||
|
||||
; Tunjuk ralat dalam browser semasa dev
|
||||
display_errors = On
|
||||
display_startup_errors = On
|
||||
error_reporting = E_ALL
|
||||
44
docker/php/php.ini
Normal file
44
docker/php/php.ini
Normal file
@@ -0,0 +1,44 @@
|
||||
; ──────────────────────────────────────────────────────────────────────────────
|
||||
; eCert MBIP — PHP Runtime Configuration
|
||||
; Letakkan dalam /usr/local/etc/php/conf.d/99-ecert.ini
|
||||
; ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
; Timezone Malaysia
|
||||
date.timezone = Asia/Kuala_Lumpur
|
||||
|
||||
; ── Upload (template sijil hingga 10MB + buffer) ──────────────────────────────
|
||||
upload_max_filesize = 20M
|
||||
post_max_size = 25M
|
||||
max_file_uploads = 10
|
||||
|
||||
; ── Execution (image generation + queue workers) ──────────────────────────────
|
||||
max_execution_time = 120
|
||||
max_input_time = 120
|
||||
|
||||
; ── Memory (Intervention Image untuk sijil resolusi tinggi) ───────────────────
|
||||
memory_limit = 512M
|
||||
|
||||
; ── Session ───────────────────────────────────────────────────────────────────
|
||||
session.cookie_httponly = 1
|
||||
session.cookie_samesite = Lax
|
||||
session.gc_maxlifetime = 7200
|
||||
|
||||
; ── OPcache ───────────────────────────────────────────────────────────────────
|
||||
opcache.enable = 1
|
||||
opcache.memory_consumption = 192
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.max_accelerated_files = 10000
|
||||
; validate_timestamps: 1 untuk dev (auto-reload), 0 untuk prod (prestasi)
|
||||
opcache.validate_timestamps = 1
|
||||
opcache.revalidate_freq = 2
|
||||
opcache.fast_shutdown = 1
|
||||
|
||||
; ── Error (override per environment via APP_ENV) ──────────────────────────────
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
log_errors = On
|
||||
error_log = /var/log/php_errors.log
|
||||
|
||||
; ── imagick ───────────────────────────────────────────────────────────────────
|
||||
[imagick]
|
||||
imagick.skip_version_check = 1
|
||||
Reference in New Issue
Block a user