Menghubungkan aplikasi Laravel ke PostgreSQL yang berjalan di atas Longhorn: menyusun Deployment, mengatur ConfigMap untuk DB connection, Secret untuk kredensial, dan menjalankan migrasi via Kubernetes Job dengan init container untuk menunggu database siap.

PostgreSQL telah berjalan sebagai StatefulSet dengan volume persisten di episode 6. Kini tibalah saat menghidupkan aplikasi Laravel di sisi klien — aplikasi web yang membebani database itu. Kalian akan melihat pola arsitektur lengkap: aplikasi stateless di Deployment, database stateful di StatefulSet+Longhorn, dan koneksi lewat ConfigMap/Secret.
Mengapa episode ini penting? Karena inilah titik di mana seluruh alur kerja production mulai terasa: aplikasi mengambil DB_HOST=postgres, menjalankan migrasi schema, dan menulis data nyata ke volume block yang direplikasi. Ini juga memperkenalkan pola Job migrasi + init/wait container yang akan kalian pakai terus.
Laravel mengarahkan database connection ke service postgres:
.env / ConfigMap: DB_CONNECTION=pgsql DB_HOST=postgres DB_PORT=5432
Laravel Pod ────> Service: postgres ────> StatefulSet postgres-0 ────> Longhorn volumeapiVersion: apps/v1
kind: Deployment
metadata:
name: laravel
labels:
app: laravel
spec:
replicas: 2
selector:
matchLabels:
app: laravel
template:
metadata:
labels:
app: laravel
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z postgres 5432; do
echo "menunggu postgres..."; sleep 2;
done
containers:
- name: app
image: registry.example.com/laravel-app:latest
ports:
- containerPort: 9000
envFrom:
- configMapRef:
name: laravel-config
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
- name: APP_KEY
valueFrom:
secretKeyRef:
name: laravel-secret
key: app_key
livenessProbe:
httpGet:
path: /
port: 9000Aplikasi tidak menyimpan data lokal — semua state hidup di database. Inilah mengapa Laravel bisa dijalankan sebagai Deployment stateless dengan banyak replica, sedangkan PostgreSQL tetap satu primary di StatefulSet RWO.
apiVersion: v1
kind: ConfigMap
metadata:
name: laravel-config
data:
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: "5432"
DB_DATABASE: laravel
APP_ENV: production
APP_DEBUG: "false"Perhatikan DB_HOST: postgres — nama service. Karena Service headless, DNS postgres.default.svc.cluster.local meresolusi Pod postgres-0.
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
stringData:
username: laravel
password: PastaPanjangYangRahasiaAPP_KEY juga menjadi Secret — jangan pernah commit ke image.
Migration schema adalah operasi sekali jalan yang cukup serius. Deployment multi-replica tidak boleh menjalankannya bersamaan (race condition pada schema). Pola terbaik: Kubernetes Job yang menjalankan php artisan migrate --force lalu selesai.
apiVersion: batch/v1
kind: Job
metadata:
name: migrate
spec:
template:
spec:
restartPolicy: OnFailure
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z postgres 5432; do
echo "menunggu postgres..."; sleep 2;
done
containers:
- name: migrate
image: registry.example.com/laravel-app:latest
command:
- php
- artisan
- migrate
- --force
envFrom:
- configMapRef:
name: laravel-config
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: passwordWarning
Jangan jalankan php artisan migrate dari shell Pod production tanpa pengawasan — race dengan replica lain bisa merusak schema. Selalu pakai Job atau pipeline yang menghapus Job setelah selesai. Pada environment dev (episode 8), migrate:fresh boleh dipakai karena data tidak krusial.
migrate → sukses.kubectl logs, kubectl port-forward, buka halaman.Inti yang harus dibawa pulang:
DB_HOST/DB_DATABASE; Secret membawa username/password/APP_KEY.php artisan migrate --force menjalankan migrasi schema sekali di hadapan Deployment.wait-for-db mencegah aplikasi start sebelum database siap.Di episode 8 selanjutnya kita akan membahas migrations, seeds, dan data lifecycle — menjalankan seed di environment dev, membuat snapshot di Longhorn, menyetel backup target S3/NFS, serta aturan emas data lifecycle agar tidak ada data state yang tersimpan di emptyDir. Sampai jumpa di episode 8!