Memuat...
👋 Selamat Pagi!

Kesalahan Umum Setup Docker untuk Laravel di VPS Production

Banyak developer Laravel deploy dengan Docker tapi config production masih pakai setting development. Hindari kesalahan fatal yang bikin aplikasi rentan dan lam...

Kesalahan Umum Setup Docker untuk Laravel di VPS Production

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=false dan APP_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.

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, React.js, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang