Membangun environment development Laravel dengan Docker memberi beberapa keuntungan praktis: dependensi sistem menjadi konsisten antar mesin, proses onboarding tim lebih mudah, dan konfigurasi service seperti PHP, Nginx, MySQL, serta Redis dapat didefinisikan sebagai kode. Untuk project Laravel, pendekatan ini sangat membantu karena aplikasi modern umumnya tidak berdiri sendiri; ia membutuhkan web server, database, cache, dan runtime PHP dengan extension tertentu.

Pada artikel ini, kita akan menyiapkan project Laravel dengan Docker dari nol untuk kebutuhan local development. Fokus utama kita adalah membuat susunan container yang jelas: app untuk PHP-FPM, nginx untuk web server, mysql untuk database, dan redis untuk cache/queue sederhana. Di akhir, kita akan memverifikasi aplikasi berjalan, menjalankan perintah Artisan dari dalam container, dan membahas masalah umum yang sering muncul.

Catatan: Artikel ini berfokus pada fondasi environment development. Topik seperti queue worker, scheduler, testing container, multi-stage build, dan optimasi image untuk production lebih cocok dibahas pada bagian berikutnya.

Mengapa Memisahkan Service di Docker?

Kesalahan umum saat mulai memakai Docker adalah mencoba memasukkan semua komponen ke satu container. Secara teknis hal itu bisa dilakukan, tetapi kurang ideal. Dalam praktik yang baik, setiap service dipisahkan berdasarkan tanggung jawabnya.

  • app: menjalankan PHP-FPM dan kode Laravel.
  • nginx: menerima request HTTP dan meneruskannya ke PHP-FPM.
  • mysql: menyediakan database relasional.
  • redis: untuk cache, session, atau queue backend ringan.

Pemisahan ini penting karena:

  • Lebih mudah dirawat: jika Nginx bermasalah, kita cukup cek container Nginx tanpa mengganggu MySQL.
  • Lebih fleksibel: versi MySQL atau Redis bisa diganti tanpa membangun ulang seluruh stack.
  • Lebih dekat ke arsitektur nyata: meskipun ini environment lokal, pola ini lebih mirip deployment modern.
  • Debugging lebih sederhana: log setiap service terpisah.

Selain service, kita juga akan memanfaatkan network Docker. Network internal memungkinkan container saling berkomunikasi menggunakan nama service, misalnya mysql atau redis, tanpa harus mengetahui IP dinamis container. Ini membuat konfigurasi lebih stabil dan mudah dipahami.

Struktur Folder Project

Supaya konfigurasi rapi, buat struktur folder seperti berikut:

laravel-docker-app/
├── app/                     
├── bootstrap/
├── config/
├── database/
├── docker/
│   ├── nginx/
│   │   └── default.conf
│   └── php/
│       └── Dockerfile
├── public/
├── resources/
├── routes/
├── storage/
├── .env
├── docker-compose.yml
└── artisan

Folder docker/ digunakan untuk menyimpan semua file konfigurasi yang terkait container. Ini lebih baik daripada mencampur file Docker ke root project tanpa struktur yang jelas. Nantinya, jika stack bertambah kompleks, kita bisa menambahkan folder lain seperti docker/mysql, docker/supervisor, atau docker/queue.

Membuat Dockerfile untuk PHP-FPM

Laravel tidak langsung melayani request HTTP. Biasanya Laravel berjalan di atas PHP-FPM, lalu Nginx meneruskan request PHP ke service tersebut. Karena itu, kita butuh image khusus untuk service app.

Buat file docker/php/Dockerfile:

FROM php:8.2-fpm

RUN apt-get update && apt-get install -y \
    git \
    curl \
    unzip \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    libzip-dev \
    default-mysql-client \
    && docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd zip

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /var/www

Penjelasannya:

  • php:8.2-fpm dipakai karena cocok untuk memisahkan web server dan PHP runtime.
  • Paket seperti libzip-dev, libpng-dev, dan extension pdo_mysql diperlukan Laravel atau package umum di ekosistemnya.
  • Composer disalin dari image resmi Composer agar kita tidak perlu instal manual.
  • WORKDIR /var/www menjadi direktori kerja utama di container.

Untuk development, Dockerfile ini sudah cukup. Nanti pada tahap lanjutan, kita bisa menambahkan user non-root, opcache, atau optimasi build agar image lebih aman dan efisien.

Menyusun docker-compose.yml

File docker-compose.yml adalah pusat definisi seluruh stack. Di sini kita mendeskripsikan service, port, volume, environment, dan network.

version: '3.9'

services:
  app:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    container_name: laravel_app
    working_dir: /var/www
    volumes:
      - ./:/var/www
    depends_on:
      - mysql
      - redis
    networks:
      - laravel

  nginx:
    image: nginx:alpine
    container_name: laravel_nginx
    ports:
      - "8000:80"
    volumes:
      - ./:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app
    networks:
      - laravel

  mysql:
    image: mysql:8.0
    container_name: laravel_mysql
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: root
      MYSQL_PASSWORD: secret
      MYSQL_USER: laravel
    ports:
      - "3307:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - laravel

  redis:
    image: redis:alpine
    container_name: laravel_redis
    ports:
      - "6379:6379"
    networks:
      - laravel

volumes:
  mysql_data:

networks:
  laravel:

Beberapa poin penting:

  • Volume ./:/var/www membuat source code lokal langsung tersedia di container. Ini cocok untuk development karena perubahan file langsung terlihat tanpa rebuild image.
  • Port 8000:80 berarti Nginx di container bisa diakses dari host melalui http://localhost:8000.
  • Port MySQL 3307:3306 dipakai agar tidak bentrok jika host Anda sudah menjalankan MySQL lokal di port 3306.
  • Named volume mysql_data menjaga data database tetap ada meskipun container dihapus dan dibuat ulang.
  • depends_on membantu urutan startup, tetapi bukan jaminan service benar-benar siap menerima koneksi. Ini penting saat debugging koneksi database saat startup awal.

Konfigurasi Nginx Dasar

Buat file docker/nginx/default.conf:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    root /var/www/public;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location ~ /\.ht {
        deny all;
    }
}

Kenapa konfigurasi ini bekerja?

  • root /var/www/public menunjuk ke folder publik Laravel, bukan root project. Ini penting untuk keamanan dan routing yang benar.
  • try_files akan mencoba file statis lebih dulu, lalu meneruskan request ke index.php jika route ditangani Laravel.
  • fastcgi_pass app:9000 memakai nama service Docker, bukan IP. Karena berada di network yang sama, Nginx bisa menjangkau PHP-FPM melalui hostname app.

Kesalahan umum di tahap ini adalah mengarahkan root ke /var/www alih-alih /var/www/public. Akibatnya, asset dan routing Laravel sering gagal atau lebih buruk, file sensitif bisa terekspos.

Menyiapkan Laravel dan Dependency

Jika project belum ada, Anda bisa membuatnya dari dalam container agar versi Composer dan PHP konsisten dengan environment Docker.

Bangun dan jalankan container terlebih dulu:

docker compose up -d --build

Lalu buat project Laravel di direktori kerja saat ini:

docker compose run --rm app composer create-project laravel/laravel .

Jika project sudah ada, cukup install dependency:

docker compose exec app composer install

Setelah itu, salin file environment:

cp .env.example .env

Lalu generate application key:

docker compose exec app php artisan key:generate

Konfigurasi .env untuk Docker

Karena Laravel berjalan di dalam network Docker, host database dan Redis bukan 127.0.0.1, melainkan nama service pada docker-compose.yml.

Sesuaikan bagian penting di file .env:

APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret

CACHE_STORE=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

Hal yang perlu diperhatikan:

  • DB_HOST=mysql, bukan localhost. Di dalam container, localhost berarti container itu sendiri.
  • DB_PORT=3306 karena itu port internal antar-container. Port host 3307 hanya dipakai jika Anda mengakses MySQL dari mesin lokal, misalnya via TablePlus atau DBeaver.
  • APP_URL disesuaikan dengan port yang diekspos oleh Nginx di host.

Permission Storage dan Bootstrap Cache

Laravel membutuhkan hak tulis pada folder storage dan bootstrap/cache. Jika permission salah, gejalanya bisa berupa error log, cache gagal ditulis, atau halaman 500 tanpa penjelasan yang jelas.

Jalankan perintah berikut:

docker compose exec app chmod -R 775 storage bootstrap/cache

Pada beberapa sistem, terutama Linux, Anda mungkin perlu menyesuaikan owner file agar cocok dengan user di host atau container. Untuk tahap awal development, pengaturan permission di atas biasanya cukup. Jika masih bermasalah, cek user yang menjalankan PHP-FPM dan siapa pemilik file di host.

Menjalankan Migrasi dan Verifikasi Koneksi Database

Setelah semua service aktif, uji koneksi database dengan migrasi bawaan Laravel:

docker compose exec app php artisan migrate

Jika berhasil, berarti koneksi Laravel ke MySQL berjalan dengan benar. Anda juga bisa menguji Redis secara sederhana melalui Tinker atau fitur cache di tahap berikutnya.

Untuk melihat log service tertentu:

docker compose logs app

docker compose logs nginx

docker compose logs mysql

Log adalah alat debugging pertama yang paling berguna. Jangan langsung mengubah banyak konfigurasi sebelum memeriksa log; sering kali penyebab error sudah terlihat di sana.

Verifikasi Aplikasi Berjalan

Pada titik ini, stack development minimal sudah siap. Lakukan verifikasi berikut:

  1. Pastikan semua container aktif dengan docker compose ps.
  2. Buka http://localhost:8000 di browser.
  3. Jika halaman default Laravel muncul, artinya alur Nginx -> PHP-FPM -> Laravel sudah berjalan.
  4. Jalankan docker compose exec app php artisan route:list untuk memastikan Artisan bisa diakses dari container.

Beberapa perintah Artisan yang sering dipakai selama development:

docker compose exec app php artisan migrate

docker compose exec app php artisan db:seed

docker compose exec app php artisan cache:clear

docker compose exec app php artisan config:clear

docker compose exec app php artisan route:list

Keuntungan menjalankan perintah ini dari dalam container adalah konsistensi environment. Anda tidak bergantung pada instalasi PHP lokal host yang mungkin berbeda versi atau extension-nya tidak lengkap.

Masalah Umum dan Solusinya

1. Error koneksi database

Gejala: SQLSTATE[HY000] [2002] Connection refused atau php_network_getaddresses.

  • Pastikan DB_HOST=mysql, bukan localhost.
  • Pastikan service MySQL sudah benar-benar siap. Container bisa aktif, tetapi database belum selesai inisialisasi.
  • Cek kredensial di .env dan environment MySQL pada Compose.

2. Permission denied pada storage atau cache

  • Jalankan ulang chmod -R 775 storage bootstrap/cache.
  • Periksa owner file jika memakai Linux dan bind mount dari host.

3. 502 Bad Gateway dari Nginx

  • Biasanya Nginx tidak bisa menjangkau PHP-FPM.
  • Pastikan fastcgi_pass app:9000 sesuai nama service.
  • Cek log Nginx dan app untuk melihat apakah PHP-FPM berjalan normal.

4. Perubahan file tidak terlihat

  • Pastikan volume project ter-mount dengan benar: ./:/var/www.
  • Jika memakai WSL2, Docker Desktop, atau filesystem network tertentu, kadang performa file sync lebih lambat.

5. Port bentrok

  • Jika 8000, 3307, atau 6379 sudah dipakai host, ganti mapping port di docker-compose.yml.
  • Port internal container biasanya tidak perlu diubah.

Penutup

Sampai di sini, kita sudah memiliki fondasi environment Laravel berbasis Docker yang cukup baik untuk local development: service dipisahkan dengan jelas, Nginx mengarah ke PHP-FPM, MySQL dan Redis tersedia di network internal, dependency dikelola konsisten, dan konfigurasi Laravel sudah menyesuaikan nama service Docker.

Pendekatan ini bukan sekadar agar “Laravel bisa jalan”, tetapi agar project mudah dirawat, mudah dipahami anggota tim lain, dan siap dikembangkan ke kebutuhan berikutnya. Di part selanjutnya, fondasi ini bisa diperluas ke topik seperti hot reload aset frontend, queue worker, scheduler, optimasi workflow development, atau penyesuaian image agar lebih dekat ke environment production.