Backend••10 min read•20 views

Deploy Laravel dengan Docker: Panduan Langkah Demi Langkah

Docker bukan lagi sekadar tren, melainkan standar de facto arsitektur software saat ini. Menguasai containerization membuat workflow development kamu jauh lebih konsisten, aman, dan mudah dideploy ke cloud provider mana pun tanpa kejutan error di tengah malam.

Pernah nggak kamu dengar kalimat klasik ini di kantor atau tongkrongan developer: "Lah, di laptop gue jalan lancar kok, pas naik ke server malah error 500!"?

Kalau kamu pernah mengalaminya, tenang, kamu nggak sendirian. Masalah beda versi PHP, ekstensi database yang lupa diaktifkan di VPS, atau versi MySQL yang beda minor release adalah makanan sehari-hari kalau kita masih deploy aplikasi secara manual.

Di dunia software engineering modern, masalah tersebut diselesaikan dengan kontainerisasi, dan Docker adalah standarnya. Dengan Docker, aplikasi Laravel kita dibungkus rapi bersama seluruh dependensinya—mulai dari runtime PHP, web server Nginx, database MySQL, sampai caching layer seperti Redis. Sekali jalan di laptop kamu, dijamin perilakunya 100% sama ketika naik ke server staging maupun production.

Sebagai software engineer, memahami alur kerja Docker bukan cuma bikin proses deploy jadi mulus tanpa begadang, tapi juga menjaga arsitektur aplikasi tetap modular, scalable, dan gampang di-maintain. Yuk, kita bedah tuntas panduan langkah demi langkah deploy Laravel menggunakan Docker dari nol sampai live.

1. Arsitektur Stack yang Akan Kita Bangun

Jangan buru-buru ngetik baris perintah sebelum paham arsitektur sistemnya. Kita nggak akan menjejalkan Laravel, Nginx, dan MySQL ke dalam satu container layaknya mesin virtual (VM) zaman purba.

Filosofi inti Docker adalah "One container, one concern" (satu container untuk satu tanggung jawab). Struktur arsitektur yang kita rancang meliputi:

  • Service app (PHP-FPM): Container berbasis PHP 8.3/8.2-FPM yang khusus mengeksekusi script PHP dan framework Laravel. Di sini kita memasang ekstensi seperti pdo_mysql, mbstring, zip, dan opcache.
  • Service webserver (Nginx): Reverse proxy dan web server berperforma tinggi. Nginx menangani static files (CSS, JS, images) secara langsung dan meneruskan request file .php ke service app melalui FastCGI port 9000.
  • Service db (MySQL 8.0): Relational database management system yang datanya kita simpan di luar lifecycle container menggunakan Docker Named Volume, agar data tidak hilang saat container di-restart atau di-update.
  • Service redis (Opsional/Best Practice): Bertindak sebagai driver in-memory untuk caching, session storage, dan processing Laravel Queue.
  • Custom Bridge Network: Jaringan internal terisolasi agar container-container ini bisa saling ngobrol menggunakan nama service masing-masing sebagai hostname.
                    Internet / User
                           │
                           ▼
                 ┌───────────────────┐
                 │   Nginx (Port 80) │
                 └─────────┬─────────┘
        Static Files       │ FastCGI Request
        (CSS/JS/Img)       ▼ (Port 9000)
                 ┌───────────────────┐
                 │  PHP-FPM (Laravel)│
                 └────┬─────────┬────┘
                      │         │
    SQL Queries (3306)│         │ Cache / Queue (6379)
                      ▼         ▼
             ┌──────────┐     ┌───────────┐
             │ MySQL DB │     │ Redis Pod │
             └──────────┘     └───────────┘

2. Struktur Direktori Proyek

Kunci dari containerization yang rapi adalah struktur folder yang bersih. Tempatkan semua konfigurasi Docker di dalam folder terpisah bernama docker/ pada root direktori proyek Laravel kamu.

Susunannya akan terlihat seperti ini:

Plaintext
my-laravel-docker/
├── app/
├── bootstrap/
├── config/
├── database/
├── docker/
│   ├── nginx/
│   │   └── default.conf
│   └── php/
│       ├── Dockerfile
│       └── local.ini
├── public/
├── resources/
├── routes/
├── storage/
├── .env
├── .env.example
├── .dockerignore
├── composer.json
├── docker-compose.yml
└── package.json

3. Konfigurasi Komponen Docker

Mari kita buat file-file konfigurasinya satu per satu. Pastikan kamu teliti di bagian ini, karena konfigurasi sistem operasi container sangat berpengaruh pada performa dan keamanan.

A. Mengamankan Build Context: .dockerignore

Sebelum Docker membaca isi project, buat file .dockerignore di root direktori. Tujuannya mencegah file-file lokal yang berat atau sensitif ikut ter-copy ke dalam Docker image saat proses build.

Buat file .dockerignore:

Plaintext
.git
.github
node_modules
vendor
storage/logs/*
storage/framework/cache/data/*
storage/framework/sessions/*
storage/framework/views/*
.env
.env.backup
.phpunit.result.cache
npm-debug.log
yarn-error.log

B. Otak Runtime: docker/php/Dockerfile

File Dockerfile bertanggung jawab meracik base image Debian/Alpine, menginstal dependensi Linux level OS, meng-compile ekstensi PHP yang dibutuhkan Laravel, menginstal Composer, dan mengatur user permissions.

Buat file docker/php/Dockerfile:

Dockerfile
# Menggunakan base image PHP 8.3 FPM resmi yang stabil
FROM php:8.3-fpm

# Definisikan arguments untuk fleksibilitas UID/GID user host
ARG USER_ID=1000
ARG GROUP_ID=1000

# Set direktori kerja di dalam container
WORKDIR /var/www

# Install dependensi sistem level OS
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl \
    libzip-dev \
    libonig-dev \
    libxml2-dev \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# Install dan konfigurasi ekstensi inti PHP untuk Laravel
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-install pdo_mysql mbstring zip exif pcntl bcmath opcache

# Ambil binary Composer versi terbaru langsung dari image resmi Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Setup non-root user agar permission storage & cache Laravel tidak conflict dengan host
RUN groupadd -g ${GROUP_ID} appuser && \
    useradd -u ${USER_ID} -ms /bin/bash -g appuser appuser

# Pindahkan kepemilikan direktori kerja ke appuser
RUN chown -R appuser:appuser /var/www

# Gunakan non-root user untuk menjalankan proses
USER appuser

# Expose port FastCGI default
EXPOSE 9000

CMD ["php-fpm"]

C. Optimasi Konfigurasi PHP: docker/php/local.ini

Bawaan PHP default biasanya membatasi alokasi RAM dan upload file. Kita atur konfigurasi custom ini agar sesuai standar aplikasi enterprise.

Buat file docker/php/local.ini:

Ini, TOML
upload_max_filesize = 40M
post_max_size = 40M
memory_limit = 512M
max_execution_time = 300
date.timezone = Asia/Jakarta

; Opcache configuration untuk performa tinggi
opcache.enable = 1
opcache.enable_cli = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 2
opcache.fast_shutdown = 1

D. Reverse Proxy Web Server: docker/nginx/default.conf

Nginx bertugas menangani routing HTTP/HTTPS dan meneruskan eksekusi PHP ke container app via FastCGI.

Buat file docker/nginx/default.conf:

Nginx
server {
    listen 80;
    server_name localhost;
    root /var/www/public;

    index index.php index.html index.htm;
    charset utf-8;

    # Logging access dan error
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;

    # Pengaturan header keamanan standar
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

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

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    # Forward eksekusi script PHP ke service app di port 9000
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_hide_header X-Powered-By;
    }

    # Blokir akses langsung ke file tersembunyi seperti .env dan .git
    location ~ /\.(?!well-known).* {
        deny all;
    }
}

4. Orkestrasi dengan docker-compose.yml

Sekarang kita gabungkan semua puzzle di atas ke dalam satu file konduktor orkestrasi: docker-compose.yml. File ini yang menyalakan dan menghubungkan service webserver, aplikasi Laravel, database MySQL, dan Redis.

Buat file docker-compose.yml di root direktori proyek:

YAML
services:
  # Service 1: PHP-FPM Application Container
  app:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
      args:
        USER_ID: 1000
        GROUP_ID: 1000
    image: laravel-app:latest
    container_name: laravel_app
    restart: unless-stopped
    working_dir: /var/www
    volumes:
      - ./:/var/www
      - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini:ro
    networks:
      - laravel_network
    depends_on:
      - db
      - redis

  # Service 2: Nginx Web Server
  webserver:
    image: nginx:alpine
    container_name: laravel_webserver
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - laravel_network
    depends_on:
      - app

  # Service 3: MySQL Database
  db:
    image: mysql:8.0
    container_name: laravel_db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE:-laravel}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-secret}
      MYSQL_USER: ${DB_USERNAME:-laravel_user}
      MYSQL_PASSWORD: ${DB_PASSWORD:-secret}
    ports:
      - "3306:3306"
    volumes:
      - dbdata:/var/lib/mysql
    networks:
      - laravel_network

  # Service 4: Redis Cache & Queue Manager
  redis:
    image: redis:alpine
    container_name: laravel_redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redisdata:/data
    networks:
      - laravel_network

# Definisi Persistent Storage
volumes:
  dbdata:
    driver: local
  redisdata:
    driver: local

# Definisi Network Internal
networks:
  laravel_network:
    driver: bridge

5. Penyesuaian Environment Variable (.env)

Ini titik krusial yang sering bikin junior garuk-garuk kepala. Di lingkungan non-Docker, kita biasa mengarahkan DB_HOST ke 127.0.0.1 atau localhost.

Namun di dalam jaringan Docker, 127.0.0.1 pada container app merujuk ke dirinya sendiri, bukan ke container database. Supaya container app bisa menghubungi database dan cache, ganti host-nya dengan nama service yang kita daftarkan di docker-compose.yml.

Buka file .env Laravel kamu dan sesuaikan variabel koneksinya:

Cuplikan kode
APP_NAME="Laravel Docker App"
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=Asia/Jakarta
APP_URL=http://localhost

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

# Konfigurasi Database (Host diarahkan ke nama service: db)
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel_user
DB_PASSWORD=secret

# Konfigurasi Driver Cache & Queue ke Redis
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=redis
CACHE_STORE=redis
CACHE_PREFIX=

# Konfigurasi Redis (Host diarahkan ke nama service: redis)
REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

6. Menjalankan dan Menginisialisasi Sistem

Koleksi konfigurasi sudah lengkap. Saatnya menyalakan seluruh stack dan menginisialisasi framework Laravel di dalam container.

Jalankan perintah ini secara berurutan di terminal:

Bash
# 1. Build image dan nyalakan container di background (detached mode)
docker compose up -d --build

# 2. Cek status apakah semua container sudah berstatus Up / Healthy
docker compose ps
Setelah keempat container berjalan (laravel_app, laravel_webserver, laravel_db, laravel_redis), kita eksekusi perintah pemeliharaan Laravel melalui container app:

Bash
# 3. Install seluruh library PHP Composer di dalam container
docker compose exec app composer install

# 4. Generate Application Encryption Key
docker compose exec app php artisan key:generate

# 5. Jalankan migrasi database
docker compose exec app php artisan migrate

# 6. Atur hak akses permission untuk folder writable Laravel
docker compose exec app chmod -R 775 storage bootstrap/cache

# 7. Buat symlink storage ke folder public
docker compose exec app php artisan storage:link
Buka browser kamu dan navigasikan ke http://localhost. Selamat! Halaman selamat datang Laravel sekarang berjalan mulus di atas stack Docker kamu.

7. Troubleshooting Umum: Jam Terbang Senior

Bahkan engineer berpengalaman pun pasti pernah ketemu kendala saat berurusan dengan Docker. Ini beberapa solusi cepat untuk problem yang paling sering muncul:

Kendala 1: "The stream or file .../storage/logs/laravel.log could not be opened: Permission denied"

  • Penyebab: User di host komputer kamu memiliki User ID (UID) yang berbeda dengan user di dalam container Linux, sehingga proses PHP-FPM tidak diizinkan menulis ke disk.
  • Solusi: Jalankan perbaikan permission langsung via container:

    Bash
    docker compose exec app chown -R appuser:appuser /var/www/storage /var/www/bootstrap/cache
    docker compose exec app chmod -R 775 /var/www/storage /var/www/bootstrap/cache
    

Kendala 2: "SQLSTATE[HY000] [2002] Connection refused"

  • Penyebab: Container app menyala lebih cepat daripada engine MySQL yang butuh inisialisasi tabel database saat pertama kali di-build.
  • Solusi: Tunggu 15–30 detik sampai engine MySQL benar-benar selesai melakukan startup. Periksa log MySQL dengan perintah:

    Bash
    docker compose logs db
    
    Begitu log menampilkan "ready for connections", ulangi perintah docker compose exec app php artisan migrate.

Kendala 3: Cache Config Mengunci Pengaturan Lama

  • Penyebab: Laravel menyimpan cache setting konfigurasi lama dari luar Docker (misalnya DB_HOST=127.0.0.1).
  • Solusi: Bersihkan semua konfigurasi cache:

    Bash
    docker compose exec app php artisan config:clear
    docker compose exec app php artisan cache:clear
    

8. Checklist Transisi ke Production

Setup yang kita buat di atas sangat ideal untuk local development. Namun, saat kamu mau membawa image ini ke level production (seperti AWS ECS, DigitalOcean App Platform, atau Kubernetes), perhatikan poin-poin berikut:

  • Multi-Stage Build Dockerfile: Jangan bawa compiler dan tool dev ke production. Gunakan multi-stage build untuk meng-compile asset front-end (Vite/Tailwind) dan dependencies Composer (composer install --no-dev --optimize-autoloader), lalu salin artefak finalnya saja ke production image agar ukuran file image jadi ramping (di bawah 150MB).
  • Pisahkan Database: Jangan menjalankan container database di server yang sama dengan web apps di production kecuali budget memang sangat ketat. Gunakan managed database service seperti AWS RDS atau DigitalOcean Managed MySQL untuk automatic backup, multi-AZ failover, dan scaling.
  • Jalankan Queue Worker Terpisah: Jika aplikasi kamu memproses jobs (seperti kirim email atau generate PDF), buat service baru di docker-compose.yml dengan image yang sama namun dengan command php artisan queue:work agar tidak membebani proses request web utama.
  • Aktifkan Caching Framework: Di production, selalu jalankan php artisan config:cache, php artisan route:cache, dan php artisan view:cache pada pipeline CI/CD kamu untuk memangkas waktu booting framework.
Docker bukan lagi sekadar tren, melainkan standar de facto arsitektur software saat ini. Menguasai containerization membuat workflow development kamu jauh lebih konsisten, aman, dan mudah dideploy ke cloud provider mana pun tanpa kejutan error di tengah malam.

Selamat mencoba, eksplorasi konfigurasinya, dan selamat tinggal pada drama "it works on my machine!"
 
Sigit Wasis Subekti

Sigit Wasis Subekti

Software Engineer & Tech Educator

Software Engineer and Tech Educator sharing insights on web development and software architecture.