Table of Contents
▼- Kenapa Config Development Docker Berbahaya di Production
- 5 Kesalahan Security Docker Laravel yang Fatal
- Optimasi Docker Image untuk Performa Laravel
- Setup Health Check dan Auto-Restart yang Benar
- Monitoring dan Logging Docker Container Production
- Checklist Deploy Docker Laravel Production
- Kesimpulan
Deploy aplikasi Laravel dengan Docker memang terlihat mudah.
Copy file docker-compose.yml dari tutorial, jalankan docker-compose up -d, dan aplikasi sudah jalan di VPS.
Tapi banyak developer yang tidak sadar bahwa konfigurasi Docker yang mereka pakai masih menggunakan setting development.
Akibatnya? Aplikasi rentan diretas, performa lambat, dan crash tiba-tiba saat traffic naik.
Artikel ini akan membahas kesalahan umum setup Docker untuk Laravel di production dan cara memperbaikinya dengan benar.
Kenapa Config Development Docker Berbahaya di Production
Setting Docker untuk development dan production punya tujuan yang berbeda.
Development fokus pada kemudahan coding: hot reload, debug mode, volume mounting langsung ke source code.
Production fokus pada keamanan, stabilitas, dan performa: image yang minimal, environment yang terisolasi, resource yang dioptimasi.
Kalau kamu deploy dengan config development, aplikasi Laravel kamu akan:
Expose informasi sensitif seperti stack trace error lengkap yang bisa dibaca hacker.
Konsumsi resource berlebihan karena dependency development seperti Xdebug ikut ter-install.
Berisiko data hilang karena volume mounting yang salah atau tidak persistent.
Rentan terhadap serangan karena port yang tidak perlu terbuka dan permission file yang terlalu longgar.
Mari kita bahas satu per satu kesalahan yang paling sering terjadi.
5 Kesalahan Security Docker Laravel yang Fatal
1. Running Container as Root User
Ini kesalahan paling umum dan paling berbahaya.
Default Docker container berjalan sebagai root user.
Kalau ada vulnerability di aplikasi atau library yang kamu pakai, hacker bisa mengeksekusi command dengan privilege root di dalam container.
Bahkan bisa escape ke host system kalau ada vulnerability di Docker engine.
Solusi yang benar:
Buat user non-root di Dockerfile dan jalankan aplikasi dengan user tersebut.
FROM php:8.2-fpm
# Install dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
# Create non-root user
RUN groupadd -g 1000 laravel && \
useradd -u 1000 -g laravel -m -s /bin/bash laravel
# Set working directory
WORKDIR /var/www
# Copy application
COPY --chown=laravel:laravel . /var/www
# Switch to non-root user
USER laravel
EXPOSE 9000
CMD ["php-fpm"]
Dengan cara ini, aplikasi Laravel akan berjalan dengan user laravel yang memiliki privilege terbatas.
2. Menggunakan Debug Mode di Production
Banyak developer lupa set APP_DEBUG=false di file .env production.
Akibatnya, setiap error akan menampilkan full stack trace yang expose:
- Struktur direktori aplikasi
- Database credentials
- API keys dan secrets
- Versi library yang dipakai
- Query SQL yang dijalankan
Informasi ini sangat berharga buat hacker untuk menyerang aplikasi kamu.
Solusi yang benar:
Pastikan environment variable di production sudah benar:
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:random-key-yang-kuat
Jangan pernah commit file .env ke Git repository.
Gunakan Docker secrets atau environment variable dari docker-compose untuk inject credential:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
environment:
- APP_ENV=production
- APP_DEBUG=false
- DB_HOST=mysql
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
Load environment variable dari host system menggunakan file .env yang tidak di-commit.
3. Expose Port yang Tidak Perlu
Developer sering expose semua port service ke host untuk kemudahan debugging.
Di production, ini membuka celah keamanan.
Contoh konfigurasi yang salah:
services:
mysql:
image: mysql:8.0
ports:
- "3306:3306" # MySQL bisa diakses dari luar
redis:
image: redis:alpine
ports:
- "6379:6379" # Redis bisa diakses dari luar
Dengan config di atas, MySQL dan Redis bisa diakses langsung dari internet kalau firewall VPS tidak dikonfigurasi dengan benar.
Solusi yang benar:
Jangan expose port internal service.
Biarkan service hanya bisa diakses dari dalam Docker network:
services:
app:
build: .
networks:
- laravel
mysql:
image: mysql:8.0
# Tidak ada port mapping
networks:
- laravel
redis:
image: redis:alpine
# Tidak ada port mapping
networks:
- laravel
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
networks:
- laravel
networks:
laravel:
driver: bridge
Hanya Nginx yang expose port 80 dan 443 untuk menerima traffic dari luar.
Service lain hanya bisa diakses melalui internal Docker network.
4. Tidak Menggunakan Secret untuk Credential
Banyak developer hardcode database password atau API key langsung di docker-compose.yml.
File ini biasanya di-commit ke Git, yang artinya credential ter-expose di repository.
Solusi yang benar:
Gunakan Docker secrets atau environment variable yang di-load dari file yang tidak di-commit:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_DATABASE: laravel
MYSQL_USER_FILE: /run/secrets/db_user
MYSQL_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_root_password
- db_user
- db_password
secrets:
db_root_password:
file: ./secrets/db_root_password.txt
db_user:
file: ./secrets/db_user.txt
db_password:
file: ./secrets/db_password.txt
File di direktori secrets/ harus ditambahkan ke .gitignore.
5. Volume Mounting yang Salah
Di development, kita biasa mount source code langsung dari host ke container supaya perubahan langsung keliatan.
Di production, ini berbahaya karena:
- File permission di host bisa salah
- Source code bisa ter-edit dari luar container
- Performa I/O lambat
Solusi yang benar:
Copy source code ke dalam image saat build, bukan mount dari host:
# Copy composer files
COPY composer.json composer.lock ./
# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Copy application code
COPY . .
# Set permissions
RUN chown -R laravel:laravel /var/www && \
chmod -R 755 /var/www/storage
Untuk data yang perlu persistent (storage, logs), gunakan Docker volume:
services:
app:
build: .
volumes:
- storage-data:/var/www/storage/app
- logs-data:/var/www/storage/logs
volumes:
storage-data:
driver: local
logs-data:
driver: local
Optimasi Docker Image untuk Performa Laravel
Image Docker yang besar akan memperlambat deployment dan konsumsi resource berlebihan.
Berikut cara optimasi image Laravel untuk production.
Gunakan Multi-Stage Build
Multi-stage build memungkinkan kita build dependency di stage terpisah, lalu copy hasil build ke final image.
Ini membuat image final lebih kecil karena tidak include build tools.
# Stage 1: Build dependencies
FROM composer:2.5 AS composer-builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
COPY . .
RUN composer dump-autoload --optimize
# Stage 2: Production image
FROM php:8.2-fpm-alpine
# Install only production dependencies
RUN apk add --no-cache \
nginx \
supervisor \
mysql-client \
&& docker-php-ext-install pdo_mysql opcache
# Copy built application from composer stage
COPY --from=composer-builder /app /var/www
# Create non-root user
RUN addgroup -g 1000 laravel && \
adduser -D -u 1000 -G laravel laravel
RUN chown -R laravel:laravel /var/www
USER laravel
EXPOSE 9000
CMD ["php-fpm"]
Image final hanya berisi production dependencies tanpa Composer atau build tools.
Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.
Enable OPcache untuk Performa PHP
OPcache meng-cache PHP bytecode di memory sehingga tidak perlu compile script setiap request.
Ini bisa meningkatkan performa hingga 3x lipat.
Buat file konfigurasi OPcache di docker/php/opcache.ini:
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=0
opcache.validate_timestamps=0
opcache.fast_shutdown=1
Copy file ini ke container saat build:
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
Setting opcache.validate_timestamps=0 membuat OPcache tidak check perubahan file, sehingga performa maksimal.
Kalau ada update code, restart container untuk clear cache.
Minimize Layer dan Clean Up
Setiap command RUN di Dockerfile membuat layer baru.
Combine multiple commands jadi satu layer untuk reduce image size:
# Bad: Multiple layers
RUN apt-get update
RUN apt-get install -y git
RUN apt-get install -y curl
RUN apt-get clean
# Good: Single layer with cleanup
RUN apt-get update && \
apt-get install -y git curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Cleanup package cache di command yang sama untuk memastikan file temporary tidak tersimpan di layer.
Setup Health Check dan Auto-Restart yang Benar
Container bisa crash atau aplikasi bisa hang tanpa container mati.
Health check memastikan Docker bisa detect kalau aplikasi bermasalah dan restart otomatis.
Implement Health Check Endpoint
Buat route health check di Laravel yang cek koneksi database dan service penting:
// routes/web.php
Route::get('/health', function () {
try {
// Check database connection
DB::connection()->getPdo();
// Check Redis connection if used
if (config('cache.default') === 'redis') {
Cache::has('health-check');
}
return response()->json([
'status' => 'healthy',
'timestamp' => now()
]);
} catch (\Exception $e) {
return response()->json([
'status' => 'unhealthy',
'error' => $e->getMessage()
], 500);
}
});
Route ini tidak perlu authentication karena diakses oleh Docker health check.
Configure Docker Health Check
Tambahkan health check configuration di Dockerfile:
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost/health || exit 1
Parameter yang perlu diperhatikan:
--interval=30s: Check setiap 30 detik--timeout=10s: Health check dianggap gagal kalau tidak response dalam 10 detik--start-period=60s: Grace period 60 detik saat container pertama start--retries=3: Container dianggap unhealthy setelah 3 kali gagal berturut-turut
Setup Restart Policy
Configure restart policy di docker-compose untuk auto-restart container yang crash:
services:
app:
build: .
restart: unless-stopped
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
Policy unless-stopped membuat container restart otomatis kecuali di-stop manual.
Deploy restart policy membatasi jumlah restart attempt untuk prevent infinite restart loop.
Monitor Health Check Status
Check health status container menggunakan command:
docker ps --format "table {{.Names}}\t{{.Status}}"
Output akan menampilkan status health check:
NAMES STATUS
laravel-app Up 2 hours (healthy)
laravel-mysql Up 2 hours (healthy)
laravel-nginx Up 2 hours (healthy)
Kalau ada container unhealthy, investigate logs untuk cari root cause:
docker logs laravel-app --tail 100
Monitoring dan Logging Docker Container Production
Tanpa monitoring dan logging yang proper, kamu tidak akan tahu kalau ada masalah sampai user komplain.
Centralized Logging dengan Docker Logging Driver
Default Docker logging driver menyimpan log di JSON file yang bisa membengkak dan susah dianalisa.
Configure logging driver untuk kirim log ke centralized logging system:
services:
app:
build: .
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "service=laravel-app"
Dengan konfigurasi di atas, Docker akan:
- Rotate log file otomatis ketika mencapai 10MB
- Keep maksimal 3 file log
- Tag log dengan label service untuk filtering
Untuk production yang lebih besar, gunakan logging driver seperti syslog atau fluentd untuk kirim log ke sistem monitoring external.
Setup Log Aggregation
Install ELK Stack (Elasticsearch, Logstash, Kibana) atau alternatif seperti Grafana Loki untuk aggregate dan visualisasi log.
Contoh konfigurasi Fluentd logging driver:
services:
app:
build: .
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "docker.laravel.app"
fluentd:
image: fluent/fluentd:v1.16-1
ports:
- "24224:24224"
volumes:
- ./fluentd/conf:/fluentd/etc
- fluentd-logs:/fluentd/log
Fluentd akan collect log dari semua container dan forward ke Elasticsearch atau storage lain.
Monitor Resource Usage
Install cAdvisor atau Prometheus untuk monitor resource usage container:
services:
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- "8080:8080"
Akses dashboard cAdvisor di http://vps-ip:8080 untuk lihat real-time metrics:
- CPU usage per container
- Memory usage dan limit
- Network I/O
- Disk I/O
Setup Alert untuk Anomaly
Configure alerting rules untuk notify kamu kalau ada masalah:
- Container restart lebih dari 3 kali dalam 10 menit
- Memory usage di atas 80% untuk periode tertentu
- Error rate di atas threshold
- Response time meningkat signifikan
Gunakan tools seperti Alertmanager atau PagerDuty untuk kirim notifikasi ke Slack, email, atau SMS.
Laravel Specific Logging Best Practices
Configure Laravel logging untuk production di config/logging.php:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'slack'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'error'),
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
],
Setting LOG_LEVEL=error di production untuk hanya log error dan critical events.
Kirim critical errors ke Slack untuk immediate notification.
Checklist Deploy Docker Laravel Production
Sebelum deploy ke production, pastikan semua item di checklist ini sudah di-check:
Security:
- Container running as non-root user
APP_DEBUG=falsedanAPP_ENV=production- Secrets menggunakan Docker secrets atau external vault
- Port internal service tidak ter-expose
- SSL/TLS certificate terpasang di Nginx
- File permission sudah benar (755 untuk direktori, 644 untuk file)
Performance:
- OPcache enabled dan configured
- Multi-stage build untuk minimize image size
- Static assets di-serve oleh Nginx, bukan PHP
- Database connection pooling configured
- Cache driver menggunakan Redis, bukan file
Reliability:
- Health check endpoint implemented
- Restart policy configured
- Backup strategy untuk data dan database
- Resource limits (CPU, memory) defined
- Logging dan monitoring tools installed
Maintenance:
- Deployment automation script atau CI/CD pipeline
- Rollback strategy kalau deployment gagal
- Documentation untuk deployment procedure
- Contact person untuk emergency
Kesimpulan
Setup Docker untuk Laravel production bukan sekadar copy-paste docker-compose.yml dari tutorial.
Butuh perhatian khusus pada security, performance, dan reliability.
Kesalahan konfigurasi yang tampak sepele seperti running container as root atau expose port yang tidak perlu bisa berujung pada data breach atau downtime yang costly.
Invest waktu untuk setup Docker dengan benar sejak awal akan save kamu dari headache di masa depan.
Start dengan checklist di atas, dan improve security dan monitoring secara incremental sesuai kebutuhan aplikasi kamu.
Docker yang dikonfigurasi dengan benar tidak hanya membuat deployment lebih mudah, tapi juga membuat aplikasi Laravel kamu lebih secure dan reliable di production.