Aplikasi monolit modern yang menggunakan Inertia.js menggabungkan kenyamanan single-page application (SPA) dengan kesederhanaan arsitektur backend monolithic seperti Laravel. Namun, pada pipeline Continuous Integration (CI), struktur ini menimbulkan tantangan: proses build frontend (Node.js/Vite) dan runtime backend (PHP-FPM) memiliki dependensi lingkungan yang bertolak belakang. Mengabaikan pemisahan ini menghasilkan Docker image yang bloated, pipeline build yang lambat, dan risiko runtime error 500 akibat hilangnya artefak kompilasi frontend.
Arsitektur Pemisahan Build Frontend dan Backend Runtime
Kesalahan umum dalam monolit Inertia adalah menginstal Node.js dan runtime PHP dalam satu stage container yang sama saat build. Pendekatan ini meningkatkan attack surface di production, menambah beban image sebesar ratusan megabyte, dan merusak efisiensi cache Docker.
Arsitektur pipeline yang ideal membagi proses menjadi tiga layer terisolasi:
- Frontend Build Stage: Menggunakan image Node.js minimalis untuk mengompilasi TypeScript, CSS, dan view components (Vue/React/Svelte) menjadi static bundle ber-hash.
- Backend Build Stage: Menggunakan container Composer untuk mengunduh PHP vendor packages tanpa development dependencies.
- Final Production Stage: Menggunakan base image PHP-FPM alpine. Runtime ini hanya menyalin artefak kompilasi
public/builddari Frontend Stage dan direktorivendordari Backend Stage. Node.js maupun pnpm/npm tidak pernah masuk ke stage runtime akhir.
Implementasi Dockerfile Multi-Stage dengan BuildKit Cache Mounts
Penggunaan BuildKit cache mounts memastikan bahwa cache paket (pnpm store dan composer cache) tetap tersimpan di luar image layer dan dapat digunakan kembali antar proses build lokal maupun CI runner.
# syntax=docker/dockerfile:1.4
# --- STAGE 1: Frontend Builder ---
FROM node:22-alpine AS frontend-builder
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest --activate
# Salin manifest dependensi terlebih dahulu untuk utilisasi layer cache
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
# Salin kode sumber frontend dan konfigurasi build
COPY vite.config.ts tsconfig.json ./
COPY resources/ ./resources/
COPY public/ ./public/
RUN pnpm run build
# --- STAGE 2: Backend Builder ---
FROM composer:2.7 AS backend-builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/cache \
composer install \
--no-dev \
--no-interaction \
--prefer-dist \
--optimize-autoloader \
--no-scripts
# --- STAGE 3: Production Runtime ---
FROM php:8.3-fpm-alpine AS runner
WORKDIR /var/www/html
# Instal dependensi sistem dan ekstensi PHP runtime yang relevan
RUN docker-php-ext-install opcache pdo_mysql
# Salin kode sumber backend dasar
COPY . .
# Salin artefak terisolasi dari stage sebelumnya
COPY --from=backend-builder /app/vendor ./vendor
COPY --from=frontend-builder /app/public/build ./public/build
# ponytail: skip non-root user setup for local dev; add prior to production deployment.
USER www-data
EXPOSE 9000
CMD ["php-fpm"]Konfigurasi GitHub Actions dengan Caching Terisolasi
Pada pipeline GitHub Actions, pemisahan cache antara pnpm dan Composer adalah keharusan. Perubahan pada file frontend tidak boleh memicu invalidasi cache backend, dan sebaliknya.
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup PHP Environment
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- name: Cache Composer Dependencies
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ runner.os }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-
- name: Install Composer Dependencies
run: composer install --prefer-dist --no-progress
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install Frontend Dependencies
run: pnpm install --frozen-lockfile
- name: Build Assets with Vite
run: pnpm run build
- name: Verify Vite Manifest Integrity
run: |
test -f public/build/manifest.json || { echo "manifest.json not found"; exit 1; }
jq -e '.["resources/js/app.tsx"]' public/build/manifest.json > /dev/null || {
echo "Entrypoint resources/js/app.tsx is missing in manifest.json";
exit 1;
}
- name: Execute Backend Tests
run: php artisan test --parallelVerifikasi Manifest Vite dalam CI: Mencegah HTTP 500 Runtime
Saat aplikasi Laravel menerima request yang ditangani Inertia, helper @vite membaca berkas public/build/manifest.json untuk me-resolve nama chunk asset yang telah diberi content hash (misalnya: app-B3x9a1.js). Jika direktori public/build tidak tersalin secara benar atau nama entrypoint tidak sinkron, Laravel akan melempar ViteManifestNotFoundException atau ViteEntrypointNotFoundException, menghasilkan HTTP 500 seketika pada user.
Untuk mencegah image rusak terdorong ke registry container:
- Gunakan utility
jqdalam CI step untuk memastikan filemanifest.jsonvalid secara format JSON dan memiliki key file entrypoint utama (misal:resources/js/app.tsatauresources/js/app.tsx). - Jalankan verifikasi ini sebelum langkah container packaging atau automated deployment dijalankan.
Trade-off: Aggressive Cache Retention vs Hash Invalidation
Vite memanfaatkan caching disk tingkat rendah di folder node_modules/.vite untuk mempercepat pra-bundling dependensi. Namun, caching artefak ini di CI memiliki konsekuensi signifikan:
1. Cache node_modules/.vite di CI
Trade-off: Menyimpan direktori node_modules/.vite di cache CI dapat memangkas 5-15 detik waktu build, tetapi meningkatkan risiko asset hash yang basi (stale chunk hashing) jika konfigurasi internal postcss, tailwind, atau alias bundler berubah tanpa perubahan di pnpm-lock.yaml.
Rekomendasi: Jangan lakukan cache pada folder node_modules/.vite atau public/build di CI. Cukup lakukan caching pada package manager store (pnpm store atau Composer global cache). Biarkan Vite mengompilasi dari scratch (clean build) pada level file input yang dijamin oleh lockfile.
2. Registry Cache Layer (Docker Buildx)
Trade-off: Menggunakan GitHub Actions cache backend (type=gha) untuk Docker layer dapat mempercepat build image secara masif, tetapi jika file resources/ sering berubah, layer frontend akan selalu invalid. Memisahkan step build frontend ke runner CI native (lalu menyalin hasilnya ke konteks Docker via COPY public/build) sering kali lebih cepat dibanding menjalankan keseluruhan Vite compile di dalam emulator/Docker context runner CI.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!